decimal-scaled 0.4.4

Const-generic base-10 fixed-point decimals (D9/D18/D38/D76/D153/D307) with integer-only transcendentals correctly rounded to within 0.5 ULP — exact at the type's last representable place. Deterministic across every platform; no_std-friendly.
Documentation

decimal-scaled

crates.io docs.rs MSRV License site CodSpeed OpenSSF Best Practices OpenSSF Scorecard cargo-audit

DocsBenchmarksAlgorithmsRoadmapAPI reference

Const-generic base-10 fixed-point decimals for Rust — bit-exact, ≤ 0.5 ULP correctly-rounded integer-only transcendentals, deterministic on every platform, no_std-friendly.


Install

[dependencies]

decimal-scaled = { version = "0.4", features = ["macros"] }

First use

use decimal_scaled::{d38, D38s12};

// The most common way to make a value: a literal, scale inferred from
// its digits.
let x = d38!(1.564232);                        // D38<6>
assert_eq!(x.to_string(), "1.564232");

let price: D38s12 = "19.99".parse().unwrap();
let qty   = D38s12::from_int(3);
let total = price * qty;                       // 59.97 exactly

assert_eq!(total.to_string(), "59.97");
assert_eq!(total, d38!(59.97, scale 12));

// Transcendentals are correctly rounded to ≤ 0.5 ULP, integer-only,
// and bit-identical across platforms.
let sqrt2 = d38!(2, scale 12).sqrt_strict();

What it does

Every value is raw × 10^(-SCALE) for a compile-time SCALE. There is exactly one bit pattern per logical value — no normalisation, no per-value scale byte, no heap allocation. 0.1 + 0.2 == 0.3 is true, and so is hash(1.10) == hash(1.1) at the same scale.

Thirteen storage widths from D9 (32-bit, ~9 decimal digits) to D1232 (4096-bit, ~1232 decimal digits) share an identical API. Pick the narrowest width that covers your range.

The two guarantees nothing else on crates.io currently combines:

  1. ≤ 0.5 ULP correctness on every transcendentalln / exp / sin / cos / tan / sqrt / cbrt / powf / asin / acos / atan / atan2 / sinh / cosh / tanh / asinh / acosh / atanh / to_degrees / to_radians lands within half an ULP of the mathematically exact result, and the bit pattern is identical on every machine. The default strict path is integer-only and no_std-compatible.
  2. Caller-chosen rounding mode at every lossy operation. The default is HalfToEven (IEEE 754 default). Every lossy entry point (* / / / %, the rescale family, every strict transcendental) ships a *_with(mode) sibling that takes a RoundingMode: HalfToEven · HalfAwayFromZero · HalfTowardZero · Ceiling · Floor · Trunc. The crate-wide default is also selectable at compile time via the rounding-* Cargo features.

Correctly rounded — and the only crate that is

Worst-case error of each transcendental, measured against a high-precision oracle (worst result across every tested input). Each cell shows the LSBεleast significant bits in error, the count of low-order bits of the stored value that are wrong — with the worst ULP distance from the true value in parentheses:

Function decimal-scaled g_math fastnum rust_decimal dashu-float decimal-rs
exp ✓ 0 (0.50) ✗ 65 (2.3e19) ✓ 0 (1e-16) ✓ 0 (2.7e-6) ✓ 0 (1e-16) ✓ 0 (1.3e-15)
ln ✓ 0 (0.50) ✗ 6 (49.5) ✓ 0 (1e-16) ✗ 31 (1.1e9) ✗ 1 (1.00) ✓ 0 (1e-16)
sin ✓ 0 (0.50) ✗ 64 (1.7e19) ✓ 0 (1.7e-12) ✓ 0 (2.1e-9)
cos ✓ 0 (0.50) ✗ 6 (50.0) ✓ 0 (5.2e-12) ✓ 0 (2.4e-9)
tan ✓ 0 (0.50) ✗ 65 (2.1e19) ✓ 0 (1.4e-12) ✗ 36 (4.3e10)
atan ✓ 0 (0.50) ✗ 64 (1.6e19) ✓ 0 (1e-16)
sqrt ✓ 0 (0.50) ✗ 6 (49.6) ✓ 0 (1e-16) ✓ 0 (7.2e-7) ✓ 0 (1e-16) ✓ 0 (3e-16)
cbrt ✓ 0 (0.50) ✓ 0 (1e-16)
rounding all 6, caller-chosen nearest HalfUp HalfEven HalfAway unspec.

