{"id":26677384,"url":"https://github.com/wubingzheng/primitive_fixed_point_decimal","last_synced_at":"2025-07-15T10:03:31.760Z","repository":{"id":165102772,"uuid":"640492694","full_name":"WuBingzheng/primitive_fixed_point_decimal","owner":"WuBingzheng","description":"Primitive fixed-point decimal types in Rust.","archived":false,"fork":false,"pushed_at":"2025-07-08T01:48:25.000Z","size":315,"stargazers_count":43,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-08T03:47:21.721Z","etag":null,"topics":["decimal","fixed-point","no-std"],"latest_commit_sha":null,"homepage":"https://docs.rs/primitive_fixed_point_decimal","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WuBingzheng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-MIT","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}},"created_at":"2023-05-14T09:19:50.000Z","updated_at":"2025-07-08T01:48:28.000Z","dependencies_parsed_at":"2025-05-31T11:34:47.333Z","dependency_job_id":"27b4a608-2c4b-4545-91e7-1f1bdc8df96b","html_url":"https://github.com/WuBingzheng/primitive_fixed_point_decimal","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/WuBingzheng/primitive_fixed_point_decimal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WuBingzheng%2Fprimitive_fixed_point_decimal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WuBingzheng%2Fprimitive_fixed_point_decimal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WuBingzheng%2Fprimitive_fixed_point_decimal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WuBingzheng%2Fprimitive_fixed_point_decimal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WuBingzheng","download_url":"https://codeload.github.com/WuBingzheng/primitive_fixed_point_decimal/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WuBingzheng%2Fprimitive_fixed_point_decimal/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265426893,"owners_count":23763177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["decimal","fixed-point","no-std"],"created_at":"2025-03-26T04:33:13.751Z","updated_at":"2025-07-15T10:03:31.730Z","avatar_url":"https://github.com/WuBingzheng.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# primitive_fixed_point_decimal\n\nPrimitive fixed-point decimal types.\n\nRust built-in `f32` and `f64` types have two drawbacks:\n\n1. can not represent decimal numbers in base 10 accurately, because they are in base 2;\n\n2. can not guarantee the fraction precision, because they are floating-point.\n\nThis crate provides fixed-point decimal types to address the issues by\n\n1. using integer types to represent numbers with a scaling factor (also\n   called as \"scale\") in base 10 to achieve the accuracy. This is a\n   [common idea](https://en.wikipedia.org/wiki/Fixed-point_arithmetic#Representation).\n   Many other decimal crates do the same thing;\n\n2. specifying the scale staticly to guarantee the fraction precision.\n   The scale is bound to the decimal type. It's fixed-point. Surprisingly,\n   it seems that [no crate has done this before](https://github.com/WuBingzheng/primitive_fixed_point_decimal/blob/master/COMPARISON.md).\n\nFor example, `ConstScaleFpdec\u003ci64, 4\u003e` means using `i64` as the underlying\nrepresentation, and `4` is the static scale.\n\nThe \"primitive\" in the crate name means straightforward representation,\ncompact memory layout, high performance, and clean APIs, just like Rust's\nprimitive number types.\n\nThis crate is `no_std`.\n\n\n## Distinctive\n\nAlthough other decimal crates also claim to be fixed-point, they all\nbind the scale to each decimal *instance*, which changes during operations.\nThey're more like\n[decimal floating point](https://en.wikipedia.org/wiki/Decimal_floating_point).\nSee the [comparison documentation](https://github.com/WuBingzheng/primitive_fixed_point_decimal/blob/master/COMPARISON.md)\nfor details.\n\nWhile this crate binds the scale to decimal *type*.\nThe decimal types keep their scale for their whole lifetime\ninstead of changing their scale during operations.\n\nThe `+`, `-` and comparison operations only perform between same types\nin same scale. There is no implicitly type or scale conversion.\nThis makes sence, for we do not want to add balance type by\nfee-rate type. Even for two balance types we do not want to add\nUSD currency by CNY. This also makes the operations very fast.\n\nHowever, the `*` and `/` operations accept operand with different\ntypes and scales, and allow the result's scale specified.\nCertainly we need to multiply between balance type and fee-rate type\nand get balance type.\n\nIf each decimal type has a fixed scale in you application, which means\nall the decimal instances under each type have the same scale, it's\nsuitable for this crate. Otherwise, you should use other floating-point\ndecimal crates.\n\nSee the examples below for more details.\n\n\n## Specify Scale\n\nThere are 2 ways to specify the scale: *const* and *out-of-band*:\n\n- For the *const* type [`ConstScaleFpdec`], we use Rust's *const generics*\n  to specify the scale. For example, `ConstScaleFpdec\u003ci64, 4\u003e` means\n  scale is 4.\n\n- For the *out-of-band* type [`OobScaleFpdec`], we do NOT save the\n  scale with decimal types, so it's your job to save it somewhere\n  and apply it in the following operations later. For example,\n  `OobScaleFpdec\u003ci64\u003e` takes no scale information.\n\nGenerally, the *const* type is more convenient and suitable for most\nscenarios. For example, in traditional currency exchange, you can use\n`ConstScaleFpdec\u003ci64, 2\u003e` to represent balance, e.g. `1234.56` USD and\n`8888800.00` JPY. And use `ConstScaleFpdec\u003ci32, 6\u003e` to represent all\nmarket prices since 6-digit-scale is big enough for all currency\npairs, e.g. `146.4730` JPY/USD and `0.006802` USD/JPY:\n\n```rust\nuse primitive_fixed_point_decimal::{ConstScaleFpdec, fpdec};\ntype Balance = ConstScaleFpdec\u003ci64, 2\u003e; // 2 is enough for all currencies\ntype Price = ConstScaleFpdec\u003ci32, 6\u003e; // 6 is enough for all markets\n\nlet usd: Balance = fpdec!(1234.56);\nlet price: Price = fpdec!(146.4730);\n\nlet jpy: Balance = usd.checked_mul(price).unwrap();\nassert_eq!(jpy, fpdec!(180829.70688));\n```\n\nHowever in some scenarios, such as in cryptocurrency exchange, the\nprice differences across various markets are very significant. For\nexample `81234.0` in BTC/USDT and `0.000004658` in PEPE/USDT. Here\nwe need to select different scales for each market. So it's\nthe *Out-of-band* type:\n\n```rust\nuse primitive_fixed_point_decimal::{OobScaleFpdec, fpdec};\ntype Balance = OobScaleFpdec\u003ci64\u003e; // no global scale set\ntype Price = OobScaleFpdec\u003ci32\u003e; // no global scale set\n\n// each market has its own scale configuration\nstruct Market {\n    base_asset_scale: i32,\n    quote_asset_scale: i32,\n    price_scale: i32,\n}\n\n// let's take BTC/USDT market as example\nlet btc_usdt = Market {\n    base_asset_scale: 8,\n    quote_asset_scale: 6,\n    price_scale: 1,\n};\n\n// we need tell the scale to `fpdec!`\nlet btc: Balance = fpdec!(0.34, btc_usdt.base_asset_scale);\nlet price: Price = fpdec!(81234.0, btc_usdt.price_scale);\n\n// we need tell the scale difference to `checked_mul()` method\nlet diff = btc_usdt.base_asset_scale + btc_usdt.price_scale - btc_usdt.quote_asset_scale;\nlet usdt = btc.checked_mul(price, diff).unwrap();\nassert_eq!(usdt, fpdec!(27619.56, btc_usdt.quote_asset_scale));\n```\n\nObviously it's verbose to use, but offers greater flexibility.\n\nAnother example is the SQL `Decimal` data type.\nIn the server end, the scale of each decimal column is fixed on created\n(at runtime), so it fits `OobScaleFpdec`.\nWhile in the client end, the application knows the business logical and\nthe scale of each decimal column ahead (at compilation time), so it fits\n`ConstScaleFpdec`.\n\n\n## Cumulative Error\n\nAs is well known, integer division can lead to precision loss; multiplication\nof decimals can also create higher precision and may potentially cause\nprecision loss.\n\nWhat we are discussing here is another issue: multiple multiplication and\ndivision may cause cumulative error, thereby exacerbating the issue of\nprecision loss. See [`int-div-cum-error`](https://docs.rs/int-div-cum-error)\nfor more information.\n\nIn this crate, functions with the `cum_error` parameter provide control\nover cumulative error based on `int-div-cum-error`.\n\nTake the transaction fees in an exchange as an example. An order may be\nexecuted in multiple deals, with each deal independently charged a fee.\nFor instance, the funds scale is 2 decimal places, one order quantity\nis `10.00` USD, and the fee rate is `0.003`. If the order is executed all\nat once, the fee would be `10.00 × 0.003 = 0.03` USD. However, if the\norder is executed in five separate deals, each worth 2.00 USD, then the\nfee for each deal would be `2.00 × 0.003 = 0.006` USD, which rounds up\nto `0.01` USD. Then the total fee for the 5 deals would be `0.05` USD,\nwhich is significantly higher than the original `0.03` USD.\n\nHowever, this issue can be avoid if using the cum_error mechanism.\n\n```rust\nuse primitive_fixed_point_decimal::{ConstScaleFpdec, Rounding, fpdec};\ntype Balance = ConstScaleFpdec\u003ci64, 2\u003e;\ntype FeeRate = ConstScaleFpdec\u003ci16, 6\u003e;\n\nlet deal: Balance = fpdec!(2.00); // 2.00 for each deal\nlet fee_rate: FeeRate = fpdec!(0.003);\n\n// normal case\nlet mut total_fee = Balance::ZERO;\nfor _ in 0..5 {\n    total_fee += deal.checked_mul(fee_rate).unwrap(); // 2.00*0.003=0.006 ~\u003e 0.01\n}\nassert_eq!(total_fee, fpdec!(0.05)); // 0.05 is too big\n\n// use `cum_error`\nlet mut cum_error = 0;\nlet mut total_fee = Balance::ZERO;\nfor _ in 0..5 {\n    total_fee += deal.checked_mul_ext(fee_rate, Rounding::Round, Some(\u0026mut cum_error)).unwrap();\n}\nassert_eq!(total_fee, fpdec!(0.03)); // 0.03 is right\n```\n\n\n## Features\n\n- `serde` enables serde traits integration (`Serialize`/`Deserialize`).\n\nLicense: MIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwubingzheng%2Fprimitive_fixed_point_decimal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwubingzheng%2Fprimitive_fixed_point_decimal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwubingzheng%2Fprimitive_fixed_point_decimal/lists"}