uninum 0.1.1

A robust, ergonomic unified number type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
Documentation
//! Special Value Tests
//!
//! This module contains comprehensive tests for special value predicates:
//! - is_nan, is_infinite, is_finite, is_normal
//! - is_pos_inf, is_neg_inf
//! - Special value behavior and edge cases

use uninum::{Number, num};

#[test]
fn test_float64_trait_implementations() {
    use std::cmp::Ordering;
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};
    use uninum::Float64;

    let default = Float64::default();
    assert_eq!(*default, 0.0);

    let from_inner = Float64::from(3.5);
    let back: f64 = from_inner.into();
    assert_eq!(back, 3.5);

    // NaN comparisons collapse to equality.
    let nan_a = Float64(f64::NAN);
    let nan_b = Float64(f64::NAN);
    assert_eq!(nan_a, nan_b);
    assert_eq!(nan_a.cmp(&nan_b), Ordering::Equal);

    // Signed zeros compare and hash identically.
    let pos_zero = Float64(0.0);
    let neg_zero = Float64(-0.0);
    assert_eq!(pos_zero, neg_zero);
    assert_eq!(pos_zero.cmp(&neg_zero), Ordering::Equal);

    let mut hasher_a = DefaultHasher::new();
    let mut hasher_b = DefaultHasher::new();
    pos_zero.hash(&mut hasher_a);
    neg_zero.hash(&mut hasher_b);
    assert_eq!(hasher_a.finish(), hasher_b.finish());

    // NaN hashing uses canonical value.
    let mut hasher_nan_a = DefaultHasher::new();
    let mut hasher_nan_b = DefaultHasher::new();
    nan_a.hash(&mut hasher_nan_a);
    nan_b.hash(&mut hasher_nan_b);
    assert_eq!(hasher_nan_a.finish(), hasher_nan_b.finish());

    // Ordering places NaN above finite numbers.
    let finite = Float64(42.0);
    assert_eq!(nan_a.cmp(&finite), Ordering::Greater);
    assert_eq!(finite.cmp(&nan_a), Ordering::Less);

    // Display delegates to inner value.
    assert_eq!(format!("{from_inner}"), "3.5");

    // Deref exposes the inner f64 reference.
    assert_eq!(*from_inner, 3.5);
}

#[test]
fn test_is_nan_comprehensive() {
    // Test is_nan - only F64 can be NaN
    assert!(num!(f64::NAN).is_nan());
    assert!(Number::from(f64::NAN).is_nan());

    // Different ways to create NaN
    assert!(Number::from(f64::INFINITY - f64::INFINITY).is_nan());
    assert!(Number::from(f64::INFINITY * 0.0).is_nan());
    assert!(Number::from((-1.0_f64).sqrt()).is_nan());

    // All other types return false
    assert!(!Number::from(0u64).is_nan());
    assert!(!Number::from(u64::MAX).is_nan());
    assert!(!Number::from(i64::MIN).is_nan());
    assert!(!Number::from(i64::MAX).is_nan());

    // Finite float values are not NaN
    assert!(!num!(0.0).is_nan());
    assert!(!num!(-0.0).is_nan());
    assert!(!num!(f64::MAX).is_nan());
    assert!(!num!(f64::MIN).is_nan());
    assert!(!num!(f64::MIN_POSITIVE).is_nan());
    assert!(!Number::from(-f64::MIN_POSITIVE).is_nan());

    // Infinite values are not NaN
    assert!(!num!(f64::INFINITY).is_nan());
    assert!(!num!(f64::NEG_INFINITY).is_nan());

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        assert!(!Number::from(Decimal::ZERO).is_nan());
        assert!(!Number::from(Decimal::MAX).is_nan());
        assert!(!Number::from(Decimal::MIN).is_nan());
    }
}

#[test]
fn test_is_infinite_comprehensive() {
    // Test is_infinite - only F64 can be infinite
    assert!(num!(f64::INFINITY).is_infinite());
    assert!(num!(f64::NEG_INFINITY).is_infinite());

    // Different ways to create infinity
    assert!(num!(1.0 / 0.0).is_infinite());
    assert!(num!(-1.0 / 0.0).is_infinite());
    assert!(num!(f64::MAX * 2.0).is_infinite());

    // All finite values return false
    assert!(!Number::from(0u64).is_infinite());
    assert!(!Number::from(u64::MAX).is_infinite());
    assert!(!Number::from(i64::MIN).is_infinite());
    assert!(!Number::from(i64::MAX).is_infinite());

    // Finite float values are not infinite
    assert!(!num!(0.0).is_infinite());
    assert!(!num!(-0.0).is_infinite());
    assert!(!num!(f64::MAX).is_infinite());
    assert!(!num!(f64::MIN).is_infinite());
    assert!(!num!(f64::MIN_POSITIVE).is_infinite());
    assert!(!Number::from(-f64::MIN_POSITIVE).is_infinite());

    // NaN is not infinite
    assert!(!num!(f64::NAN).is_infinite());

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        assert!(!Number::from(Decimal::ZERO).is_infinite());
        assert!(!Number::from(Decimal::MAX).is_infinite());
        assert!(!Number::from(Decimal::MIN).is_infinite());
    }
}