= 0 LSBε (correctly rounded — the stored value is exactly right, ≤ 0.5 ULP from true) on every tested input. = at least one input with ≥ 1 LSBε. = not implemented by that crate. First number = worst-case LSBε (least significant bits in error); parenthesised = worst ULP distance from the true value.

decimal-scaled is the only crate ✓ on every function — and its ✓ holds for all six rounding modes and all thirteen widths (D9 … D1232).

At deep precision the field collapses. Repeat the test at a 150-digit scale and only decimal-scaled computes all eight functions correctly; the fixed-precision crates (g_math ≈ 19, rust_decimal ≈ 28, fastnum ≈ 34, decimal-rs ≈ 38 digits) can no longer represent the value at all, and arbitrary-precision dashu-float — the lone peer that reaches it — still misses ln.

Scope of this table: measured at a 19-digit scale (D38<19>) and a 150-digit scale (D307<150>, with D616<308> also verified), under HalfToEven; each cell is the worst case across the scales a library supports. It samples a slice of the full matrix — decimal-scaled's guarantee holds across all six rounding modes and all thirteen widths (D9D1232). For the complete per-scale, per-width, per-mode tables and the methodology, see the benchmarks.

Speed across the width range

Per-operation throughput against the wider numeric ecosystem (bnum, ruint, rust_decimal, fixed), at two representative widths:

i128 / D38 — 128-bit, scale 19:

operations at 128-bit vs the ecosystem

i1024 / D307 — 1024-bit, scale 150:

operations at 1024-bit vs the ecosystem

Full per-width charts (32-bit … 4096-bit) and the methodology are in the benchmarks.


Documentation

In-depth guides live under docs/:

  • Getting started — constructing values, arithmetic, formatting, parsing.
  • The width familyD9 through D1232, scale aliases, the Decimal trait, picking a tier.
  • Conversions — integers, floats, cross-width widening / narrowing, the float bridge.
  • Cross-scale operationsmul_of / add_of / cmp_of / clamp_of / etc. on every width for mixed-width mixed-SCALE expressions, plus the nightly-gated cross::mul(a, b) auto-inferred form.
  • RoundingRoundingMode, the _with pairs, rescale, the compile-time rounding-* features.
  • Strict mode — integer-only *_strict transcendentals, the ≤ 0.5 ULP guarantee.
  • The d*! macros — compile-time decimal literals, scale inference, scientific / radix notation.
  • Cargo features — every feature flag with what it enables and the common configurations.
  • Benchmarks — head-to-head against bnum, ruint, rust_decimal, fixed, fastnum, dashu-float, bigdecimal, and the fast-vs-strict trade.
  • Algorithms — every kernel with its citation (Möller–Granlund, Brent, Knuth, Karatsuba, Burnikel–Ziegler, Mercator / Cody–Waite, …).
  • Roadmap — what's queued (signed SCALE, RNG surface, wide-tier perf catch-up, downstream adapter / ecosystem crates).
  • Changelog — release-by-release notes.
  • Contributing — algorithm-library tour, adding a per-(width, scale) override, performance gates, license-compatibility rules.

API reference: https://docs.rs/decimal-scaled/.


License

Licensed under either of:

at your option.

Copyright 2026 John Moxley. Third-party code attributions are listed in LICENSE-THIRD-PARTY.