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 accessor methods
//!
//! This module tests the accessor methods (try_get_u64, try_get_f64, etc.)
//! and utility methods like as_i128 and as_f64_lossy.

use uninum::{Number, num};

/// Tests accessor methods
#[test]
fn test_accessor_methods() {
    // Successful extractions
    assert_eq!(Number::from(70000_u64).try_get_u64(), Some(70000));
    assert_eq!(Number::from(1_000_000_u64).try_get_u64(), Some(1_000_000));
    assert_eq!(Number::from(-70000_i64).try_get_i64(), Some(-70000));
    assert_eq!(Number::from(-1_000_000_i64).try_get_i64(), Some(-1_000_000));
    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        use std::str::FromStr;
        let dec_num = Number::from(Decimal::from_str("2.72").unwrap());
        assert!(dec_num.try_get_decimal().is_some());
    }
    #[cfg(not(feature = "decimal"))]
    assert_eq!(
        Number::from(std::f64::consts::E).try_get_f64(),
        Some(std::f64::consts::E)
    );

    // Failed extractions (wrong type)
    assert_eq!(Number::from(42_u64).try_get_i64(), None);
    assert_eq!(Number::from(std::f64::consts::PI).try_get_u64(), None);
    assert_eq!(Number::from(42_u64).try_get_f64(), None);

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        let dec = Number::from(Decimal::new(42, 0));
        assert!(dec.try_get_decimal().is_some());
        assert_eq!(Number::from(42_u64).try_get_decimal(), None);
    }
}

/// Tests that all accessor methods properly handle type mismatches
#[test]
fn test_accessor_methods_all_type_combinations() {
    // Create test values for each type
    let values = vec![
        Number::from(70000_u64),
        Number::from(-70000_i64),
        Number::from(1_000_000_u64),
        Number::from(-1_000_000_i64),
        Number::from(std::f64::consts::E),
    ];

    // Test each accessor method returns None for wrong types
    for value in &values {
        // Count how many accessors return Some
        let mut success_count = 0;

        if value.try_get_u64().is_some() {
            success_count += 1;
        }
        if value.try_get_i64().is_some() {
            success_count += 1;
        }
        if value.try_get_f64().is_some() {
            success_count += 1;
        }
        #[cfg(feature = "decimal")]
        if value.try_get_decimal().is_some() {
            success_count += 1;
        }

        // Exactly one accessor should succeed (the matching type)
        assert_eq!(
            success_count, 1,
            "Expected exactly one accessor to succeed for {value:?}, but {success_count} succeeded"
        );
    }

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        let dec = Number::from(Decimal::new(42, 0));

        // Only try_get_decimal should succeed
        assert!(dec.try_get_decimal().is_some());
        assert!(dec.try_get_u64().is_none());
        assert!(dec.try_get_i64().is_none());
        assert!(dec.try_get_f64().is_none());
    }
}

