Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Compared with Others

Each crate below is described from its own source and documentation, at the version the benchmarks build, so every claim can be checked there. Each speed is from the run Performance cites, where every crate is measured in one binary on one machine, on values every crate holds, and every result is checked against an exact reference.

Speed

Nanoseconds per operation on the unpredictable values. ddust rounds half to even here, and each other crate in its own mode; Performance gives ddust in every crate’s mode beside it:

CrateSumRounded productRounded quotientTo centsReadWriteThe mix
ddust D64<8>0.763.975.401.309.759.841.73
fixdec 0.1.0 D640.734.49, inexact8.123.8223.540.43.21, inexact
nexus-decimal 1.2.2 <i64, 8>0.735.578.184.3219.223.33.38
primitive_fixed_point_decimal 1.5.0 <i64, 8>0.733.6810.93.2120.533.43.60
fin_decimal 0.4.0 Decimal<8>0.759.6311.44.9920.7, inexact26.64.85
fastnum 0.7.5 D6416.633.886.218.324.280.031.1
rust_decimal 1.43.09.7930.711421.819.167.035.1
decimal-rs 0.2.07.6140.91996.5743.644.051.1
bigdecimal 0.4.1138.01244,41675.591.1111931
fixnum 0.9.5 <i64, U8>0.7513.314.329.428.5
CrateSumRounded productRounded quotientTo centsReadWriteThe mix
ddust D128<18>1.068.1215.84.1510.016.45.68
primitive_fixed_point_decimal 1.5.0 <i128, 18>1.057.3029.98.3939.677.68.40
nexus-decimal 1.2.2 <i128, 18>1.058.6322.214.720.849.38.52
fin_decimal 0.4.0 Decimal128<18>1.0512.927.48.8935.2, inexact46.29.77
rust_decimal 1.43.010.657.898.843.730.091.742.2
decimal-rs 0.2.07.6552.51989.5862.148.453.3
fastnum 0.7.5 D12818.244.720624.031.910360.0
bigdecimal 0.4.1147.817110,12790.51171392,074
fixnum 0.9.5 <i128, U18>1.0317.434.765.943.0

The fixed-point crates add and compare as their integers do, within a few percent of each other. ddust’s checked sum takes two instructions more than the integer’s, and its exact product one more, where its repr(C) layout, which keeps a decimal its integer’s bytes, has Rust build the Option in memory: the narrow sum is 0.76 ns against nexus-decimal’s 0.73, and the exact product 0.89 against primitive_fixed_point_decimal’s 0.87. Past them, ddust is the fastest at every operation, in the same rounding mode, but three rows of the Performance tables:

  • a narrow rescale to cents on predictable values, where fixdec takes 1.19 ns and ddust 1.30;
  • a chain of narrow rounded products on predictable values, where fin_decimal takes 11.7 ns a link and ddust 12.4;
  • a chain of wide rounded products on unpredictable values, where LLVM inlines primitive_fixed_point_decimal’s smaller code into the chain and calls ddust’s: 14.1 ns a link against 15.7.

The decimals that carry a scale in each value take ten to a hundred times as long, and bigdecimal, which allocates, longer still.

rust_decimal

rust_decimal 1.43.0’s Decimal is a 96-bit magnitude, a sign and a scale from 0 to 28, in 16 bytes: each value carries its own scale, and an operation chooses its result’s.

