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

use uninum::Number;

/// Tests basic division operations
#[test]
fn test_basic_division() {
    // Exact integer division
    assert_eq!(
        Number::from(30u64) / Number::from(3u64),
        Number::from(10u64)
    );
    assert_eq!(
        Number::from(-30i64) / Number::from(3i64),
        Number::from(-10i64)
    );
    assert_eq!(
        Number::from(-30i64) / Number::from(-3i64),
        Number::from(10i64)
    );

    // Float division
    assert_eq!(Number::from(10.5) / Number::from(2.5), Number::from(4.2));

    // Mixed type division
    assert_eq!(Number::from(10u64) / Number::from(4.0), Number::from(2.5));
}

/// Tests exact vs inexact integer division
#[test]
fn test_exact_vs_inexact_division() {
    // Exact division stays integer
    assert_eq!(Number::from(10u64) / Number::from(2u64), Number::from(5u64));
    assert_eq!(
        Number::from(-20i64) / Number::from(4i64),
        Number::from(-5i64)
    );

    // Inexact division promotes to higher precision
    let result = Number::from(10u64) / Number::from(3u64);
    assert!(result.try_get_u64().is_none()); // No longer an integer

    // Verify the result is approximately correct
    if let Some(f) = result.try_get_f64() {
        assert!((f - 3.333333).abs() < 0.000001);
    }

    #[cfg(feature = "decimal")]
    {
        // With decimal feature, should maintain precision
        if let Some(d) = result.try_get_decimal() {
            assert!(d.to_string().starts_with("3.333"));
        }
    }
}

/// Tests division by zero behavior (follows IEEE 754 semantics)
#[test]
fn test_division_by_zero() {
    // Positive / 0 = +Infinity
    let result = Number::from(42) / Number::from(0);
    assert!(result.is_pos_inf());

    // Negative / 0 = -Infinity
    let result = Number::from(-42) / Number::from(0);
    assert!(result.is_neg_inf());

    // 0 / 0 = NaN (indeterminate)
    let result = Number::from(0) / Number::from(0);
    assert!(result.is_nan());

    // Float division by zero
    let result = Number::from(1.0) / Number::from(0.0);
    assert!(result.is_pos_inf());
}

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

    assert_eq!(&num / 4, Number::from(5));
    assert_eq!(100 / &num, Number::from(5));
    assert_eq!(&num / 2.5, Number::from(8.0));
}

/// Tests division by one and identity
#[test]
fn test_identity_operations() {
    // Division by one preserves value
    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));

    // Division by negative one negates
    assert_eq!(
        Number::from(42i64) / Number::from(-1i64),
        Number::from(-42i64)
    );
    assert_eq!(
        Number::from(-42i64) / Number::from(-1i64),
        Number::from(42i64)
    );

    // Self division equals one (except for special cases)
    assert_eq!(Number::from(42) / Number::from(42), Number::from(1));
    assert_eq!(Number::from(-42) / Number::from(-42), Number::from(1));
}

/// 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!((neg_inf.clone() / normal.clone()).is_neg_inf());

    // Normal / Infinity = 0
    let result = normal.clone() / inf.clone();
    if let Some(f) = result.try_get_f64() {
        assert_eq!(f, 0.0);
    }

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

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

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

    // Test exact decimal division
    let a = Number::from(Decimal::new(1000, 2)); // 10.00
    let b = Number::from(Decimal::new(400, 2)); // 4.00

    let quotient = a / b;
    if let Some(d) = quotient.try_get_decimal() {
        assert_eq!(d.to_string(), "2.50");
    }

    // Test precision preservation
    let precise1 = Number::from(Decimal::new(10000, 4)); // 1.0000
    let precise2 = Number::from(Decimal::new(30000, 4)); // 3.0000
    let quotient = precise1 / precise2;
    if let Some(d) = quotient.try_get_decimal() {
        // Should maintain precision for repeating decimal
        assert!(d.to_string().starts_with("0.3333"));
    }
}

