Anchored Decimal
Floating point type is fast, but not accurate.
With finite bits, binary cannot represent decimal fractions.
Fixed point type is accurate, but not independent.
Conversely, more integer digits means fewer fractional digits.
Rust decimal is comfortable, but still a fixed point decimal.
Internally, an integer with a scale factor and a sign bit.
Anchored decimal is fast, accurate, independent, and comfortable.
Natively, two independent integers, each with nineteen digits.
Benchmarks
All Types vs rust_decimal
| Operation |
AncDec8 |
AncDec32 |
AncDec |
AncDec128 |
rust_decimal |
| add |
2.8 ns |
4.0 ns |
6.3 ns |
14.5 ns |
11.4 ns |
| sub |
3.2 ns |
3.9 ns |
6.2 ns |
14.9 ns |
11.4 ns |
| mul |
4.4 ns |
4.3 ns |
7.4 ns |
13.5 ns |
11.1 ns |
| div |
3.8 ns |
5.5 ns |
13.4 ns |
20.3 ns |
20.5 ns |
| cmp |
1.2 ns |
3.0 ns |
4.4 ns |
8.0 ns |
5.1 ns |
| parse |
5.7 ns |
10.5 ns |
10.8 ns |
14.6 ns |
10.4 ns |
Speedup vs rust_decimal
| Operation |
AncDec8 |
AncDec32 |
AncDec |
AncDec128 |
| add |
4.07x |
2.85x |
1.81x |
0.79x |
| sub |
3.56x |
2.92x |
1.84x |
0.77x |
| mul |
2.52x |
2.58x |
1.50x |
0.82x |
| div |
5.39x |
3.73x |
1.53x |
1.01x |
| cmp |
4.25x |
1.70x |
1.16x |
0.64x |
| parse |
1.82x |
~1.0x |
~1.0x |
0.71x |
AncDec128 High Precision
| Operation |
AncDec128 |
rust_decimal |
Ratio |
| mul |
19.6 ns |
18.3 ns |
1.07x |
| div |
54.5 ns |
55.8 ns |
0.98x |
| parse |
34.7 ns |
30.3 ns |
1.15x |
AncDec128 is comparable to rust_decimal while supporting 38+38 digit precision vs rust_decimal's 28 shared digits.
Benchmarked on Intel Core i7-10750H @ 2.60GHz, Rust 1.87.0, release mode
https://crates.io/crates/ancdec
In what way does this differ from rust_decimal and what are the use cases for one or the other? Do you have concrete examples for when rust_decimal is not enough? What are the tradeoffs?
Rust Decimal is accurate 28 digits, but it shares integer parts and fractional part. So if you contain value like 999_999_999_999_999_999_999_999_999 in integer(27 digits) then you can only contain one digit in fractional part. Also the reverse is same. It happens when you calculate some values with black rock’s aum.
The precision depends on the range.
Well, if floating-point is not accurate because it cannot represent decimal fractions, then AncDev is not accurate because it cannot represent ternary fractions.
What do you expect people to use this for? Decimalized money is the only thing that comes to mind, but for that the structure seems odd to me:
pub struct AncDec {
pub int: u64,
pub frac: u64,
pub scale: u8,
pub neg: bool,
}
That means changing scale needs to move things between int and frac, which makes simple things like ×10 more expensive than I'd expect in a decimal library.
I'd have expected something more like an i128 or i64 that stores a decimal scale in the bottom few bits, or something. What are you doing that needs 38+38 precision in specifically decimal?
So what you mean is that you can have 38 digits at the left of decimal point, and 38 digits at the right of decimal point ?
Fixed point (i64): integer digits shrink as scale grows
scale=4:922337203685477.5807
scale=8:______922337203.68547758
_______←—- integer —-→.← frac →
19+19 digits (independent)
max:18446744073709551615.18446744073709551615
e.g:9999999999999999999.9999999999999999999
___←——— 19 digits -—→ ←—- 19 digits ——→
rust_decimal's structure: [flags: u32] [hi: u32] [mid: u32] [lo: u32]
How rust_decimal calculate the value: (hi * 2^64 + mid * 2^32 + lo) / 10^scale
It uses 96 bits of unsigned integer.
So it is basically fixed point decimal.
// 1. Finance - Interest calculation on large amounts
let gdp = Decimal::from_str("1000000000000").unwrap(); // 13 integer digits used
let rate = Decimal::from_str("0.0325178923456789").unwrap(); // needs 16 decimal places
let interest = gdp * rate;
// integer part consumed 13 digits → only 15 digits left for decimals
// last digit of rate gets truncated: 0.0325178923456789 → 0.032517892345678
// → millions in error on trillion-scale interest calculations
// 2. Crypto - Small unit + large quantity
let amount = Decimal::from_str("999999999999999999.00000001").unwrap(); // 18 integer + scale 8
let fee = Decimal::from_str("0.00000000001").unwrap(); // needs scale 11
let total = amount + fee;
// 18 + 11 = 29 digits → exceeds 28-digit pool
// last decimal digit of fee silently dropped → fee partially disappears
// 3. Scientific - Precision boundary
let big = Decimal::from_str("9999999999999999999999999999").unwrap(); // 28 integer digits
let small = Decimal::from_str("0.1").unwrap();
let result = big + small;
// no digits left for decimals → 0.1 vanishes entirely
// result == big