rust_decimal 1.43.0ddust
a scale per value, 0 to 28; + lines two upa scale in the type, 0 to 38, or carried at run time and lined up
28 or 29 significant digits, in 16 bytesthe integer’s: 18 in 8 bytes, 38 in 16, down to 2 in 1
* exact while the product fits, then rounded half to even* exact, its scale the sum of the factors’, or an overflow
/ rounded half to even at the last digit that fits/ truncated at the dividend’s scale, or rounded by a mode when asked
seven rounding strategies, taken by round_dp_with_strategy and round_sf_with_strategynine modes, taken by every operation that can lose a digit
operators that panic past the range in every build; checked_* and saturating_add, _sub and _muloperators that panic or wrap as the integer’s; all four families
FromStr rounds a digit past 28 decimals or 96 bits; from_str_exact refuses itevery reader exact or refused; from_ascii_round rounds by a mode
Display keeps the trailing zeros a value carriesDisplay writes the shortest exact decimal; {:#} every decimal
from_f64 from the double’s shortest digits; to_f64 not correctly roundedfrom_f64 from the double’s exact value, by a mode; to_f64 correctly rounded
no_std with allocno_std with no allocator
stable Rust, from 1.67.1nightly Rust

rust_decimal has what ddust has not: square roots, exponentials, logarithms, powers and trigonometry, under its maths feature; rounding to significant figures; reading text in any radix; and integrations with diesel, postgres, tokio-postgres, borsh, ndarray and rocket. Take it on stable Rust, where values of many scales meet and each result should keep as many digits as fit, or where those integrations or functions are needed.

fastnum

fastnum 0.7.5 is decimal floating point, after IEEE 754 and the General Decimal Arithmetic specification: a coefficient of 64 to 1,024 bits and an exponent, with NaN, the infinities, signed zero and signals. A value carries a context, which holds its rounding mode, one of seven. A coefficient that fills rounds away its last digits and signals that it did; an exponent past the range gives an infinity, and a trapped signal panics only with debug_assertions on. It has square roots, exponentials, logarithms, powers and trigonometry, and integrations with diesel, sqlx and tokio-postgres; it needs alloc, and builds on stable Rust from 1.94. Take it for IEEE decimal semantics, for precision past 38 digits, or for those functions.

bigdecimal

bigdecimal 0.4.11 is arbitrary precision: a heap-allocated integer of any length and a 64-bit scale, so no value overflows. A quotient is rounded to 100 significant digits unless a build-time setting says otherwise, and a Context sets the precision of the functions that take one: square and cube roots, exponentials and powers. Take it where a value can outgrow 38 digits and allocation is acceptable.

The Fixed-Point Crates

These crates are decimals of a fixed scale, as ddust’s static ones are, each in one of Rust’s integers:

CrateScaleRounding* and /An operator past the rangeText past the scaleno_std
ddusta const generic, or at run timenine modes, as types or a valueexact; truncatedas the integer’srefusedyes
primitive_fixed_point_decimal 1.5.0a const generic, or passed with each callfive modes, by an argumentrounded half away from zero+ and - as the integer’s; * and / panicrefusedyes
nexus-decimal 1.2.2a const genericfour, each a methodtruncatedpanics in every buildrefusedyes
fixnum 0.9.5a type parameterthree modes, by an argumentno operators: methods with a modeno operators: its methods return an errorrounded; from_str_exact refusesyes
fin_decimal 0.4.0a const genericfive modes, by an argumentrounded by its HalfUp+ and - as the integer’s, but on 256 bits; * and / panicrounded by its HalfUpyes
const-decimal 0.4.0a const generictruncation alonetruncatedpanics in every buildrefusedno

Each builds on stable Rust, and has what ddust has not: primitive_fixed_point_decimal, negative scales and a scale kept outside the value; nexus-decimal, helpers for ticks, basis points and spreads; fixnum, a macro for typed wrappers’ arithmetic, and parity-scale-codec, rkyv and schemars; fin_decimal, split_evenly, a correctly rounded square root and 256-bit values; const-decimal, borsh. Take one of them on stable Rust, choosing by the columns above.

decimal-rs 0.2.0, also measured, is a 38-digit decimal with a floating exponent, in 20 bytes, that rounds half up wherever it rounds and needs std.

fixed

fixed 1.31.0 is binary fixed point: a number of fraction bits, not of decimals, so it holds no decimal fraction exactly, as its own docs say. Its * and / overflow by debug_assertions rather than overflow-checks, and it has the integer’s families and more. Take it for signal processing and embedded arithmetic, where a binary fraction is the point; the benchmarks time its multiply beside the decimals’ for contrast.

fixdec

fixdec 0.1.0 is measured with its results marked inexact. Its D64 multiply divides by through an approximate reciprocal, which is wrong once the product of the steps reaches : 1,000 × 1,000 is 1,000,000.0000049. Parsing an integer part too large for its word wraps silently, and its D96 writes and divides some large values wrongly.

What ddust Gives Up

  • Stable Rust. ddust needs nightly, for const traits.
  • A scale each value carries and each operation chooses, as rust_decimal’s and the decimal floats’ do. ddust’s run-time scale lines up exactly but never grows a quotient’s decimals.
  • Arbitrary precision, and decimal floating point: 38 digits is its limit.
  • NaN, the infinities and negative zero.
  • Square roots, exponentials, logarithms, powers and trigonometry.
  • The integrations a crate gathers over years of use: diesel, sqlx, postgres, borsh, rkyv and the rest.