#[test]
fn test_is_finite_comprehensive() {
    // All integers and decimals are finite
    assert!(Number::from(0u64).is_finite());
    assert!(Number::from(u64::MAX).is_finite());
    assert!(Number::from(i64::MIN).is_finite());
    assert!(Number::from(i64::MAX).is_finite());

    // Finite float values
    assert!(num!(0.0).is_finite());
    assert!(num!(-0.0).is_finite());
    assert!(num!(f64::MAX).is_finite());
    assert!(num!(f64::MIN).is_finite());
    assert!(num!(f64::MIN_POSITIVE).is_finite());
    assert!(Number::from(-f64::MIN_POSITIVE).is_finite());
    assert!(num!(f64::EPSILON).is_finite());
    assert!(Number::from(-f64::EPSILON).is_finite());

    // Infinite and NaN values are not finite
    assert!(!num!(f64::INFINITY).is_finite());
    assert!(!num!(f64::NEG_INFINITY).is_finite());
    assert!(!num!(f64::NAN).is_finite());

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        assert!(Number::from(Decimal::ZERO).is_finite());
        assert!(Number::from(Decimal::MAX).is_finite());
        assert!(Number::from(Decimal::MIN).is_finite());
    }
}

#[test]
fn test_is_pos_inf() {
    // Only positive infinity returns true
    assert!(num!(f64::INFINITY).is_pos_inf());
    assert!(num!(1.0 / 0.0).is_pos_inf());

    // Negative infinity returns false
    assert!(!num!(f64::NEG_INFINITY).is_pos_inf());
    assert!(!num!(-1.0 / 0.0).is_pos_inf());

    // All finite values return false
    assert!(!Number::from(0u64).is_pos_inf());
    assert!(!Number::from(u64::MAX).is_pos_inf());
    assert!(!Number::from(i64::MAX).is_pos_inf());
    assert!(!num!(f64::MAX).is_pos_inf());
    assert!(!num!(0.0).is_pos_inf());

    // NaN returns false
    assert!(!num!(f64::NAN).is_pos_inf());

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        assert!(!Number::from(Decimal::MAX).is_pos_inf());
    }
}

#[test]
fn test_is_neg_inf() {
    // Only negative infinity returns true
    assert!(num!(f64::NEG_INFINITY).is_neg_inf());
    assert!(num!(-1.0 / 0.0).is_neg_inf());

    // Positive infinity returns false
    assert!(!num!(f64::INFINITY).is_neg_inf());
    assert!(!num!(1.0 / 0.0).is_neg_inf());

    // All finite values return false
    assert!(!Number::from(0i64).is_neg_inf());
    assert!(!Number::from(i64::MIN).is_neg_inf());
    assert!(!num!(f64::MIN).is_neg_inf());
    assert!(!num!(-0.0).is_neg_inf());

    // NaN returns false
    assert!(!num!(f64::NAN).is_neg_inf());

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        assert!(!Number::from(Decimal::MIN).is_neg_inf());
    }
}

#[test]
fn test_special_value_operations() {
    let nan = num!(f64::NAN);
    let inf = num!(f64::INFINITY);
    let neg_inf = num!(f64::NEG_INFINITY);
    let normal = Number::from(42);

    // NaN operations always produce NaN
    assert!((nan.clone() + normal.clone()).is_nan());
    assert!((nan.clone() - normal.clone()).is_nan());
    assert!((nan.clone() * normal.clone()).is_nan());
    assert!((nan.clone() / normal.clone()).is_nan());

    // Infinity operations
    assert!((inf.clone() + normal.clone()).is_pos_inf());
    assert!((inf.clone() * Number::from(2)).is_pos_inf());
    assert!((inf.clone() * Number::from(-1)).is_neg_inf());

    // Special cases that produce NaN
    assert!((inf.clone() - inf.clone()).is_nan());
    assert!((inf.clone() + neg_inf.clone()).is_nan());
    assert!((inf * Number::from(0)).is_nan());
}

#[test]
fn test_edge_case_comparisons() {
    // Test NaN comparisons (in uninum, NaN == NaN unlike IEEE 754)
    let nan1 = num!(f64::NAN);
    let nan2 = num!(f64::NAN);
    assert_eq!(nan1, nan2); // In uninum, NaN == NaN

    // Test positive and negative zero
    let pos_zero = num!(0.0);
    let neg_zero = num!(-0.0);
    assert_eq!(pos_zero, neg_zero); // 0.0 == -0.0 for equality

    // Test infinities
    let inf1 = num!(f64::INFINITY);
    let inf2 = num!(f64::INFINITY);
    assert_eq!(inf1, inf2); // Infinity == Infinity

    let neg_inf1 = num!(f64::NEG_INFINITY);
    let neg_inf2 = num!(f64::NEG_INFINITY);
    assert_eq!(neg_inf1, neg_inf2); // -Infinity == -Infinity
}

#[test]
fn test_is_normal() {
    // Normal numbers (not zero, subnormal, infinite, or NaN)
    assert!(Number::from(1u64).is_normal());
    assert!(Number::from(-1i64).is_normal());
    assert!(num!(1.0).is_normal());
    assert!(num!(-1.0).is_normal());
    assert!(num!(f64::MAX).is_normal());
    assert!(num!(f64::MIN).is_normal());

    // Zero is not normal
    assert!(!Number::from(0u64).is_normal());
    assert!(!Number::from(0i64).is_normal());
    assert!(!num!(0.0).is_normal());
    assert!(!num!(-0.0).is_normal());

    // Subnormal values are not normal
    let subnormal = num!(f64::MIN_POSITIVE / 2.0);
    assert!(!subnormal.is_normal());

    // Special values are not normal
    assert!(!num!(f64::INFINITY).is_normal());
    assert!(!num!(f64::NEG_INFINITY).is_normal());
    assert!(!num!(f64::NAN).is_normal());

    #[cfg(feature = "decimal")]
    {
        use rust_decimal::Decimal;
        assert!(Number::from(Decimal::ONE).is_normal());
        assert!(!Number::from(Decimal::ZERO).is_normal());
    }
}