fin_decimal 0.1.0

A high-performance decimal fixed-point arithmetic library for financial applications
Documentation
  • Coverage
  • 100%
    72 out of 72 items documented23 out of 45 items with examples
  • Size
  • Source code size: 96.77 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 5.11 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 11s Average build duration of successful builds.
  • all releases: 11s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • vsukhoml/fin_decimal
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • vsukhoml

fin_decimal - Rust High-Performance Decimal Fixed-point Arithmetic

Build Status

fin_decimal is a high-performance, #![no_std] compatible decimal fixed-point library tailored specifically for financial and tax computations.

Unlike arbitrary-precision "big decimal" libraries that are slow and heap-allocate, or standard floating-point numbers (f32/f64) which suffer from rounding errors and precision loss, fin_decimal operates on an implicit scaling factor over a standard 64-bit signed integer (i64).

This means additions and subtractions compile down to single, lightning-fast native CPU instructions, while multiplications and divisions utilize 128-bit math (with platform-specific inline assembly on x86_64) to guarantee strictly compliant decimal rounding without floating-point artifacts.

Features

  • Const Generics (Decimal<const DIGITS: u8>): Zero-cost abstraction over multiple precision types.
  • Amount64 (4 Decimal Digits): The standard precision for accounting, monetary amounts, and ledgers (up to ~±922 billion).
  • Rate64 (8 Decimal Digits): A higher-precision type used for exact forex/exchange rates and interest calculations.
  • Strict Rounding Modes: Explicit .round_to(mode), .mul_rounded(), and .div_rounded() methods to ensure compliance with arbitrary tax codes (e.g. HalfUp, HalfEven Banker's Rounding, Down, Up).
  • Checked Math: Built-in methods like .checked_add() to prevent overflow panics in mission-critical applications.
  • Serde Support: Optional string-based JSON serialization via the serde feature, preventing transit precision loss over REST APIs.
  • no_std Environment Support: Works gracefully in embedded contexts or core-only systems.

Getting Started

Add fin_decimal to your Cargo.toml. To enable JSON serialization, include the serde feature:

[dependencies]
fin_decimal = { version = "0.1", features = ["serde"] }

Basic Usage

The library provides trait overloads, so the standard +, -, *, /, and % operators work ergonomically alongside native primitives. By default, standard multiplication and division utilize standard "Half-Up" financial rounding.

use fin_decimal::Amount64;
use core::str::FromStr;

fn main() {
    // Initialization
    let a = Amount64::from(3);              // 3.0000
    let b = Amount64::from(2.02f64);        // 2.0200
    let c = Amount64::from_str("1.50").unwrap();

    // Standard Math Traits
    let sum = a + b;                        // 5.0200
    let diff = a - c;                       // 1.5000
    
    // Interactions with native integers
    let multiplied = sum * 2;               // 10.0400
    
    println!("Total: {}", multiplied);      // Outputs: "Total: 10.04"
}

Explicit Rounding & Compliance

When calculating taxes or complex financial algorithms, regulations often dictate exact rounding constraints. Use the Rounding enum to dictate exactly how calculations terminate.

use fin_decimal::{Amount64, Rounding};

fn main() {
    let tax_rate = Amount64::from(0.075); // 7.5%
    let item_price = Amount64::from(19.99);
    
    // Explicitly round tax down to benefit the consumer, 
    // rather than using standard Half-Up.
    let tax_owed = item_price.mul_rounded(tax_rate, Rounding::Down); 
    
    // You can also explicitly round arbitrary values
    let val = Amount64::from(2.5);
    assert_eq!(val.round_to(Rounding::HalfEven), Amount64::from(2)); // Banker's Rounding
    assert_eq!(val.round_to(Rounding::HalfUp), Amount64::from(3));   // Standard Rounding
}

Safety and Overflow (Checked Math)

Because Amount64 is backed by a 64-bit integer, values exceeding ~922,337,203,685 (for 4 decimal digits) will wrap or panic in debug mode depending on your build settings.

For robust backends, always use the checked_* routines to gracefully handle theoretical overflows:

use fin_decimal::Amount64;

fn main() {
    let massive_balance = Amount64::MAX;
    let deposit = Amount64::from(100);

    match massive_balance.checked_add(deposit) {
        Some(new_balance) => println!("Success: {}", new_balance),
        None => println!("Transaction failed: Account balance overflow!"),
    }
}

Exchange Rates (Rate64)

If you are dealing with fractional percentages or Forex multi-currency pipelines, 4 decimal places is rarely enough. The Rate64 type provides 8 decimal places of precision automatically.

use fin_decimal::{Amount64, Rate64};

fn main() {
    let usd_to_jpy = Rate64::from(150.12345678);
    println!("Exchange Rate: {}", usd_to_jpy); // Outputs: "150.12345678"
}

Why Not f64 or Big Numbers?

  1. Floating Point (f64): Floats cannot perfectly represent base-10 decimals. 0.1 + 0.2 famously equals 0.30000000000000004 in floating-point math, violating strict accounting properties.
  2. Big Numbers (e.g. num-bigint or rust-decimal): These libraries perform heap allocations on virtually every math operation and represent numbers internally as slow arrays or Vecs. fin_decimal relies purely on i64 CPU registers, making it orders of magnitude faster.
  3. The Sweet Spot: The ±9.22 trillion limit of Amount64 is vastly more than sufficient for 99% of general ledger entries, eCommerce carts, and standard banking, while reaping the ultimate performance benefits of fixed-width hardware math.