#[test]
fn test_float_integer_paths() {
    // Floats that represent integers and divide exactly stay as floats
    let exact = Number::from(8.0) / Number::from(4.0);
    assert!(matches!(exact, Number::F64(_)));
    assert_eq!(exact.try_get_f64().unwrap(), 2.0);

    // Floats that represent integers but divide inexactly promote appropriately
    let inexact = Number::from(10.0) / Number::from(3.0);
    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        use std::convert::TryFrom;
        let as_decimal = inexact
            .try_get_decimal()
            .expect("expected Decimal fallback");
        let expected = Decimal::try_from(10.0).unwrap() / Decimal::try_from(3.0).unwrap();
        assert_eq!(as_decimal.to_string(), expected.to_string());
    }
    #[cfg(not(feature = "decimal"))]
    {
        let as_f64 = inexact.try_get_f64().unwrap();
        assert!((as_f64 - 3.3333333333333335).abs() < 1e-12);
    }
}

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

    let zero = Number::from(Decimal::new(0, 0));
    let positive = Number::from(Decimal::new(123, 2)); // 1.23
    let negative = Number::from(Decimal::new(-456, 2)); // -4.56

    assert!(matches!(zero.clone() / zero.clone(), Number::F64(ref f) if f.0.is_nan()));
    assert!(
        matches!(positive / zero.clone(), Number::F64(ref f) if f.0.is_sign_positive() && f.0.is_infinite())
    );
    assert!(
        matches!(negative / zero, Number::F64(ref f) if f.0.is_sign_negative() && f.0.is_infinite())
    );
}

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

    let near_max = Number::from(Decimal::MAX);
    let tiny = Number::from(Decimal::new(1, 28)); // 1e-28 triggers overflow on division

    let result = near_max / tiny;
    assert!(
        result.try_get_decimal().is_none(),
        "expected Decimal::checked_div overflow to fall back to F64"
    );
    assert!(
        result.try_get_f64().is_some(),
        "division overflow should produce a lossy F64"
    );
}

#[cfg(feature = "decimal")]
#[test]
fn test_float_division_decimal_conversion_failure_falls_back() {
    let large_integral = Number::from(1e29);
    let divisor = Number::from(3.0);

    let result = large_integral / divisor;
    assert!(
        result.try_get_decimal().is_none(),
        "values beyond Decimal::MAX should not produce Decimal results"
    );
    assert!(
        result.try_get_f64().is_some(),
        "conversion failure should fall back to F64"
    );
}

/// Tests reciprocal operations
#[test]
fn test_reciprocal() {
    // 1 / n
    assert_eq!(Number::from(1) / Number::from(2), Number::from(0.5));
    assert_eq!(Number::from(1) / Number::from(4), Number::from(0.25));

    // Reciprocal of reciprocal
    let n = Number::from(5);
    let reciprocal = Number::from(1) / n.clone();
    let double_reciprocal = Number::from(1) / reciprocal;

    // Should get back approximately the original number
    if let Some(f) = double_reciprocal.try_get_f64() {
        assert!((f - 5.0).abs() < 1e-10);
    }
}

/// Tests chain division operations
#[test]
fn test_chain_operations() {
    // Simple chain
    let result = Number::from(100) / Number::from(5) / Number::from(4);
    assert_eq!(result, Number::from(5));

    // Chain with inexact division
    let result = Number::from(100) / Number::from(3) / Number::from(2);
    if let Some(f) = result.try_get_f64() {
        assert!((f - 16.666666).abs() < 0.000001);
    }
}

/// Tests extreme values
#[test]
fn test_extreme_values() {
    // Very large divided by very small
    let large = Number::from(1e308);
    let small = Number::from(1e-308);
    let result = large / small;
    assert!(result.is_pos_inf()); // Overflow to infinity

    // Very small divided by very large
    let small = Number::from(1e-308);
    let large = Number::from(1e308);
    let result = small / large;
    if let Some(f) = result.try_get_f64() {
        assert_eq!(f, 0.0); // Underflow to zero
    }

    // Division near machine epsilon
    let a = Number::from(1.0);
    let b = Number::from(1.0 + f64::EPSILON);
    let result = a / b;
    if let Some(f) = result.try_get_f64() {
        assert!((f - (1.0 / (1.0 + f64::EPSILON))).abs() < 1e-15);
    }
}