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 multiplication operations

use uninum::Number;

/// Tests basic multiplication operations
#[test]
fn test_basic_multiplication() {
    // Integer multiplication
    assert_eq!(
        Number::from(10u64) * Number::from(3u64),
        Number::from(30u64)
    );
    assert_eq!(
        Number::from(-10i64) * Number::from(3i64),
        Number::from(-30i64)
    );
    assert_eq!(
        Number::from(-10i64) * Number::from(-3i64),
        Number::from(30i64)
    );

    // Float multiplication
    assert_eq!(Number::from(10.5) * Number::from(2.0), Number::from(21.0));

    // Mixed type multiplication
    assert_eq!(Number::from(10u64) * Number::from(2.5), Number::from(25.0));
    assert_eq!(
        Number::from(-5i64) * Number::from(10u64),
        Number::from(-50i64)
    );
}

/// Tests multiplication overflow handling
#[test]
fn test_overflow_handling() {
    // Large multiplication within bounds
    assert_eq!(
        Number::from(1_000_000u64) * Number::from(1_000_000u64),
        Number::from(1_000_000_000_000u64)
    );

    // U64 overflow
    let result = Number::from(u64::MAX) * Number::from(2u64);
    assert!(result.try_get_u64().is_none()); // Overflowed
    assert!(result.is_finite());

    // I64 overflow
    let result = Number::from(i64::MAX) * Number::from(2i64);
    assert!(result.try_get_i64().is_none());
    assert!(result.is_finite());
}

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

    assert_eq!(&num * 3, Number::from(30));
    assert_eq!(3 * &num, Number::from(30));
    assert_eq!(&num * 2.5, Number::from(25.0));
    assert_eq!(&num * 0.5, Number::from(5.0));
}

/// Tests multiplication by zero and one
#[test]
fn test_identity_and_zero() {
    // Multiplication by zero
    assert_eq!(Number::from(42u64) * Number::from(0u64), Number::from(0u64));
    assert_eq!(
        Number::from(-42i64) * Number::from(0i64),
        Number::from(0i64)
    );
    assert_eq!(Number::from(42.5) * Number::from(0.0), Number::from(0.0));

    // Multiplication by one (identity)
    assert_eq!(
        Number::from(42u64) * Number::from(1u64),
        Number::from(42u64)
    );
    assert_eq!(
        Number::from(-42i64) * Number::from(1i64),
        Number::from(-42i64)
    );
    assert_eq!(Number::from(42.5) * Number::from(1.0), Number::from(42.5));

    // Multiplication by negative one
    assert_eq!(
        Number::from(42i64) * Number::from(-1i64),
        Number::from(-42i64)
    );
    assert_eq!(
        Number::from(-42i64) * Number::from(-1i64),
        Number::from(42i64)
    );
}

/// 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 zero = Number::from(0);
    let normal = Number::from(42);

    // Infinity * normal
    assert!((inf.clone() * normal.clone()).is_pos_inf());
    assert!((neg_inf.clone() * normal.clone()).is_neg_inf());

    // Infinity * negative
    assert!((inf.clone() * Number::from(-1)).is_neg_inf());
    assert!((neg_inf.clone() * Number::from(-1)).is_pos_inf());

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

    // Infinity * 0 = NaN (indeterminate form)
    assert!((inf.clone() * zero.clone()).is_nan());
    assert!((neg_inf * zero).is_nan());
}

/// Tests signed zero behavior
#[test]
fn test_signed_zero() {
    let pos_zero = Number::from(0.0);
    let neg_zero = Number::from(-0.0);
    let pos = Number::from(42.0);
    let neg = Number::from(-42.0);

    // Positive * positive zero = positive zero
    let result = pos.clone() * pos_zero.clone();
    if let Some(f) = result.try_get_f64() {
        assert!(f == 0.0 && f.is_sign_positive());
    }

    // Positive * negative zero = negative zero
    let result = pos * neg_zero.clone();
    if let Some(f) = result.try_get_f64() {
        assert!(f == 0.0 && f.is_sign_negative());
    }

    // Negative * negative zero = positive zero
    let result = neg * neg_zero;
    if let Some(f) = result.try_get_f64() {
        assert!(f == 0.0 && f.is_sign_positive());
    }
}

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

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

    let product = a * b;
    if let Some(d) = product.try_get_decimal() {
        assert_eq!(d.to_string(), "10.0000"); // Maintains precision
    }

    // Test precision in small number multiplication
    let small1 = Number::from(Decimal::new(123, 3)); // 0.123
    let small2 = Number::from(Decimal::new(456, 3)); // 0.456
    let product = small1 * small2;
    if let Some(d) = product.try_get_decimal() {
        assert_eq!(d.to_string(), "0.056088");
    }
}

/// Tests mathematical properties
#[test]
fn test_mathematical_properties() {
    let a = Number::from(2);
    let b = Number::from(3);
    let c = Number::from(4);

    // Commutativity: a * b = b * a
    assert_eq!(a.clone() * b.clone(), b.clone() * a.clone());

    // Associativity: (a * b) * c = a * (b * c)
    assert_eq!(
        (a.clone() * b.clone()) * c.clone(),
        a.clone() * (b.clone() * c.clone())
    );

    // Distributivity with addition: a * (b + c) = a * b + a * c
    assert_eq!(
        a.clone() * (b.clone() + c.clone()),
        a.clone() * b.clone() + a.clone() * c.clone()
    );
}

/// Tests chain operations
#[test]
fn test_chain_operations() {
    // Simple chain
    let result = Number::from(2) * Number::from(3) * Number::from(4);
    assert_eq!(result, Number::from(24));

    // Chain with mixed types
    let result = Number::from(10u64) * Number::from(2.5) * Number::from(2u64);
    assert_eq!(result, Number::from(50.0));

    // Chain with potential overflow
    let result = Number::from(1000u64) * Number::from(1000u64) * Number::from(1000u64);
    assert_eq!(result, Number::from(1_000_000_000u64));
}

/// Tests extreme values
#[test]
fn test_extreme_values() {
    // Very small numbers
    let tiny1 = Number::from(1e-150);
    let tiny2 = Number::from(1e-150);
    let product = tiny1 * tiny2;
    if let Some(f) = product.try_get_f64() {
        assert_eq!(f, 1e-300);
    }

    // Very large numbers
    let large1 = Number::from(1e150);
    let large2 = Number::from(1e150);
    let product = large1 * large2;
    if let Some(f) = product.try_get_f64() {
        // Allow for floating point precision differences
        assert!((f - 1e300).abs() < 1e290 || f == 9.999999999999999e299);
    }

    // Mix of large and small (tests precision)
    let large = Number::from(1e308);
    let small = Number::from(1e-308);
    let product = large * small;
    if let Some(f) = product.try_get_f64() {
        // Allow for floating point precision differences
        assert!((f - 1.0).abs() < 0.1 || f == 0.9999999999999999);
    }
}