{"id":47829658,"url":"https://github.com/yobeonline/io1-money","last_synced_at":"2026-04-03T20:08:14.421Z","repository":{"id":41889938,"uuid":"467631591","full_name":"yobeonline/io1-money","owner":"yobeonline","description":"A c++ class to hold money amounts.","archived":false,"fork":false,"pushed_at":"2025-12-24T12:27:23.000Z","size":201,"stargazers_count":2,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-25T22:19:38.819Z","etag":null,"topics":["accounting","cpp","cpp20","currency","money"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yobeonline.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-08T18:31:46.000Z","updated_at":"2025-12-24T12:20:52.000Z","dependencies_parsed_at":"2023-08-20T09:40:38.586Z","dependency_job_id":"7230054c-0db7-4fcd-85e8-31a00429f753","html_url":"https://github.com/yobeonline/io1-money","commit_stats":null,"previous_names":["yobeonline/io1-money"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/yobeonline/io1-money","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yobeonline%2Fio1-money","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yobeonline%2Fio1-money/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yobeonline%2Fio1-money/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yobeonline%2Fio1-money/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yobeonline","download_url":"https://codeload.github.com/yobeonline/io1-money/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yobeonline%2Fio1-money/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31374133,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T17:53:18.093Z","status":"ssl_error","status_checked_at":"2026-04-03T17:53:17.617Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accounting","cpp","cpp20","currency","money"],"created_at":"2026-04-03T20:08:13.770Z","updated_at":"2026-04-03T20:08:14.402Z","avatar_url":"https://github.com/yobeonline.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Money\n\n`io1::money` is a header-only c++ 20 class that holds money amounts in the range  -9,223,372,036,854,775,808  +9,223,372,036,854,775,807. Amounts are intended to be expressed in the lowest subdivision allowed by a currency (eg. cents for USD), hence the integer base type. Localized formatting is supported by the standard `moneypunct`facet. Arithmetic operations involving `io1::money` instances and integers are exact (yet subject to overflow). Arithmetic operations with floats are rounded towards the nearest even integer (aka banker’s rounding).\n\n# Install\n\nConfigure and install with `cmake`:\n\n```shell\ncmake -B build WITH_TESTS=OFF\ncmake --install build --prefix \u003cinstall_dir\u003e\n```\n\nCall `find_package` from your `CMakeLists.txt` file:\n\n```cmake\nfind_package(io1 REQUIRED COMPONENTS money)\ntarget_link_libraries(\u003ctarget\u003e PRIVATE io1::money)\n```\n\n# Rationale\n\nAn amount like \\$0.10 cannot be represented by a float because of the involved loss of precision that would make the following operation unbalanced: \\$1.00≠\\$0.10+\\$0.10… (ten times). Besides, not all currencies have a 1/100 subdivision[^dinar] or even a decimal subdivision[^ougiya]. As a result, money amounts are stored as plain integer values and formatting them with the correct currency / sub-currency format is left to the user of the class. For example, the value 12550 stored in a `io1::money` instance may be formatted as $125.50, 12.550 DT or 2510 UM depending on the locale context. The `io1` namespace provides overloads to `std::put_money` and `std::get_money` into order to format `io1::money` instances with the currently imbued `std::moneypunct` facet.\n\nTo prevent any unexpected rounding issue, it is not possible to construct a `io1::money` instance from a floating-point type. Multiplication and division by a floating-point type are provided and round to the nearest even integer. They are intended to support operations like percentages or loan interest rate calculations.\n\nInteger and `io1::money` arithmetic is exact but may overflow in the same conditions as any integers. Integer division may throw an exception if the result is not exact.\n\n[^dinar]:  In Tunisia 1 TND equals 1000 millimes.\n[^ougiya]: In Mauritania 1 MRU equals 5 khoums.\n\n# References\n\n`io1::money` is a trivial class (`std::is_trivial_v\u003cio1::money\u003e == true`) and has standard layout (`std::is_standard_layout_v\u003cio1::money\u003e == true`).\n\n## Traits\n\n```cpp\nio1::money::value_type;\n```\n\nThe underlying type used to store amounts.\n\n## Constructors\n\n```cpp\nio1::money::money() noexcept; (1)\nconstexpr io1::money::money(io1::money const \u0026 o) noexcept; (2)\ntemplate\u003cstd::integral T\u003e explicit constexpr io1::money(T amount) noexcept; (3)\ntemplate\u003cchar... STR\u003e constexpr io1::money operator \"\"_money() noexcept; (4)\n```\n\n(1)    Create an uninitialized instance. Such instances are meant to be initialized by copy assignment. Before they are they can only be destructed. Any other use of a `void` constructed `io1::money` has undefined behavior.\n\n(2)    Create an instance which amount is the same as `o`.\n\n(3)    Create an instance which amount is `amount`. If `amount` overflows the capacity of `io1::money` then it has undefined behavior.\n\n(4)    If provided with an integer number, it behaves like (3) except that overflows are diagnosed with a compilation failure.\nIf provided with a float number, it deduces the lowest currency subdivision amount by ignoring the decimal separator (see the example below).\n\n### Example\n\n```cpp\nstatic_assert(io1::money(120) == 120_money, \"Assuming lowest subdivision.\");\nstatic_assert(120_money == 1.20_money,      \"Assuming 1/100th subdivision.\");\nstatic_assert(12_money == 1.2_money,        \"Assuming 1/10th subdivision.\");\nstatic_assert(102_money == 1.02_money,      \"Assuming 1/100th subdivision.\");\nstatic_assert(1022_money == 1.022_money,    \"Assuming 1/1000th subdivision.\");\n```\n\n## Observers\n\n```cpp\n[[nodiscard]] constexpr io1::money::value_type const \u0026 io1::money::data() const noexcept;\n```\n\nReturns the amount stored by the instance.\n\n## Operator Overloads\n\n### Increment / Decrement\n\n```cpp\n[[nodiscard]] constexpr io1::money io1::money::operator++(int ignored) noexcept; (1)\n[[nodiscard]] constexpr io1::money io1::money::operator--(int ignored) noexcept; (2)\nconstexpr io1::money \u0026 io1::money::operator++() noexcept; (3)\nconstexpr io1::money \u0026 io1::money::operator--() noexcept; (4)\n```\n\n(1, 2)    Post increment/decrement operator. The `int` argument is ignored. Overflow has undefined behavior.\n\n(3, 4)    Pre increment/decrement operator. Overflow has undefined behavior.\n\n### Assignment Operators\n\n```cpp\nconstexpr io1::money \u0026 io1::money::operator=(io1::money const \u0026 m) noexcept; (1)\nconstexpr io1::money \u0026 io1::money::operator+=(io1::money m) noexcept; (2)\nconstexpr io1::money \u0026 io1::money::operator-=(io1::money m) noexcept; (3)\ntemplate\u003cstd::integral T\u003e constexpr io1::money \u0026 io1::money::operator*=(T i) noexcept; (4)\ntemplate\u003cstd::integral T\u003e constexpr io1::money \u0026 io1::money::operator/=(T i); (5)\nio1::money \u0026 io1::money::operator*=(long double f) noexcept; (6)\nio1::money \u0026 io1::money::operator/=(long double f) noexcept; (7)\n```\n\n(1)    Copy assignment.\n\n(2, 3)    Addition and subtraction assignment. Replace the amount with the result of addition / subtraction the between the previous amount and the amount of `m`. Overflow has undefined behavior.\n\n(4)    Multiplication by an integer assignment. Replace the amount with the result of the multiplication of the previous amount by `i`. Overflow has undefined behavior.\n\n(5)    Division by an integer assignment. Replace the amount with the result of the division of the previous amount by `i`. If the result is not exact, the operator throws an instance of `io1::money::inexact_division_error` and provides the strong exception guarantee (see example below). Dividing by zero has undefined behavior.\n\n(6, 7)    Multiplication and division by a float. Replace the amount with the result of the multiplication / division of the previous amount by `f`. Overflow and division by zero have undefined behavior. Inexact results are rounded to nearest even integer. These operators assert that the current floating-point rounding mode is still (or has been restored to) the default value of `FE_TONEAREST`.\n\nMultiplication and division assignments from `io1::money` instances are not provided because they would violate dimensional analysis and would hence allow flawed uses of money arithmetic. For example, consider the result of 5 USD multiplied by 10 USD. According to dimensional analysis, the result should be homogeneous to USD _squared_, hence 50 USD² and not 50 USD. Likewise, 10 USD divided by 5 USD equals 2 (no dimension) and not 2 USD.\n\n### Example\n\n```cpp\nio1::money m = 10_money;\nm /= 2; // ok, the result is 5_money\ntry { m /= 2; }\ncatch (io1::money::inexact_division_error const \u0026 e)\n{\n    std::cerr \u003c\u003c \"Dividing \" \u003c\u003c e.dividend \u003c\u003c \" by \" \u003c\u003c e.divisor \u003c\u003c \" is not exact.\\n\";\n    std::cout \u003c\u003c \"m still holds: \" \u003c\u003c m \u003c\u003c '\\n'; // 5_money\n}\n```\n\n### Arithmetic Operators\n\n```cpp\n[[nodiscard]] constexpr io1::money io1::money::operator-() const noexcept; (1)\n```\n\n(1)    Return an instance with an opposite amount. Overflow has undefined behavior.\n\nThe following operators are built from the assignment operators of the previous section and thus have the same restrictions.\n\n```cpp\n[[nodiscard]] constexpr io1::money io1::operator+(io1::money lhs, io1::money rhs) noexcept;\n[[nodiscard]] constexpr io1::money io1::operator-(io1::money lhs, io1::money rhs) noexcept;\ntemplate\u003cstd::integral T\u003e [[nodiscard]] constexpr io1::money io1::operator*(io1::money lhs, T rhs) noexcept;\ntemplate\u003cstd::integral T\u003e [[nodiscard]] constexpr io1::money io1::operator*(T lhs, io1::money rhs) noexcept;\ntemplate\u003cstd::integral T\u003e [[nodiscard]] constexpr io1::money io1::operator/(io1::money lhs, T rhs);\n[[nodiscard]] io1::money io1::operator*(long double lhs, io1::money rhs) noexcept;\n[[nodiscard]] io1::money io1::operator/(io1::money lhs, long double rhs) noexcept;\n```\n\nRight multiplication by `long double` is not provided because it has more rounding issues than left multiplication. Consider the following example.\n\n```cpp\nauto const m1 = 2_money * 0.1 * 10.; (1)\nauto const m1 = 0.1 * 10. * 2_money; (2)\n```\n\n(1)    Left to right evaluation results in `0_money` because of the intermediate rounding that occurs.\n\n(2)    Left to right evaluation gives the expected result of `2_money`.\n\n```cpp\n[[nodiscard]] io1::moneydiv_t io1::div(io1::money m, io1::money::value_type divisor) noexcept;\n```\n\nCompute both the quotient (`io1::moneydiv_t::quot`) and remainder (`io1::moneydiv_t::rem`) of `m` divided by `divisor`. It has undefined behavior if `0 == divisor`. Returned `quot` and `rem` are such that `divisor * quot + rem == m` and `std::abs(rem) \u003c std::abs(m)`. Like `std::div_t`, `io1::moneydiv_t` may be implemented as\n\n```cpp\nstruct moneydiv_t { io1::money quot; io1::money rem; };\n```\n\nor\n\n```cpp\nstruct moneydiv_t { io1::money rem; io1::money quot; };\n```\n\n### Comparison Operators\n\n```cpp\n[[nodiscard]] constexpr std::strong_ordering io1::operator\u003c=\u003e(io1::money lhs, io1::money rhs) noexcept;\n[[nodiscard]] constexpr bool io1::operator==(io1::money lhs, io1::money rhs) noexcept;\n[[nodiscard]] constexpr bool io1::operator!=(io1::money lhs, io1::money rhs) noexcept;\n[[nodiscard]] constexpr bool io1::operator\u003c(io1::money lhs, io1::money rhs) noexcept;\n[[nodiscard]] constexpr bool io1::operator\u003c=(io1::money lhs, io1::money rhs) noexcept;\n[[nodiscard]] constexpr bool io1::operator\u003e(io1::money lhs, io1::money rhs) noexcept;\n[[nodiscard]] constexpr bool io1::operator\u003e=(io1::money lhs, io1::money rhs) noexcept;\n```\n\n### Stream Operators\n\n```cpp\nstd::ostream \u0026 operator\u003c\u003c(std::ostream \u0026 stream, io1::money m) noexcept; (1)\nstd::istream \u0026 operator\u003e\u003e(std::istream \u0026 stream, io1::money \u0026 m); (2)\n[[nodiscard]] /*unspecified*/ io1::put_money(io1::money const \u0026 m, bool intl = false) noexcept; (3)\n[[nodiscard]] /*unspecified*/ io1::get_money(io1::money \u0026 m, bool intl = false); (4)\n```\n\n(1)    Write the amount held by `m` in `stream`.\n\n(2)    Parse an instance of `io1::money` from `stream` and copy assign it to `m`. The implementation is equivalent to the following code.\n\n```cpp\nstd::istream \u0026 operator\u003e\u003e(std::istream \u0026 stream, io1::money \u0026 m)\n{\n    io1::money::value_type amount;\n    if (stream \u003e\u003e amount) m = io1::money{amount};\n    return stream;\n}\n```\n\n(3)    Return an object of unspecified type that can be inserted into a `std::ostream` instance to format `m` according to its current `moneypunct` facet. The `intl` argument, if true, uses the international currency string instead of the currency symbol.\n\n(4)    Return an object of unspecified type that can extract an instance of `io1::money` from a `std::istream` instance according to its current `moneypunct` facet. The `intl` argument, if true, expects to find a required international currency string instead of an optional currency symbol.\n\n### Standard Format Support\n\nThe syntax of format specifications is the same as the standard syntax for `integers`. Alternatively, localized formatting through the current `moneypunct` facet can be achieved with the following syntax:\n\n\u003e  _fill-and-align_(optional) _width_(optional) _#_(optional) _type_(m or M)\n\n#### Fill, Align and Width\n\nSame as standard specifications for `std::string`.\n\n#### Show Currency \\#\n\nDisplay currency in a way equivalent to activating the `showbase` I/O manipulator on streams.\n\n#### Type\n\nThe type option must be one of the following:\n\n- **m**: format with the `std::moneypunct\u003cCharT, false\u003e` facet.\n\n- **M**: format with the `std::moneypunct\u003cCharT, true\u003e` facet.\n\n#### Examples\n\n```cpp\nstd::cout \u003c\u003c std::format(\"{}\\n\", 12.35_money);\nstd::cout \u003c\u003c std::format(\"{:m}\\n\", 12.35_money);\nstd::cout \u003c\u003c std::format(\"{:M)\\n\", 12.35_money);\nstd::cout \u003c\u003c std::format(std::locale(\"en_US.UTF-8\"), \"{:m}\\n\", 12.35_money);\nstd::cout \u003c\u003c std::format(std::locale(\"en_US.UTF-8\"), \"{:M}\\n\", 12.35_money);\nstd::cout \u003c\u003c std::format(std::locale(\"en_US.UTF-8\"), \"{:#m}\\n\", 12.35_money);\nstd::cout \u003c\u003c std::format(std::locale(\"en_US.UTF-8\"), \"{:#M}\\n\", 12.35_money);\n```\n\nAssuming the global locale is the default C++ locale, the above code prints out:\n\n```cpp\n1235\n1235\n1235\n12.35\n 12.35\n$12.35\nUSD  12.35\n```\n\n## Exceptions\n\n```cpp\nstruct [[nodiscard]] io1::money::inexact_division_error\n{\n    io1::money::value_type dividend;\n    io1::money::value_type divisor;\n};\n```\n\nException thrown when trying to divide `dividend` by `divisor` and `dividend % divisor != 0`.\n\n# Tutorial\n\n```cpp\n// file test/tutorial.cpp\n#include \"io1/money.hpp\"\n\n#include \u003cdoctest/doctest.h\u003e\n#include \u003cnumeric\u003e\n#include \u003csstream\u003e\n#include \u003cvector\u003e\n\nclass american_moneypunct_facet : public std::moneypunct\u003cchar, false\u003e\n{\nprivate:\n  char do_decimal_point() const override { return '.'; };\n  char do_thousands_sep() const override { return ','; };\n  std::string do_grouping() const override { return \"\\003\"; };\n  std::string do_curr_symbol() const override { return \"$\"; };\n  std::string do_positive_sign() const override { return \"\"; };\n  std::string do_negative_sign() const override { return \"-\"; };\n  int do_frac_digits() const override { return 2; };\n  pattern do_pos_format() const override { return {{symbol, sign, value}}; };\n  pattern do_neg_format() const override { return {{symbol, sign, value}}; };\n};\n\n[[nodiscard]] auto compute_installment_plan(io1::money price, std::size_t count) noexcept\n{\n  assert(0 != count \u0026\u0026 \"Not doing a plan for no payment.\");\n\n  auto const div_result = div(price, count);\n  assert(div_result.rem \u003e= 0_money \u0026\u0026 \"Count was unsigned.\");\n\n  auto const m = static_cast\u003cstd::size_t\u003e(div_result.rem.data());\n  std::vector\u003cio1::money\u003e plan(count, div_result.quot);\n  assert(m \u003c plan.size() \u0026\u0026 \"As per the io1::div documentation since plan.size() is count.\");\n\n  for (std::size_t i = 0; i \u003c m; ++i) ++plan[i];\n\n  return plan;\n}\n\nTEST_CASE(\"Tutorial\")\n{\n  auto const unit_price = 12.00_money;\n  auto const vat = 1.2;\n  auto const discount_rate = 10. / 100.;\n\n  // Parse How many items are bought:\n  unsigned int nb = 0;\n  std::stringstream(\"2\") \u003e\u003e nb;\n\n  // Parse how much tip is given:\n  auto tip = 0_money;\n  {\n    std::stringstream cin(\"$1.00\");\n    cin.imbue(std::locale(cin.getloc(), std::make_unique\u003camerican_moneypunct_facet\u003e().release()));\n    cin \u003e\u003e get_money(tip);\n  }\n\n  // Parse how many installments for payment plan\n  std::size_t installments_nb = 0;\n  std::stringstream(\"5\") \u003e\u003e installments_nb;\n\n  auto const final_price = tip + nb * (1. - discount_rate) * vat * unit_price;\n  std::stringstream cout;\n  cout.imbue(std::locale(cout.getloc(), std::make_unique\u003camerican_moneypunct_facet\u003e().release()));\n  cout \u003c\u003c \"Total: \" \u003c\u003c std::showbase \u003c\u003c put_money(final_price) \u003c\u003c '\\n';\n\n  auto const payment_plan = compute_installment_plan(final_price, installments_nb);\n  cout \u003c\u003c installments_nb \u003c\u003c \" payments:\";\n  for (auto const \u0026 amount : payment_plan) cout \u003c\u003c ' ' \u003c\u003c put_money(amount);\n\n  CHECK_EQ(std::accumulate(payment_plan.begin(), payment_plan.end(), 0_money), final_price);\n  CHECK_EQ(cout.str(), \"Total: $26.92\\n5 payments: $5.39 $5.39 $5.38 $5.38 $5.38\");\n\n  return;\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyobeonline%2Fio1-money","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyobeonline%2Fio1-money","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyobeonline%2Fio1-money/lists"}