rust_decimal 1.41.0

Decimal number implementation written in pure Rust suitable for financial and fixed-precision calculations.
Documentation
#![cfg(target_arch = "wasm32")]

// Tests use both `Decimal::from_str` (the standard `FromStr` trait) and `Decimal::from_string`
// (the wasm-bindgen binding). This is intentional: `from_string` tests exercise the wasm API
// surface, while `from_str` tests verify that core Decimal functionality works under wasm.

use core::str::FromStr;
use rust_decimal::Decimal;
use wasm_bindgen_test::*;

// from_number

#[wasm_bindgen_test]
fn it_converts_positive_integer_from_number() {
    let a = Decimal::from_number(42.0).unwrap();
    assert!(!a.is_sign_negative());
    assert_eq!("42", a.to_string());
}

#[wasm_bindgen_test]
fn it_converts_negative_integer_from_number() {
    let a = Decimal::from_number(-123.0).unwrap();
    assert!(a.is_sign_negative());
    assert_eq!("-123", a.to_string());
}

#[wasm_bindgen_test]
fn it_converts_zero_from_number() {
    let a = Decimal::from_number(0.0).unwrap();
    assert_eq!("0", a.to_string());
}

#[wasm_bindgen_test]
fn it_converts_positive_float_from_number() {
    let a = Decimal::from_number(3.14).unwrap();
    assert_eq!("3.14", a.to_string());
}

#[wasm_bindgen_test]
fn it_converts_small_fractional_from_number() {
    let a = Decimal::from_number(0.001).unwrap();
    assert_eq!("0.001", a.to_string());
}

#[wasm_bindgen_test]
fn it_converts_large_number_from_number() {
    let a = Decimal::from_number(1_000_000_000.0).unwrap();
    assert_eq!("1000000000", a.to_string());
}

#[wasm_bindgen_test]
fn it_returns_none_for_nan() {
    assert!(Decimal::from_number(f64::NAN).is_none());
}

#[wasm_bindgen_test]
fn it_returns_none_for_positive_infinity() {
    assert!(Decimal::from_number(f64::INFINITY).is_none());
}

#[wasm_bindgen_test]
fn it_returns_none_for_negative_infinity() {
    assert!(Decimal::from_number(f64::NEG_INFINITY).is_none());
}

// from_string

#[wasm_bindgen_test]
fn it_converts_positive_float_from_string() {
    let a = Decimal::from_string("233.323223").unwrap();
    assert!(!a.is_sign_negative());
    assert_eq!(a.scale(), 6);
    assert_eq!("233.323223", a.to_string());
}

#[wasm_bindgen_test]
fn it_converts_negative_float_from_string() {
    let a = Decimal::from_string("-233.43343").unwrap();
    assert!(a.is_sign_negative());
    assert_eq!("-233.43343", a.to_string());
}

#[wasm_bindgen_test]
fn it_converts_big_integer_from_string() {
    let a = Decimal::from_string("79228162514264337593543950330").unwrap();
    assert_eq!("79228162514264337593543950330", a.to_string());
}

#[wasm_bindgen_test]
fn it_returns_none_for_empty_string() {
    assert!(Decimal::from_string("").is_none());
}

#[wasm_bindgen_test]
fn it_returns_none_for_invalid_string() {
    assert!(Decimal::from_string("not_a_number").is_none());
}

// to_string_js

#[wasm_bindgen_test]
fn it_converts_decimal_to_string() {
    let a = Decimal::from_string("12.3456789").unwrap();
    assert_eq!("12.3456789", a.to_string_js());
}

#[wasm_bindgen_test]
fn it_converts_negative_decimal_to_string() {
    let a = Decimal::from_number(-42.0).unwrap();
    assert_eq!("-42", a.to_string_js());
}

// to_number

#[wasm_bindgen_test]
fn it_converts_positive_decimal_to_number() {
    let a = Decimal::from_str("233.323223").unwrap();
    let n = a.to_number();
    assert!((n - 233.323223).abs() < 1e-10);
}

#[wasm_bindgen_test]
fn it_converts_negative_decimal_to_number() {
    let a = Decimal::from_str("-233.43343").unwrap();
    let n = a.to_number();
    assert!((n - -233.43343).abs() < 1e-10);
}

#[wasm_bindgen_test]
fn it_converts_zero_to_number() {
    let a = Decimal::from_str("0").unwrap();
    assert_eq!(a.to_number(), 0.0);
}

// Round-trip: from_number -> to_number

#[wasm_bindgen_test]
fn it_round_trips_positive_float() {
    let n = 12345.6789;
    let a = Decimal::from_number(n).unwrap();
    assert!((a.to_number() - n).abs() < 1e-10);
}

#[wasm_bindgen_test]
fn it_round_trips_negative_float() {
    let n = -9876.54321;
    let a = Decimal::from_number(n).unwrap();
    assert!((a.to_number() - n).abs() < 1e-10);
}

// Round-trip: from_str -> to_number -> from_number -> to_string

#[wasm_bindgen_test]
fn it_round_trips_through_string_and_number() {
    let tests = ["1.5", "100", "0.001", "-42.42"];
    for test in &tests {
        let a = Decimal::from_str(test).unwrap();
        let n = a.to_number();
        let b = Decimal::from_number(n).unwrap();
        assert_eq!(test.to_string(), b.to_string(), "Round-trip failed for {test}");
    }
}

