Expand description
A Fixed-point decimal implementation written in Rust suitable for a wide range of financial calculations that require significant integral and fractional digits to follow decimal arithmetic rounding.
The binary representation consists of a 64 bit integer number, multiplied by power of 10 - default 10000 for ‘Amount64’ type.
Such implementation results in highly efficient addition and subtraction which are implemented as native operations on i64. These operations also don’t result in any rounding errors.
Multiplication and division are also relatively efficient and implemented via operations on i128 type internally. On some platforms (like x86-64) where 64 x 64 bit multiplication with 128 bit result and division of 128 bit integer by 64 bit are implemented as native instructions, performance penalty compared to regular i64 division and multiplication is negligible.
While 4 fractional decimal digits handles most of cases for accounting and tax computations, in some cases like exchange rates higher precision is desirable. To address this, a sibling type ‘Rate’ is introduced with 8 fractional digits.
§Wider backings: 128-bit and 256-bit
When the ~±922 trillion range of Amount64 is not enough, the same decimal
semantics are available on wider backing integers:
Amount128/Rate128(Decimal128): backed byi128, range about ±1.7 * 10^34 at 4 digits.Amount256/Rate256(Decimal256): backed by a 256-bit integer (I256), range about ±5.8 * 10^72 at 4 digits.
Addition and subtraction remain native integer operations (one or two
add/adc instructions). Multiplication builds the double-width intermediate
from 64-bit limbs and re-scales it by the compile-time constant 10^DIGITS
using Möller–Granlund reciprocal multiplication with a compile-time
reciprocal — no division instructions and no compiler builtins on that
path, on any architecture (scripts/check_asm.sh verifies the generated
code). Division by another decimal (a runtime divisor) uses a
128-bit-by-64-bit division primitive — the native div instruction on
x86_64 with the asm feature, or a portable long division on 32-bit
half-digits — and Knuth’s algorithm D over the significant limbs for
divisors wider than one limb.
Key operations (checked_mul, mul_rounded, round_to, trunc, powi,
from_str_rounded, from_str_const, …) are const fn, so rates, fees
and whole derived constants can be evaluated at compile time:
use fin_decimal::{Amount64, Rounding};
const PRICE: Amount64 = Amount64::from_str_const("19.99");
const RATE: Amount64 = Amount64::from_str_const("0.0825");
const TAX: Amount64 = PRICE.mul_rounded(RATE, Rounding::HalfEven);The * and / operators of every backing panic on overflow regardless of
build profile (they share one arithmetic core); use
checked_mul/checked_div to get None instead.
§Usage
The stable version of rust requires you to create a Decimal number using one of its convenience methods.
use fin_decimal::Amount64;
use core::str::FromStr;
// Using an integer number.
let from_int = Amount64::from(3); // 3.0000
// Using a floating point number.
let from_f64 = Amount64::from(2.02f64); // 2.0200
// From a string representation
let from_string = Amount64::from_str("2.02").unwrap(); // 2.0200
// Using the `Into` trait
let my_int : Amount64 = 3i32.into();Structs§
- Decimal
- Amount64 type implements decimal fixed-point arithmetic for financial computations. It is implemented to be as efficient as possible with most common add/sub operations to be native binary add/sub. Actual decimal processing is needed for multiplication and division where rounding should follow specific rules. Number of decimal points is chosen to be 4 - this seems to be enough for most use cases except for exchange rates where sometimes up to 8 decimal digits is required
- Decimal128
- Decimal fixed-point number backed by a 128-bit signed integer, with
DIGITSfractional decimal digits (DIGITS <= 19). - Decimal256
- Decimal fixed-point number backed by a 256-bit signed integer, with
DIGITSfractional decimal digits (DIGITS <= 19). - I256
- A 256-bit signed integer in two’s complement form, used as the backing
storage of
Decimal256. Only the operations the decimal type needs are provided; it is not a general-purpose big integer.
Enums§
- Amount
Error Kind - Enum to store the various types of errors that can cause parsing an integer to fail.
- Amount
Sign - Defines how to display the sign of the parsed number.
- Rounding
Roundingrepresents the different strategies that can be used.
Functions§
- parse_
decimal_ i64 - Converts a string in base 10 to a fixed-point scaled value. Can be used with non-default scale to handle higher precision exchange rates and other scenarios with longer fractional parts.
- parse_
decimal_ i64_ rounded - Converts a string in base 10 to a fixed-point scaled value, rounding any
fractional digits beyond
scalewith the givenRoundingmode. - str_i64
- Converts an i64 to a fixed-point string representation, optionally padded.
Type Aliases§
- Amount64
- Amount64 is a 64-bit integer based decimal type with 4 decimal digits precision.
- Amount128
- Amount128 is a 128-bit integer based decimal type with 4 decimal digits precision.
- Amount256
- Amount256 is a 256-bit integer based decimal type with 4 decimal digits precision.
- Rate64
- Rate64 is a 64-bit integer based decimal type with 8 decimal digits precision.
- Rate128
- Rate128 is a 128-bit integer based decimal type with 8 decimal digits precision.
- Rate256
- Rate256 is a 256-bit integer based decimal type with 8 decimal digits precision.