uninum 0.1.1

A robust, ergonomic unified number type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
Documentation
#![cfg(feature = "decimal")]
//! Tests for decimal pool functionality in the uninum crate.
//!
//! This module tests the thread-safe decimal allocation pool that reduces
//! heap allocations for commonly used decimal values.

use rust_decimal::Decimal;
use uninum::{Number, num};

#[test]
fn test_decimal_pool_common_values() {
    // Test that common decimal values are properly cached
    let zero1 = Number::from(Decimal::ZERO);
    let zero2 = Number::from(Decimal::ZERO);

    // While we can't directly test Arc sharing due to the pool implementation,
    // we can test that the values are equal
    assert_eq!(zero1, zero2);

    let one1 = Number::from(Decimal::ONE);
    let one2 = Number::from(Decimal::ONE);
    assert_eq!(one1, one2);

    let neg_one1 = Number::from(Decimal::NEGATIVE_ONE);
    let neg_one2 = Number::from(Decimal::NEGATIVE_ONE);
    assert_eq!(neg_one1, neg_one2);

    let two1 = Number::from(Decimal::TWO);
    let two2 = Number::from(Decimal::TWO);
    assert_eq!(two1, two2);

    let ten1 = Number::from(Decimal::TEN);
    let ten2 = Number::from(Decimal::TEN);
    assert_eq!(ten1, ten2);
}

#[test]
fn test_decimal_pool_uncommon_values() {
    // Test that uncommon decimal values are handled properly
    let custom1 = Number::from(Decimal::new(12345, 2)); // 123.45
    let custom2 = Number::from(Decimal::new(12345, 2)); // 123.45

    assert_eq!(custom1, custom2);

    let pi = Number::from(Decimal::try_from(std::f64::consts::PI).unwrap());
    let pi2 = Number::from(Decimal::try_from(std::f64::consts::PI).unwrap());
    assert_eq!(pi, pi2);
}

#[test]
fn test_decimal_pool_arithmetic_operations() {
    // Test that arithmetic operations with decimals work correctly
    let a = Number::from(Decimal::from(100));
    let b = Number::from(Decimal::from(200));

    let sum = &a + &b;
    assert_eq!(sum, Number::from(Decimal::from(300)));

    let diff = &b - &a;
    assert_eq!(diff, Number::from(Decimal::from(100)));

    let product = &a * &b;
    assert_eq!(product, Number::from(Decimal::from(20000)));

    let quotient = &b / &a;
    assert_eq!(quotient, Number::from(Decimal::from(2)));
}

#[test]
fn test_decimal_pool_mixed_operations() {
    // Test operations between decimals and other types
    let decimal = Number::from(Decimal::from(100));
    let int = Number::from(50u64);
    let float = num!(25.0);

    let result1 = &decimal + ∫
    assert_eq!(result1, Number::from(Decimal::from(150)));

    let result2 = &decimal - ∫
    assert_eq!(result2, Number::from(Decimal::from(50)));

    let result3 = &decimal * ∫
    assert_eq!(result3, Number::from(Decimal::from(5000)));

    let result4 = &decimal / ∫
    assert_eq!(result4, Number::from(Decimal::from(2)));

    // Mixed with float should promote to decimal for better precision
    let result5 = &decimal + &float;
    assert_eq!(result5, Number::from(Decimal::from(125)));
}

#[test]
fn test_decimal_pool_special_values() {
    // Test decimal with special mathematical values
    let zero = Number::from(Decimal::ZERO);
    let one = Number::from(Decimal::ONE);

    // Division by zero should produce infinity
    let result = &one / &zero;
    assert!(result.is_infinite());

    // Zero divided by zero should produce NaN
    let result = &zero / &zero;
    assert!(result.is_nan());

    // Large decimal values
    let large = Number::from(Decimal::from(i64::MAX));
    let result = &large + &large;
    // Should either succeed or overflow to F64
    assert!(result.try_get_decimal().is_some() || result.try_get_f64().is_some());
}

#[test]
fn test_decimal_pool_precision() {
    // Test that decimal operations maintain precision
    let a = Number::from(Decimal::new(1, 1)); // 0.1
    let b = Number::from(Decimal::new(2, 1)); // 0.2
    let c = Number::from(Decimal::new(3, 1)); // 0.3

    let sum = &a + &b;
    assert_eq!(sum, c);

    // Test with high precision
    let high_precision = Number::from(Decimal::new(123_456_789, 8)); // 1.23456789
    let result = &high_precision * &Number::from(Decimal::from(2));
    assert_eq!(result, Number::from(Decimal::new(246_913_578, 8)));
}

#[test]
fn test_decimal_pool_comparison() {
    // Test decimal comparison operations
    let a = Number::from(Decimal::from(100));
    let b = Number::from(Decimal::from(200));
    let c = Number::from(Decimal::from(100));

    assert!(a < b);
    assert!(b > a);
    assert!(a == c);
    assert!(a <= c);
    assert!(a >= c);
    assert!(a != b);
}

#[test]
fn test_decimal_pool_conversion() {
    // Test decimal conversions
    let decimal = Number::from(Decimal::from(42));

    // Test conversion to different types
    assert!(decimal.try_get_decimal().is_some());
    assert_eq!(decimal.try_get_u64(), None);
    assert_eq!(decimal.try_get_f64(), None);

    // Test is_integer and is_float
    assert!(decimal.is_integer()); // Decimal with integer value is considered integer
    assert!(decimal.is_float());

    // Test integer value decimal
    let int_decimal = Number::from(Decimal::from(42));
    assert!(int_decimal.is_integer()); // Integer value should be detected

    // Test fractional decimal
    let frac_decimal = Number::from(Decimal::new(425, 1)); // 42.5
    assert!(!frac_decimal.is_integer()); // Fractional value should not be
    // integer
}

#[test]
fn test_decimal_pool_thread_safety() {
    // Test that decimal pool works correctly in multi-threaded context
    use std::thread;

    let handles: Vec<_> = (0..10)
        .map(|i| {
            thread::spawn(move || {
                let decimal = Number::from(Decimal::from(i));
                let result = &decimal + &Number::from(Decimal::from(100));
                assert_eq!(result, Number::from(Decimal::from(100 + i)));
            })
        })
        .collect();

    for handle in handles {
        handle.join().unwrap();
    }
}

#[test]
fn test_decimal_pool_large_numbers() {
    // Test decimal pool with large numbers
    let large1 = Number::from(Decimal::from(u64::MAX));
    let large2 = Number::from(Decimal::from(u64::MAX));

    assert_eq!(large1, large2);

    // Test arithmetic with large numbers
    let one = Number::from(Decimal::ONE);
    let result = &large1 + &one;

    // Should either succeed or overflow to F64
    assert!(result.try_get_decimal().is_some() || result.try_get_f64().is_some());
}

#[test]
fn test_decimal_pool_negative_numbers() {
    // Test decimal pool with negative numbers
    let neg_one = Number::from(Decimal::NEGATIVE_ONE);
    let neg_hundred = Number::from(Decimal::from(-100));

    assert!(neg_one.is_negative());
    assert!(neg_hundred.is_negative());
    assert!(!neg_one.is_positive());
    assert!(!neg_hundred.is_positive());

    let result = &neg_one + &neg_hundred;
    assert_eq!(result, Number::from(Decimal::from(-101)));

    let result = &neg_hundred - &neg_one;
    assert_eq!(result, Number::from(Decimal::from(-99)));
}