AncDec
Anchored Decimal
A no_std Rust decimal where fraction precision never shrinks as integers grow — four types from 4 bytes (embedded) to 40 bytes (institutional), integer and fraction stored independently.
- AncDec8 (u8): 2-digit integer + 2-digit fraction, 4 bytes — embedded/IoT
- AncDec32 (u32): 9-digit integer + 9-digit fraction, 12 bytes — general purpose
- AncDec (u64): 19-digit integer + 19-digit fraction, 24 bytes — financial
- AncDec128 (u128): 38-digit integer + 38-digit fraction, 40 bytes — institutional
Why AncDec?
- Stable fraction precision: Fraction digits never shrink as integers grow — each part has its own full-width field
- Exact arithmetic: No floating-point rounding errors
- Overflow-safe: Wide arithmetic (u256/u512) for mul/div prevents overflow
- Fast: Competitive with rust_decimal across all operations
- no_std: Zero heap allocation, embedded-friendly
- Zero dependencies: No external crates required (serde, sqlx optional)
- Safe: All public APIs return
Result, internal panics are unreachable by design
Why AncDec128?
AncDec128 was introduced to handle institutional-scale financial data (e.g., BlackRock fund data) where integer parts regularly exceed u64::MAX (~1.8 x 10^19). When processing total asset values, NAV calculations, or aggregated positions, u64 integer overflow was unavoidable.
AncDec128 provides:
- 38-digit integer part (u128::MAX ~ 3.4 x 10^38)
- 38-digit fractional part (independent)
- Tiered fast paths for arithmetic: u64 -> partial product -> u128 -> u256, selecting the cheapest path automatically
Core Structures
AncDec8 (u8) — 4 bytes
AncDec32 (u32) — 12 bytes
AncDec (u64) — 24 bytes
AncDec128 (u128) — 40 bytes
Why pub(crate) on AncDec8, AncDec32, AncDec128?
Their arithmetic relies on the invariant frac < 10^scale. Fields are pub(crate) to enforce validation through new() with debug_assert! at zero runtime cost in release builds.
AncDec keeps pub fields for backward compatibility and FFI use cases.
All structs use #[repr(C)] layout for FFI bindings.
Installation
[]
= "0.3"
Zero dependencies by default. All 4 types included. Only core is used (no std, no alloc).
Minimal embedded build (single type only):
= { = "0.3", = false, = ["dec8"] }
With serde support:
= { = "0.3", = ["serde"] }
With SQLx (PostgreSQL) support:
= { = "0.3", = ["sqlx"] }
Usage
Construction
use ;
// From string (all types)
let a: AncDec8 = "1.23".parse?;
let b = parse?;
let c: AncDec = "456.789".parse?;
let d = parse?;
// Validated constructor (AncDec8, AncDec32, AncDec128)
let e = new; // 1.23
let f = new; // 123.456
let g = new; // 123.456
// AncDec has pub fields — direct construction
let h = AncDec ;
// From integer primitives
let i: AncDec8 = 42u8.into; // AncDec8: i8, u8
let j: AncDec32 = 1000i32.into; // AncDec32: i8-i32, u8-u32
let k: AncDec = 123i64.into; // AncDec: all 12 integer types
let l: AncDec128 = u128MAX.into; // AncDec128: all 12 integer types
// From float (all types, fallible)
let m = try_from?; // TryFrom<f64>
let n = try_from?; // TryFrom<f32>
Accessors (AncDec8, AncDec32, AncDec128)
let a = new; // -123.456
assert_eq!;
assert_eq!;
assert_eq!;
assert!;
Arithmetic
// All 4 types support: +, -, *, /, %, +=, -=, *=, /=, %=, unary -
let a: AncDec = "12.345".parse?;
let b: AncDec = "1.2".parse?;
let sum = a + b; // 13.545
let diff = a - b; // 11.145
let product = a * b; // 14.814
let quotient = a / b; // 10.2875
let remainder = a % b; // 0.345
let negated = -a; // -12.345
// Reference variants
let c = &a + &b;
let d = a + &b;
let e = &a + b;
// Assign operators
let mut x = a;
x += b;
x -= b;
x *= b;
x /= b;
x %= b;
Primitive Arithmetic
// Direct arithmetic with integer primitives (no conversion needed)
let a: AncDec8 = "1.5".parse?;
let b = a + 2u8; // AncDec8 + u8 → AncDec8
let c = 3i8 * a; // i8 * AncDec8 → AncDec8
let d: AncDec32 = "100.5".parse?;
let e = d + 50i32; // AncDec32 + i32 → AncDec32
let f = 2u16 * d; // u16 * AncDec32 → AncDec32
let g: AncDec = "100.0".parse?;
let h = g / 3i64; // AncDec / i64 → AncDec
let i = 1000u128 - g; // u128 - AncDec → AncDec
// Supported primitive types per variant:
// AncDec8: i8, u8
// AncDec32: i8, i16, i32, u8, u16, u32
// AncDec: i8-i128, isize, u8-u128, usize (all 12 types)
// AncDec128: i8-i128, isize, u8-u128, usize (all 12 types)
Cross-Type Arithmetic
use ;
// Smaller + larger → larger type (all 5 ops: +, -, *, /, %)
let a = parse?;
let b = parse?;
let c: AncDec32 = a + b; // AncDec8 + AncDec32 → AncDec32
let d: AncDec32 = b * a; // AncDec32 * AncDec8 → AncDec32
let e: AncDec = parse? + a; // AncDec + AncDec8 → AncDec
let f: AncDec128 = parse? - b; // AncDec128 - AncDec32 → AncDec128
// 6 pairs: (8↔32), (8↔64), (8↔128), (32↔64), (32↔128), (64↔128)
// Each pair: 5 ops × 2 directions = 10 impls
// Explicit widening via From (lossless)
let g: AncDec32 = from; // AncDec8 → AncDec32
let h: AncDec = from; // AncDec8 → AncDec
let i: AncDec128 = from; // AncDec32 → AncDec128
Math
let a: AncDec = "123.456".parse?;
// Square root (all 4 types)
// AncDec8: 1-digit fractional, AncDec32: 8-digit, AncDec: 18-digit, AncDec128: 37-digit
let root = a.sqrt; // 11.1111075555498660
// Power (all 4 types, supports negative exponents)
let squared = a.pow; // 15241.383936
let cubed = a.pow; // 1881640.295202816
let inverse = a.pow; // 1 / 123.456
let one = a.pow; // 1
// Sign and query (all 4 types)
let abs_val = .abs; // 123.456
let sign = a.signum; // 1
assert!;
assert!;
assert!;
// Range (all 4 types)
let b: AncDec = "200.0".parse?;
let min_val = a.min; // 123.456
let max_val = a.max; // 200.0
let clamped = a.clamp; // 100
Rounding
use RoundMode;
// All 4 types support all 7 rounding modes
let a: AncDec = "123.456789".parse?;
a.round; // 123.46
a.round; // 123.45
a.round; // 123.46
a.round; // 123.46
a.round; // 123.45
a.round; // 123.45
// Convenience methods
a.floor; // 123
a.ceil; // 124
a.trunc; // 123
a.fract; // 0.456789
Conversion
// Output conversions (all 4 types)
let a: AncDec = "123.456".parse?;
let f: f64 = a.to_f64; // 123.456
let i: i64 = a.to_i64; // 123
let i128: i128 = a.to_i128; // 123
// Display with precision (all 4 types)
let s = format!; // "123.456"
let s2 = format!; // "123.45"
let s0 = format!; // "123"
Iterator Support
// Sum and Product (all 4 types, owned and reference)
let values: = vec!
.into_iter
.map
.collect;
let total: AncDec = values.iter.sum; // 6.6
let product: AncDec = values.iter.product; // 7.986
Benchmarks
8-way comparison: all ancdec types vs rust_decimal, fastnum (D256), fixed-num (Dec19x19), bigdecimal.
Arithmetic
| Operation | ancdec8 | ancdec32 | ancdec | ancdec128 | rust_decimal | fastnum_d256 | fixed_num | bigdecimal |
|---|---|---|---|---|---|---|---|---|
| add | 9.0 ns | 7.9 ns | 10.0 ns | 16.0 ns | 12.5 ns | 37.0 ns | 1.9 ns 🥇 | 166 ns |
| sub | — | 8.5 ns | 10.9 ns | 17.2 ns | 13.8 ns | 34.0 ns | 1.9 ns 🥇 | 129 ns |
| mul | 14.6 ns | 8.9 ns 🥇 | 12.8 ns | 18.1 ns | 13.4 ns | 22.8 ns | 22.1 ns | 58 ns |
| div | 14.0 ns | 12.0 ns 🥇 | 19.4 ns | 33.9 ns | 27.6 ns | 171 ns | 40.0 ns | 287 ns |
| rem | 16.0 ns | 13.9 ns 🥇 | 24.3 ns | 36.9 ns | 15.3 ns | 63.0 ns | 17.1 ns | 171 ns |
| neg_add | 3.2 ns | 5.3 ns | 6.1 ns | 14.7 ns | 10.9 ns | 33.8 ns | 1.3 ns 🥇 | 140 ns |
| abs | 1.7 ns | 2.7 ns | 4.4 ns | 10.3 ns | 2.1 ns | 2.6 ns | 1.1 ns 🥇 | 31.9 ns |
Comparison, Parse, Rounding
| Operation | ancdec8 | ancdec32 | ancdec | ancdec128 | rust_decimal | fastnum_d256 | fixed_num | bigdecimal |
|---|---|---|---|---|---|---|---|---|
| cmp | 3.8 ns | 6.8 ns | 8.5 ns | 12.3 ns | 9.7 ns | 14.8 ns | 0.97 ns 🥇 | 8.6 ns |
| parse | 9.4 ns 🥇 | 14.3 ns | 14.2 ns | 19.7 ns | 12.8 ns | 21.6 ns | 407 ns | 202 ns |
| round | 17.3 ns | 12.6 ns 🥇 | 19.3 ns | 36.2 ns | 35.8 ns | 69.0 ns | 21.4 ns | 241 ns |
| floor | 14.4 ns | 7.3 ns 🥇 | 14.9 ns | 32.5 ns | 35.7 ns | 108.7 ns | 19.5 ns | 238 ns |
| ceil | 13.8 ns | 8.1 ns 🥇 | 15.7 ns | 31.1 ns | 75.3 ns | 111.8 ns | 18.5 ns | 210 ns |
| sqrt | 21.7 ns 🥇 | 79.0 ns | 98.0 ns | 402 ns | 761 ns | 131.7 ns | 126.9 ns | 3,600 ns |
Display, Chaining, Workflow
| Operation | ancdec8 | ancdec32 | ancdec | ancdec128 | rust_decimal | fastnum_d256 | fixed_num | bigdecimal |
|---|---|---|---|---|---|---|---|---|
| display | 99 ns | 93 ns 🥇 | 108 ns | 114.6 ns | 120 ns | 219 ns | 313 ns | 220 ns |
| chain_ops | 20.6 ns | 12.9 ns 🥇 | 18.4 ns | 41.7 ns | 29.1 ns | 50.8 ns | 21.8 ns | 175 ns |
| sum_10 | 36.5 ns | 19.3 ns | 28.3 ns | 50.8 ns | 118 ns | 166.6 ns | 2.8 ns 🥇 | 591 ns |
| workflow¹ | 30.0 ns | 21.1 ns 🥇 | 39.5 ns | 100.6 ns | 115.4 ns | 305.9 ns | 1,503 ns | 1,048 ns |
¹ workflow = parse → add → mul → div → round, all in one call.
High-Precision & Extreme
| Operation | ancdec128 | rust_decimal | fastnum_d256 | fixed_num | bigdecimal |
|---|---|---|---|---|---|
| mul (high-prec) | 17.9 ns 🥇 | 29.7 ns | 22.9 ns | 25.2 ns | 131 ns |
| div (high-prec) | 41.6 ns | 61.0 ns | 407 ns | 40.0 ns 🥇 | 10,800 ns |
| parse (high-prec) | 28.1 ns 🥇 | 30.1 ns | 41.9 ns | 381 ns | 230 ns |
| extreme large mul | 14.4 ns | 17.1 ns | 17.2 ns | 12.4 ns | 82.2 ns |
| extreme precision add | 13.0 ns | 10.8 ns | 11.4 ns | 0.99 ns 🥇 | 78.6 ns |
Accuracy (1÷3 and accumulated error)
| Operation | ancdec8 | ancdec32 | ancdec | ancdec128 | rust_decimal | fastnum_d256 | fixed_num | bigdecimal |
|---|---|---|---|---|---|---|---|---|
| 1÷3 (div) | 4.4 ns 🥇 | 5.6 ns | 10.5 ns | 18.8 ns | 22.8 ns | 277.5 ns | 18.8 ns | 8,391 ns |
| ⅓+⅓+⅓−1 | 1.1 ns 🥇 | 1.2 ns | 1.7 ns | 4.3 ns | 74.5 ns | 347.6 ns | 509.3 ns | 8,647 ns |
bigdecimal is slowest on recurring decimals (8.4 µs) because it recomputes arbitrary-precision 1÷3 at runtime on each call. ancdec types produce a truncated result at their fixed precision limit (e.g., AncDec8: 0.33; AncDec128: 38 decimal places). fastnum (D256) is the slowest fixed-precision type at 277 ns for division.
Win Count (fastest across all 24 benchmark groups)
| Library | Wins |
|---|---|
| ancdec32 | 10 — mul, div, rem, round, floor, ceil, display, chain_ops, workflow, extreme_large |
| fixed_num | 8 — add, sub, neg_add, abs, cmp, div_hi_prec, sum_10, extreme_precision |
| ancdec8 | 4 — parse, sqrt, accuracy_recurring, accuracy_accumulated |
| ancdec128 | 2 — parse_hi, mul_hi_prec |
| rust_decimal | 0 |
| fastnum_d256 | 0 |
| bigdecimal | 0 |
| ancdec (u64) | 0 |
fixed_num (Dec19x19) excels at simple scalar ops because it uses a compile-time-fixed scale with no alignment overhead. ancdec32 dominates general-purpose use because its 32-bit fields fit in a single cache line with fast scalar arithmetic. ancdec8 wins on throughput tasks (sqrt, parse) due to minimal data size.
Analysis
vs rust_decimal: ancdec32 beats it across the board — mul 1.5×, div 2.3×, floor 4.9×, ceil 9.3×, workflow 5.5×. No category where rust_decimal leads ancdec32.
vs fastnum (D256): ancdec32 is 14× faster on div (12 ns vs 171 ns). On recurring decimals (1÷3), ancdec8 is 63× faster (4.4 ns vs 277 ns). fastnum trades general speed for extreme precision width.
vs bigdecimal: ancdec wins everywhere by wide margins — workflow is 50× faster (21 ns vs 1048 ns), 1÷3 is 1900× faster (4.4 ns vs 8391 ns). bigdecimal's heap allocation cost is unavoidable.
vs fixed_num: the only library that beats individual ancdec types in isolated scalar ops (add, sub, cmp). However, fixed_num's parse is catastrophically slow (407 ns vs ancdec8's 9.4 ns — 43× slower), which causes it to collapse in any real-world workflow:
| ancdec32 | fixed_num | |
|---|---|---|
| parse | 14 ns | 407 ns |
| display | 93 ns | 313 ns |
| workflow (parse→add→mul→div→round) | 21 ns | 1,503 ns — 71× slower |
fixed_num is only faster when values are pre-constructed in memory and you are doing nothing but arithmetic on them in a tight loop. In any workload that includes parsing input or formatting output, ancdec32 wins decisively.
Overall: ancdec32 is the best general-purpose decimal type across this benchmark set, winning 10 of 24 groups and dominating real-world workflow scenarios. All ancdec types combined win 16 of 24 groups. The only library that takes categories from ancdec is fixed_num, and only in workloads that never touch strings.
Running the Benchmarks Yourself
All benchmark code is in benches/ancdec_bench.rs — 24 groups, 8 libraries side by side. Clone the repo and run:
To run a specific group:
The benchmark compares ancdec against rust_decimal, fastnum (D256), fixed-num (Dec19x19), and bigdecimal on identical inputs. Results are written to target/criterion/ as HTML reports.
Benchmarked on Intel Core i7-10750H @ 2.60GHz, Rust 1.87.0 nightly, release mode
Performance Architecture
AncDec Multiplication Fast Path
AncDec combines int and frac into a single u128 value (int × 10^scale + frac) before multiplication. When both combined values exceed u64 range, this requires u256 wide arithmetic (mul_wide). To avoid this overhead for common cases:
combined = int × 10^scale + frac
if combined ≤ u64::MAX for both operands:
→ cast to u64, multiply natively (u64 × u64 → u128) ~7 ns
else:
→ mul_wide (u128 × u128 → u256) + div_wide ~21 ns
Why this is safe: The fast path guard a ≤ u64::MAX && b ≤ u64::MAX guarantees the product fits in u128, because (2⁶⁴ - 1)² = 2¹²⁸ - 2⁶⁵ + 1 < 2¹²⁸ - 1 = u128::MAX. The operands are explicitly cast down to u64 before multiplication to make the intent unambiguous: (a as u64 as u128) * (b as u64 as u128). When the combined value exceeds u64 range (e.g., int=10^18, scale=19 → combined ≈ 10^37), the condition fails and execution falls through to mul_wide which handles the full u128×u128→u256 range safely.
AncDec128 Tiered Fast Paths
AncDec128 automatically selects the fastest arithmetic path based on operand size:
Multiplication:
| Tier | Condition | Method | Cost |
|---|---|---|---|
| 1 | Both fit in u64 combined | Native u64 x u64 | ~15 ns |
| 2 | Parts fit in u64, scale <= 19 | 4x partial product | ~25 ns |
| 3 | Both fit in u128 combined | mul_wide + divmod_u256 |
~45 ns |
| 4 | Everything else | Full u256 x u256 -> u512 | ~80 ns |
Division:
| Tier | Condition | Method | Cost |
|---|---|---|---|
| 1 | Both fit in u64 combined | Algebraic decomposition | ~25 ns |
| 2 | Both fit in u128 combined | mul_wide + divmod_u256 |
~40 ns |
| 3 | Everything else | Full u512 / u256 | ~80 ns |
Performance Cliffs
Performance drops when operands exceed a tier's threshold:
| Trigger | Effect | Typical cause |
|---|---|---|
scale > 19 |
Skips u64 and partial product tiers | div() produces scale=38 |
int > u64::MAX |
Skips u64 and partial product tiers | Large aggregated values |
int * 10^scale + frac > u128::MAX |
Falls to u256 slow path | High-scale large values |
Common pattern: a.div(&b).mul(&c) -- division produces scale=38, forcing subsequent multiplication into the u128 or u256 path. This is inherent to the split int/frac representation, not a bug.
Precision Limits
| AncDec8 (u8) | AncDec32 (u32) | AncDec (u64) | AncDec128 (u128) | |
|---|---|---|---|---|
| Integer part | 2 digits | 9 digits | 19 digits | 38 digits |
| Fractional part | 2 digits | 9 digits | 19 digits | 38 digits |
| Total precision | 4 digits | 18 digits | 38 digits | 76 digits |
| sqrt() precision | 1 digit | 8 digits | 18 digits | 37 digits |
| Scale range | 0-2 | 0-9 | 0-19 | 0-38 |
| Struct size | 4 bytes | 12 bytes | 24 bytes | 40 bytes |
Fractional digits beyond the limit are truncated during parsing. Integer parts saturate at MAX.
Complete API Reference
Methods (all 4 types)
| Category | Methods |
|---|---|
| Construction | parse(T), new(int, frac, scale, neg) (8/32/128), direct fields (AncDec) |
| Accessors | int(), frac(), scale(), is_neg() (8/32/128) |
| Arithmetic | add, sub, mul, div, rem, checked_add, checked_sub, checked_mul |
| Math | sqrt(), pow(i32), abs(), signum() |
| Query | is_zero(), is_positive(), is_negative() |
| Range | min(), max(), clamp() |
| Rounding | round(places, mode), floor(), ceil(), trunc(), fract() |
| Conversion | to_f64(), to_i64(), to_i128() |
Operator Traits (all 4 types)
| Trait | Operators | Variants |
|---|---|---|
Add, Sub, Mul, Div, Rem |
+, -, *, /, % |
value, &ref, cross-type, primitives |
AddAssign, SubAssign, MulAssign, DivAssign, RemAssign |
+=, -=, *=, /=, %= |
|
Neg |
-a |
value, &ref |
Conversion Traits
| Trait | AncDec8 | AncDec32 | AncDec | AncDec128 |
|---|---|---|---|---|
From<i8>, From<u8> |
Yes | Yes | Yes | Yes |
From<i16>, From<u16> |
— | Yes | Yes | Yes |
From<i32>, From<u32> |
— | Yes | Yes | Yes |
From<i64>, From<u64> |
— | — | Yes | Yes |
From<i128>, From<u128> |
— | — | Yes | Yes |
From<isize>, From<usize> |
— | — | Yes | Yes |
TryFrom<f32>, TryFrom<f64> |
Yes | Yes | Yes | Yes |
TryFrom<&str>, FromStr |
Yes | Yes | Yes | Yes |
Widening From (lossless, cfg-gated)
AncDec8 → AncDec32 → AncDec → AncDec128
| From | To AncDec32 | To AncDec | To AncDec128 |
|---|---|---|---|
| AncDec8 | Yes | Yes | Yes |
| AncDec32 | — | Yes | Yes |
| AncDec | — | — | Yes |
Primitive Arithmetic
| Type | Supported primitives for +, -, *, / (both directions) |
|---|---|
| AncDec8 | i8, u8 |
| AncDec32 | i8, i16, i32, u8, u16, u32 |
| AncDec | i8-i128, isize, u8-u128, usize (12 types) |
| AncDec128 | i8-i128, isize, u8-u128, usize (12 types) |
Cross-Type Arithmetic (cfg-gated)
All 5 operators (+, -, *, /, %) in both directions. Output = larger type.
| Pair | Output | Feature gate |
|---|---|---|
| AncDec8 ↔ AncDec32 | AncDec32 | dec8 + dec32 |
| AncDec8 ↔ AncDec | AncDec | dec8 + dec64 |
| AncDec8 ↔ AncDec128 | AncDec128 | dec8 + dec128 |
| AncDec32 ↔ AncDec | AncDec | dec32 + dec64 |
| AncDec32 ↔ AncDec128 | AncDec128 | dec32 + dec128 |
| AncDec ↔ AncDec128 | AncDec128 | dec64 + dec128 |
Other Traits (all 4 types)
| Trait | Notes |
|---|---|
PartialEq, Eq |
0 == -0, trailing zeros normalized |
PartialOrd, Ord |
Total ordering |
Hash |
Normalized (trailing zeros, 0 == -0), usable in HashMap/HashSet |
Clone, Copy, Debug |
Derived |
Default |
Returns ZERO |
Display |
Precision support: format!("{:.2}", a) |
Sum, Product |
Iterator support (owned + reference) |
Serialize, Deserialize |
String-based, with serde feature |
Constants
ZERO ZERO ZERO ZERO
ONE ONE ONE ONE
TWO TWO TWO TWO
TEN TEN TEN TEN
MAX MAX MAX MAX
Features
| Feature | Dependencies | Description |
|---|---|---|
| (default) | None | All 4 types, only uses core |
dec8 |
— | AncDec8 only |
dec32 |
— | AncDec32 only |
dec64 |
— | AncDec only |
dec128 |
— | AncDec128 only |
serde |
serde |
Serialization for all enabled types |
sqlx |
sqlx, std |
PostgreSQL NUMERIC (AncDec only) |
Serde
All types serialize as decimal strings:
// JSON: {"sensor": "1.23", "price": "123.456", "total_value": "12345678901234567890.123456"}
SQLx (AncDec only)
PostgreSQL NUMERIC binary wire protocol for AncDec only. Implements Type<Postgres>, Encode<Postgres>, Decode<Postgres>.
let price: AncDec = "123.456".parse?;
query
.bind
.execute
.await?;
let row: AncDec = query_scalar
.fetch_one
.await?;
Safety Design
Public API - Always Safe
All public APIs return Result for fallible operations. Integer conversions via From are infallible. checked_add, checked_sub, checked_mul return Option<Self> for overflow-safe arithmetic.
Invariant Enforcement
AncDec8, AncDec32, and AncDec128 enforce frac < 10^scale through:
pub(crate)fields -- external code must usenew()orparse()debug_assert!innew()-- catches violations in debug builds at zero release cost- All arithmetic preserves the invariant -- internal construction is trusted
Division by Zero
Division by zero panics (consistent with Rust's integer division). Use is_zero() to check before division.
Comparison with Alternatives
| Feature | AncDec8 | AncDec32 | AncDec | AncDec128 | rust_decimal | f64 |
|---|---|---|---|---|---|---|
| Integer precision | 2 digits | 9 digits | 19 digits | 38 digits | 28 shared | ~15 shared |
| Fractional precision | 2 digits | 9 digits | 19 digits | 38 digits | 28 shared | ~15 shared |
| Exact decimal | Yes | Yes | Yes | Yes | Yes | No |
| no_std | Yes | Yes | Yes | Yes | Feature flag | Yes |
| Zero dependencies | Yes | Yes | Yes | Yes | No | Yes |
| FFI-friendly | Yes | Yes | Yes | Yes | No | Yes |
| Struct size | 4 bytes | 12 bytes | 24 bytes | 40 bytes | 16 bytes | 8 bytes |
Changelog
v0.3.5
- Updated crate description and README subtitle to lead with the core value: fraction precision never shrinks as integers grow.
v0.3.4
- Updated crate description and README subtitle to accurately reflect 4-type family.
v0.3.3
Bug Fixes:
- Fixed
to_i64()panic in debug mode when value equalsi64::MIN(AncDec,AncDec128): unary negation ofi64::MINoverflows; replaced withwrapping_neg(). - Fixed
to_i128()panic in debug mode when value equalsi128::MIN(AncDec128): same root cause, same fix. - Fixed
Negoperator producing negative zero (neg=trueon a zero value) for all 4 types:-AncDec::ZEROnow always returns a value withneg=false. - Fixed
is_neg()returningtruefor negative zero onAncDec8,AncDec32,AncDec128: method now correctly returnsfalsefor any zero value.
Tests:
- Added regression tests:
test_to_i64_min,test_to_i64_min_128,test_to_i128_min,test_neg_zero,test_neg_zero_128.
v0.3.2
- Updated crate description.
v0.3.1
- Minor maintenance release.
v0.3.0
New Types:
AncDec8(u8): 4-byte decimal for embedded/IoT (2+2 digit precision)AncDec32(u32): 12-byte decimal for general purpose (9+9 digit precision)
New Features:
- Feature flags (
dec8,dec32,dec64,dec128) for selective compilation - Cross-type arithmetic:
AncDec8 + AncDec32 → AncDec32(automatic widening) - Widening conversions via
From:AncDec8 → AncDec32 → AncDec → AncDec128 - Serde support for all 4 types
sqrt()for AncDec (18-digit fractional precision via Newton-Raphson on u256)sqrt()for AncDec128 (37-digit fractional precision via Newton-Raphson on u512)checked_add,checked_sub,checked_mulfor all 4 types (returnsOption)
Breaking Changes:
- Default features changed from none to
["dec8", "dec32", "dec64", "dec128"]- Existing code compiles unchanged (all types enabled by default)
default-features = falsenow requires explicit feature selection
AncDec128fields changed frompubtopub(crate)- Use
AncDec128::new(int, frac, scale, neg)for construction - Use
.int(),.frac(),.scale(),.is_neg()for field access - Enforces
frac < 10^scaleinvariant viadebug_assert!
- Use
Bug Fixes:
- Fixed
mul_wideoverflow in debug mode (hl + lh→wrapping_add)
Performance:
- AncDec mul: u64 fast path bypasses
mul_widewhen both operands fit in u64 (-65%) - AncDec128 mul: partial product fast path for u64-sized operands (-45% for high precision)
- AncDec128 mul: u64 ultra-fast path for small operands (-10%)
- AncDec128 div: algebraic decomposition for u64 operands (-33%)
- AncDec128 div: u128 fast path avoiding full u256 arithmetic (-20%)
- AncDec128 sub: branchless
borrow * limitpattern (-37%) - AncDec128 add: branchless
overflow * limitpattern (-17%) - AncDec128 parse: two-stage u64/u128 accumulator with stage 2 gating (-18%)
v0.2.0
- Added serde serialization/deserialization support
- Added SQLx PostgreSQL support
- Fixed mul/div overflow with u256 wide arithmetic
v0.1.0
- Initial release with AncDec (u64-based decimal)
License
MIT License
Contributing
Contributions welcome! Please ensure:
- All tests pass (
cargo test) - Individual type tests pass (
cargo test --no-default-features --features dec8) - Serde tests pass (
cargo test --features serde) - Benchmarks don't regress (
cargo bench) - Code follows existing style
AI Usage
The core AncDec (u64) type — including its architecture, algorithms, and all implementation details — was designed and written entirely by the author from scratch without AI assistance.
AI tools (Claude) were used to accelerate the development of derivative types (AncDec8, AncDec32, AncDec128) whose implementations follow the same patterns as the original AncDec with adjusted integer sizes and scale ranges. The author reviewed and verified all AI-generated code.