fastnum

repository·master·Indexed 42 days ago

https://github.com/neogenie/fastnum

A high-performance Rust library providing fixed-size, exact-precision decimal and integer types for financial and cryptographic applications. Version 0.7.5 features no dynamic allocation, compile-time evaluation via macros, and support for IEEE 754 semantics including NaN and Infinity. It offers various bit sizes (64, 128, 256, 512) and integrates with the Rust ecosystem through optional features for serde, diesel, sqlx, and tokio-postgres.

Tokens
17.6K
Snippets
53
Records
73
Agent score
53%

What's inside fastnum

  1. Understand the fastnum memory layout

    master

    Any N-bit decimal type in fastnum consists of an N-bit big unsigned integer (coefficient) and a 64-bit control block.

    The control block contains:

    • A signed 16-bit integer scaling factor (the negotiated exponent).
    • Decimal number flags (including sign and special value flags).
    • Signaling flags for exceptional conditions.
    • Decimal Context (includes rounding mode and signal traps).
    • Extra precision digits.
  2. How fastnum decimal types work

    master

    A fastnum decimal type is composed of two parts:

    1. An N-bit big unsigned integer (the significand).
    2. A 64-bit control block containing:
      • A 16-bit scaling factor (determines the decimal point position).
      • Sign flag.
      • Special and signaling flags (supporting ±0, ±Infinity, and NaN per IEEE 754 semantics).

    Because they are fixed-size, these types are trivially copyable, require no dynamic allocation, and are highly cache-friendly.

  3. Exact precision and fixed precision in fastnum

    master

    Unlike binary floating-point types (like f64), fastnum provides decimal floating-point arithmetic that represents decimal numbers exactly. This avoids common issues like 1.1 + 2.2 resulting in 3.3000000000000003.

    fastnum also supports fixed precision (significant places). It preserves trailing zeros to indicate significance, which is critical for monetary applications. For example, 1.30 + 1.20 results in 2.50, and 1.30 * 1.20 results in 1.5600.

    use fastnum::udec256;
    
    // Exact arithmetic
    let n = udec256!(1.1) + udec256!(2.2);
    assert_eq!(n.to_string(), "3.3");
    
    // Preserving significance (trailing zeros)
    let n_add = udec256!(1.30) + udec256!(1.20);
    assert_eq!(n_add, udec256!(2.50));
    
    let n_mul = udec256!(1.30) * udec256!(1.20);
    assert_eq!(n_mul, udec256!(1.5600));
  4. How fastnum decimals work

    master

    Unlike BigDecimal or BigInt which use dynamic allocation and Vec-based storage, fastnum uses fixed-size decimal numbers.

    Each decimal type consists of:

    1. An N-bit big unsigned integer to store the digits.
    2. A 64-bit signaling block containing:
      • A 16-bit scaling factor (determines the decimal point position).
      • Sign information.
      • Special and signaling flags (supporting ±0, ±Infinity, and NaN with IEEE 754 semantics).

    Key Advantages:

    • No dynamic allocation: All types are fixed-size and stored on the stack.
    • Trivially copyable: Since they are fixed-size, they are highly cache-friendly and efficient to pass around.
    • Strictly exact precision: Prevents common floating-point errors like 0.1 + 0.2 != 0.3.
    • Compile-time evaluation: Most from_* methods and mathematical functions are const, allowing for compile-time parsing and complex calculations.
  5. Configure Decimal Context

    master

    A Decimal context defines the rules governing arithmetic operations, specifically rounding_mode and signal_traps (which determine the action taken when signals like OVERFLOW or DIVISION_BY_ZERO occur).

    By default, the following signals are trapped (⚠️):

    • DIVISION_BY_ZERO
    • INVALID_OPERATION
    • OVERFLOW

    Other signals like CLAMPED, INEXACT, ROUNDED, SUBNORMAL, and UNDERFLOW are not trapped by default.

  6. Compare fastnum with other decimal libraries

    master

    fastnum is a high-performance decimal library designed for speed and precision. Compared to other common Rust decimal crates, it offers:

    • Arbitrary (but fixed) precision: Supports various bit sizes (64, 128, 256, 512, etc.).
    • Exact precision: Unlike f64, it provides exact decimal representation.
    • Performance: Highly optimized for parsing, arithmetic, and allocation, often outperforming bigdecimal and rust_decimal.
    • Features: Supports no-std, compile-time calculations, and handles special values like ±0, ±Infinity, and NaN.
    • Memory: Many types are trivially copyable and require no dynamic allocation.
    • Exceptional conditions: Supports signaling flags for inexact, subnormal, round, etc.
  7. Understand precision in fastnum

    master
    In fastnum, precision is defined as an integral number of decimal digits. The maximum possible precision is constrained by the maximum number of decimal digits that the underlying N-bit unsigned coefficient can hold.
  8. Understand the fastnum abstract number model

    master

    fastnum numbers are represented using an abstract model that distinguishes between finite numbers and special values.

    Finite Numbers

    A finite number is defined by three components:

    • sign: 0 for positive/zero, 1 for negative/negative zero.
    • coefficient: An unsigned integer (zero or positive).
    • exponent: A signed 16-bit integer (two's complement) representing the power of ten.

    Numerical value formula: (-1)^sign * coefficient * 10^exponent.

    Quantum: The value of a unit in the least significant position of the coefficient, calculated as 1 * 10^-exponent.

    Special Values

    fastnum supports three named special values:

    • NaN (Not a Number): Returned for invalid operations (e.g., 0/0, sqrt(-1)). It can be a quiet NaN or a signaling NaN.
    • Infinity: Represents a magnitude that is infinitely large. It can be +Infinity or -Infinity.
    • Signed Zero: Supports both +0 and -0. While numerically equal, they can behave differently in specific operations (e.g., 1/-0 = -Infinity).
  9. Use compile-time macro-helpers for constants

    master

    Each fastnum type provides a dedicated macro helper to define constants or variables that are evaluated at compile time. This allows for compile-time parsing and arithmetic checks, ensuring that invalid values or arithmetic errors (like division by zero) are caught during compilation.

    use fastnum::udec256;
    
    // Evaluated at compile-time and inlined
    const PI: UD256 = udec256!(3.141592653589793115997963468544185161590576171875);
    
    // Compile-time arithmetic
    const PI_X_2: UD256 = udec256!(2).mul(udec256!(3.141592653589793115997963468544185161590576171875));
  10. Use arbitrary precision with serde_json

    master

    When using serde_json, standard parsing can truncate or lose precision by converting numbers to f64. To prevent this, you can use the arbitrary_precision feature of serde_json in conjunction with the serde-arbitrary-precision feature in fastnum.

    To avoid global configuration conflicts in complex dependency trees, use the #[serde(with = "...")] attribute to apply arbitrary precision parsing to specific fields using decimal::serde::dec::arbitrary_precision.

    // #[cfg(feature="serde-arbitrary-precision")]
    {
        use serde::Deserialize;
        use fastnum::*;
    
        #[derive(Deserialize)]
        pub struct Arbitrary {
           #[serde(with = "decimal::serde::dec::arbitrary_precision")]
           value: D128,
        }
    
        let data: Arbitrary = serde_json::from_str(r#"{"value": -1.09861228866810969139524523692252570465}"#).unwrap();
        assert_eq!(dec128!(-1.09861228866810969139524523692252570465), data.value);
    }
  11. Install fastnum

    master

    To use fastnum in your Rust project, add it to your Cargo.toml dependencies. You can install the base version or enable optional features like serde for serialization.

    Base installation:

    fastnum = "0.7"

    Installation with features (e.g., serde):

    fastnum = { version = "0.7", features = ["serde"] }