/// Tests as_i128 method comprehensively
#[test]
fn test_as_i128_comprehensive() {
    // Test all integer types convert correctly
    assert_eq!(Number::from(4294967295_u64).as_i128(), Some(4294967295));
    assert_eq!(Number::from(-2147483648_i64).as_i128(), Some(-2147483648));
    assert_eq!(
        Number::from(18446744073709551615_u64).as_i128(),
        Some(18446744073709551615)
    );
    assert_eq!(
        Number::from(-9223372036854775808_i64).as_i128(),
        Some(-9223372036854775808)
    );

    // Test floats that are integers
    assert_eq!(num!(42.0).as_i128(), Some(42));
    assert_eq!(num!(-1000.0).as_i128(), Some(-1000));
    assert_eq!(num!(1e10).as_i128(), Some(10_000_000_000));

    // Test floats that are not integers
    assert_eq!(Number::from(std::f64::consts::PI).as_i128(), None);
    assert_eq!(Number::from(std::f64::consts::E).as_i128(), None);
    assert_eq!(num!(0.5).as_i128(), None);
    assert_eq!(num!(-0.1).as_i128(), None);

    // Test special float values
    assert_eq!(num!(f64::NAN).as_i128(), None);
    assert_eq!(num!(f64::INFINITY).as_i128(), None);
    assert_eq!(num!(f64::NEG_INFINITY).as_i128(), None);

    // Test floats that exceed i128 range
    assert_eq!(num!(1e40).as_i128(), None); // Too large
    assert_eq!(num!(-1e40).as_i128(), None); // Too small

    // Test edge cases at i128 boundaries
    // i128::MAX and MIN lose precision when converted to f64
    // We test that values clearly in range work
    assert_eq!(
        num!(9.223372036854776e18).as_i128(),
        Some(9223372036854775808)
    );

    #[cfg(feature = "decimal")]
    {
        // Arc import removed as it's no longer needed"

        use rust_decimal::Decimal;

        // Test decimal integers
        assert_eq!(Number::from(Decimal::new(42, 0)).as_i128(), Some(42));
        assert_eq!(Number::from(Decimal::new(-1000, 0)).as_i128(), Some(-1000));

        // Test decimal non-integers
        assert_eq!(Number::from(Decimal::new(314, 2)).as_i128(), None); // 3.14
        assert_eq!(Number::from(Decimal::new(1, 1)).as_i128(), None); // 0.1
    }
}

/// Tests type predicates (is_integer, is_float)
#[test]
fn test_type_predicates() {
    // Test is_integer
    assert!(Number::from(70000_u64).is_integer());
    assert!(Number::from(-70000_i64).is_integer());
    assert!(Number::from(1_000_000_u64).is_integer());
    assert!(Number::from(-1_000_000_i64).is_integer());
    assert!(num!(42.0).is_integer());
    assert!(num!(-100.0).is_integer());
    assert!(!Number::from(std::f64::consts::PI).is_integer());
    assert!(!Number::from(std::f64::consts::E).is_integer());
    assert!(!num!(0.5).is_integer());
    assert!(!num!(-0.1).is_integer());

    // Special float values are not integers
    assert!(!num!(f64::NAN).is_integer());
    assert!(!num!(f64::INFINITY).is_integer());
    assert!(!num!(f64::NEG_INFINITY).is_integer());

    // Test is_float
    assert!(!Number::from(42_u64).is_float());
    assert!(!Number::from(-42_i64).is_float());
    assert!(Number::from(std::f64::consts::PI).is_float());
    assert!(Number::from(std::f64::consts::E).is_float());

    #[cfg(feature = "decimal")]
    {
        // Arc import removed as it's no longer needed"

        use rust_decimal::Decimal;

        let int_dec = Number::from(Decimal::new(42, 0));
        let frac_dec = Number::from(Decimal::new(314, 2));

        assert!(int_dec.is_integer());
        assert!(!frac_dec.is_integer());
        assert!(int_dec.is_float());
        assert!(frac_dec.is_float());
    }
}

#[test]
fn test_boundary_accessor_methods() {
    // Test accessor methods with boundary values
    assert_eq!(
        Number::from(u32::MAX as u64).try_get_u64(),
        Some(u32::MAX as u64)
    );
    assert_eq!(
        Number::from(u32::MIN as u64).try_get_u64(),
        Some(u32::MIN as u64)
    );
    assert_eq!(
        Number::from(i32::MAX as i64).try_get_i64(),
        Some(i32::MAX as i64)
    );
    assert_eq!(
        Number::from(i32::MIN as i64).try_get_i64(),
        Some(i32::MIN as i64)
    );

    assert_eq!(Number::from(u64::MAX).try_get_u64(), Some(u64::MAX));
    assert_eq!(Number::from(u64::MIN).try_get_u64(), Some(u64::MIN));
    assert_eq!(Number::from(i64::MAX).try_get_i64(), Some(i64::MAX));
    assert_eq!(Number::from(i64::MIN).try_get_i64(), Some(i64::MIN));
}