// Round-trip: from_string -> to_string_js

#[wasm_bindgen_test]
fn it_round_trips_from_string_to_string_js() {
    let tests = [
        "12.3456789",
        "79228162514264337593543950330",
        "-5233.9008808150288439427720175",
        "0",
        "0.001",
    ];
    for test in &tests {
        let a = Decimal::from_string(test).unwrap();
        assert_eq!(test.to_string(), a.to_string_js(), "Round-trip failed for {test}");
    }
}

// Round-trip: from_string -> to_number

#[wasm_bindgen_test]
fn it_round_trips_from_string_to_number() {
    let tests = [("1.5", 1.5), ("100", 100.0), ("-42.42", -42.42), ("0.001", 0.001)];
    for (input, expected) in &tests {
        let a = Decimal::from_string(input).unwrap();
        let n = a.to_number();
        assert!((n - expected).abs() < 1e-10, "Round-trip failed for {input}: got {n}");
    }
}

// Arithmetic via wasm-constructed decimals
//
// Note: `from_number` converts via `f64`, which may introduce trailing scale
// (e.g. `1.5` has scale 1). Arithmetic preserves scale from the operands, so
// results may include trailing zeros (e.g. `1.5 + 2.5 = "4.0"` not `"4"`).

#[wasm_bindgen_test]
fn it_can_add_wasm_decimals() {
    let a = Decimal::from_number(1.5).unwrap();
    let b = Decimal::from_number(2.5).unwrap();
    assert_eq!("4.0", (a + b).to_string());
}

#[wasm_bindgen_test]
fn it_can_subtract_wasm_decimals() {
    let a = Decimal::from_number(10.0).unwrap();
    let b = Decimal::from_number(3.5).unwrap();
    assert_eq!("6.5", (a - b).to_string());
}

#[wasm_bindgen_test]
fn it_can_multiply_wasm_decimals() {
    // Multiplication of two integer-valued f64s (scale 0) produces scale 0
    let a = Decimal::from_number(6.0).unwrap();
    let b = Decimal::from_number(7.0).unwrap();
    assert_eq!("42", (a * b).to_string());
}

#[wasm_bindgen_test]
fn it_can_divide_wasm_decimals() {
    // Division preserves scale from operands, so 10.0 / 4.0 yields "2.50"
    let a = Decimal::from_number(10.0).unwrap();
    let b = Decimal::from_number(4.0).unwrap();
    assert_eq!("2.50", (a / b).to_string());
}

// Core functionality works under wasm

#[wasm_bindgen_test]
fn it_can_parse_string_in_wasm() {
    let a = Decimal::from_str("79228162514264337593543950330").unwrap();
    assert_eq!("79228162514264337593543950330", a.to_string());
}

#[wasm_bindgen_test]
fn it_can_serialize_deserialize_in_wasm() {
    let tests = [
        "12.3456789",
        "5233.9008808150288439427720175",
        "-5233.9008808150288439427720175",
    ];
    for test in &tests {
        let a = Decimal::from_str(test).unwrap();
        let bytes = a.serialize();
        let b = Decimal::deserialize(bytes);
        assert_eq!(test.to_string(), b.to_string());
    }
}

#[wasm_bindgen_test]
fn it_can_compare_wasm_decimals() {
    let a = Decimal::from_number(1.0).unwrap();
    let b = Decimal::from_number(2.0).unwrap();
    let c = Decimal::from_number(1.0).unwrap();
    assert!(a < b);
    assert!(b > a);
    assert_eq!(a, c);
}

#[wasm_bindgen_test]
fn it_can_check_sign_of_wasm_decimal() {
    let pos = Decimal::from_number(5.0).unwrap();
    let neg = Decimal::from_number(-5.0).unwrap();
    assert!(!pos.is_sign_negative());
    assert!(neg.is_sign_negative());
}

#[wasm_bindgen_test]
fn it_can_check_zero_of_wasm_decimal() {
    let zero = Decimal::from_number(0.0).unwrap();
    let non_zero = Decimal::from_number(1.0).unwrap();
    assert!(zero.is_zero());
    assert!(!non_zero.is_zero());
}

#[wasm_bindgen_test]
fn it_can_round_wasm_decimal() {
    let a = Decimal::from_str("2.567").unwrap();
    let rounded = a.round_dp(2);
    assert_eq!("2.57", rounded.to_string());
}

#[wasm_bindgen_test]
fn it_can_floor_and_ceil_wasm_decimal() {
    let a = Decimal::from_str("2.3").unwrap();
    assert_eq!("2", a.floor().to_string());
    assert_eq!("3", a.ceil().to_string());
}

#[wasm_bindgen_test]
fn it_can_extract_mantissa_and_scale_in_wasm() {
    let a = Decimal::from_str("1.123456").unwrap();
    assert_eq!(a.mantissa(), 1123456i128);
    assert_eq!(a.scale(), 6);
}

#[wasm_bindgen_test]
fn it_can_access_constants_in_wasm() {
    assert_eq!("0", Decimal::ZERO.to_string());
    assert_eq!("1", Decimal::ONE.to_string());
    assert_eq!("10", Decimal::TEN.to_string());
}