uninum 0.1.1

A robust, ergonomic unified number type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
Documentation
//! Tests for subtraction operations

use uninum::Number;

/// Tests basic subtraction operations
#[test]
fn test_basic_subtraction() {
    // Integer subtraction
    assert_eq!(
        Number::from(30u64) - Number::from(10u64),
        Number::from(20u64)
    );
    assert_eq!(
        Number::from(20i64) - Number::from(30i64),
        Number::from(-10i64)
    );

    // Float subtraction
    assert_eq!(Number::from(10.5) - Number::from(2.5), Number::from(8.0));

    // Mixed type subtraction
    assert_eq!(Number::from(10u64) - Number::from(2.5), Number::from(7.5));
}

/// Tests unsigned underflow behavior
#[test]
fn test_unsigned_underflow() {
    // U64 underflow to signed
    let result = Number::from(5u64) - Number::from(10u64);
    assert_eq!(result, Number::from(-5i64));

    // Zero minus positive becomes negative
    assert_eq!(Number::from(0u64) - Number::from(1u64), Number::from(-1i64));

    // Large underflow
    let result = Number::from(1000u64) - Number::from(2000u64);
    assert!(result.try_get_u64().is_none()); // No longer unsigned
    // Result may be I64 or Decimal depending on internal promotion logic
    #[cfg(feature = "decimal")]
    assert!(result.try_get_i64() == Some(-1000) || result.try_get_decimal().is_some());
    #[cfg(not(feature = "decimal"))]
    {
        if let Some(i) = result.try_get_i64() {
            assert_eq!(i, -1000);
        } else if let Some(f) = result.try_get_f64() {
            assert_eq!(f, -1000.0);
        } else {
            panic!("Unexpected result type: {result:?}");
        }
    }
}

/// Tests signed overflow/underflow
#[test]
fn test_signed_overflow() {
    // Test i64 underflow
    let result = Number::from(i64::MIN) - Number::from(1i64);
    assert!(result.try_get_i64().is_none()); // Overflowed
    assert!(result.is_finite());

    // Test large positive minus large negative (potential overflow)
    let result = Number::from(i64::MAX) - Number::from(i64::MIN);
    assert!(result.try_get_i64().is_none()); // Too large for i64
}

/// Tests subtraction with primitives
#[test]
fn test_primitive_operations() {
    let num = Number::from(20);

    assert_eq!(&num - 5, Number::from(15));
    assert_eq!(30 - &num, Number::from(10));
    assert_eq!(&num - 5.5, Number::from(14.5));
}

/// Tests zero subtraction edge cases
#[test]
fn test_zero_operations() {
    // Subtracting zero preserves value and type
    assert_eq!(
        Number::from(42u64) - Number::from(0u64),
        Number::from(42u64)
    );
    assert_eq!(
        Number::from(-42i64) - Number::from(0i64),
        Number::from(-42i64)
    );
    assert_eq!(Number::from(3.16) - Number::from(0.0), Number::from(3.16));

    // Zero minus zero
    assert_eq!(Number::from(0u64) - Number::from(0u64), Number::from(0u64));

    // Equal values produce zero
    assert_eq!(
        Number::from(100u64) - Number::from(100u64),
        Number::from(0u64)
    );
}

/// Tests subtraction chains
#[test]
fn test_chain_operations() {
    // Chain causing underflow
    let result = Number::from(100u64) - Number::from(50u64) - Number::from(60u64);
    assert_eq!(result, Number::from(-10i64));

    // Multiple subtractions
    let result = Number::from(1000) - Number::from(100) - Number::from(200) - Number::from(300);
    assert_eq!(result, Number::from(400));
}

/// Tests special float values
#[test]
fn test_special_float_values() {
    let inf = Number::from(f64::INFINITY);
    let neg_inf = Number::from(f64::NEG_INFINITY);
    let nan = Number::from(f64::NAN);
    let normal = Number::from(42);

    // Infinity - normal = Infinity
    assert!((inf.clone() - normal.clone()).is_pos_inf());
    assert!((normal.clone() - inf.clone()).is_neg_inf());

    // NaN propagation
    assert!((nan.clone() - normal.clone()).is_nan());
    assert!((normal - nan).is_nan());

    // Infinity - Infinity = NaN
    assert!((inf.clone() - inf).is_nan());
    assert!((neg_inf.clone() - neg_inf).is_nan());
}

#[cfg(feature = "decimal")]
#[test]
fn test_decimal_precision() {
    use rust_decimal::Decimal;

    let a = Number::from(Decimal::new(1050, 2)); // 10.50
    let b = Number::from(Decimal::new(250, 2)); // 2.50

    let diff = a - b;
    if let Some(d) = diff.try_get_decimal() {
        assert_eq!(d.to_string(), "8.00");
    }

    // Test precision preservation in subtraction
    let precise1 = Number::from(Decimal::new(54321, 4)); // 5.4321
    let precise2 = Number::from(Decimal::new(12345, 4)); // 1.2345
    let diff = precise1 - precise2;
    if let Some(d) = diff.try_get_decimal() {
        assert_eq!(d.to_string(), "4.1976");
    }
}

/// Tests mathematical properties
#[test]
fn test_mathematical_properties() {
    let a = Number::from(100);
    let b = Number::from(30);
    let c = Number::from(20);

    // Subtraction is NOT commutative: a - b ≠ b - a
    assert_ne!(a.clone() - b.clone(), b.clone() - a.clone());

    // Subtraction is NOT associative: (a - b) - c ≠ a - (b - c)
    assert_ne!(
        (a.clone() - b.clone()) - c.clone(),
        a.clone() - (b.clone() - c.clone())
    );

    // But we can verify: a - b - c = a - (b + c)
    assert_eq!(a.clone() - b.clone() - c.clone(), a.clone() - (b + c));
}

/// Tests extreme values
#[test]
fn test_extreme_values() {
    // Very small differences
    let a = Number::from(1.0000000000001);
    let b = Number::from(1.0);
    let diff = a - b;
    if let Some(f) = diff.try_get_f64() {
        assert!((f - 0.0000000000001).abs() < 1e-15);
    }

    // Large number minus small number (precision test)
    let large = Number::from(1e308);
    let small = Number::from(1e-308);
    let diff = large - small;
    if let Some(f) = diff.try_get_f64() {
        assert_eq!(f, 1e308); // Small value lost in precision
    }
}