num-valid 0.3.3

A robust numerical library providing validated types for real and complex numbers to prevent common floating-point errors like NaN propagation. Features a generic, layered architecture with support for native f64 and optional arbitrary-precision arithmetic.
Documentation
//! Tests for examples in README.md to ensure they compile and run correctly

use num::One;
use num_valid::functions::{Abs, ComplexScalarConstructors, Conjugate, Exp, Ln, Reciprocal, Sqrt};
use num_valid::{
    RealScalar,
    backends::native64::validated::{ComplexNative64StrictFinite, RealNative64StrictFinite},
};
use try_create::TryNew;

#[test]
fn test_quick_example() -> Result<(), Box<dyn std::error::Error>> {
    // ✅ Valid value - compiles and runs safely
    let x = RealNative64StrictFinite::try_new(4.0)?;
    let sqrt_x = x.sqrt(); // Always safe - no NaN surprises!
    assert_eq!(*sqrt_x.as_ref(), 2.0);

    // ❌ Invalid value - caught immediately
    let bad = RealNative64StrictFinite::try_new(f64::NAN); // Returns Err
    assert!(bad.is_err());

    Ok(())
}

#[test]
fn test_basic_usage() -> Result<(), Box<dyn std::error::Error>> {
    // Create a validated number
    let x = RealNative64StrictFinite::try_new(4.0)?;

    // Panicking version - for known-valid inputs
    let sqrt_x = x.sqrt();
    assert_eq!(*sqrt_x.as_ref(), 2.0);

    // Fallible version - for runtime validation
    let neg = RealNative64StrictFinite::try_new(-4.0)?;
    match neg.try_sqrt() {
        Ok(_result) => panic!("Should have failed"),
        Err(_e) => {} // Expected error for sqrt of negative
    }

    Ok(())
}

#[test]
fn test_generic_functions() -> Result<(), Box<dyn std::error::Error>> {
    // Generic function works with any RealScalar type
    fn pythagorean_identity<T: RealScalar>(angle: T) -> T {
        let sin_x = angle.clone().sin();
        let cos_x = angle.cos();
        (sin_x.clone() * sin_x) + (cos_x.clone() * cos_x)
    }

    // Use with native f64 precision
    let angle = RealNative64StrictFinite::try_from_f64(0.5)?;
    let result = pythagorean_identity(angle);
    let one = RealNative64StrictFinite::one();

    // The result should be very close to 1.0 (sin²x + cos²x = 1)
    let diff = (result - one).abs();
    assert!(*diff.as_ref() < 1e-15);

    Ok(())
}

#[cfg(feature = "rug")]
#[test]
fn test_arbitrary_precision() -> Result<(), Box<dyn std::error::Error>> {
    use num_valid::RealRugStrictFinite;

    fn pythagorean_identity<T: RealScalar>(angle: T) -> T {
        let sin_x = angle.clone().sin();
        let cos_x = angle.cos();
        (sin_x.clone() * sin_x) + (cos_x.clone() * cos_x)
    }

    // Use with 200-bit precision (≈60 decimal digits)
    type HighPrecision = RealRugStrictFinite<200>;

    let angle = HighPrecision::try_from_f64(0.5)?;
    let result = pythagorean_identity(angle);
    let one = HighPrecision::one();

    // More accurate than f64!
    let diff = (result - one).abs();
    let threshold = HighPrecision::try_from_f64(1e-50)?;
    assert!(*diff.as_ref() < *threshold.as_ref());

    Ok(())
}

#[test]
fn test_complex_numbers() {
    // Create complex number: 3 + 4i
    let re = RealNative64StrictFinite::from_f64(3.0);
    let im = RealNative64StrictFinite::from_f64(4.0);
    let z = ComplexNative64StrictFinite::new_complex(re, im);

    // Complex operations
    let magnitude = z.abs(); // |3 + 4i| = 5.0
    assert_eq!(*magnitude.as_ref(), 5.0);

    let _conjugate = z.conjugate(); // 3 - 4i
    let _reciprocal = z.reciprocal(); // 1/z

    // Complex math functions
    let _exp_z = z.exp();
    let _log_z = z.ln();
    let _sqrt_z = z.sqrt();
}

#[test]
fn test_zero_copy_conversions() {
    use bytemuck::checked::try_from_bytes;

    // From bytes to validated type
    let bytes = 42.0_f64.to_ne_bytes();
    let validated: &RealNative64StrictFinite = try_from_bytes(&bytes).unwrap();
    assert_eq!(*validated.as_ref(), 42.0);

    // Invalid values automatically rejected
    let nan_bytes = f64::NAN.to_ne_bytes();
    assert!(try_from_bytes::<RealNative64StrictFinite>(&nan_bytes).is_err());
}

#[test]
fn test_error_handling_propagation() -> Result<(), Box<dyn std::error::Error>> {
    use num_valid::functions::Sqrt;

    // Pattern 1: Propagate errors with ?
    fn compute_radius(area: f64) -> Result<RealNative64StrictFinite, Box<dyn std::error::Error>> {
        let validated_area = RealNative64StrictFinite::try_from_f64(area)?;
        let pi = RealNative64StrictFinite::try_from_f64(std::f64::consts::PI)?;
        let radius_squared = validated_area / pi;
        Ok(radius_squared.try_sqrt()?)
    }

    // Test with valid input
    let radius = compute_radius(std::f64::consts::PI * 4.0)?; // Area of circle with radius 2
    let expected = RealNative64StrictFinite::try_from_f64(2.0)?;
    let diff = (radius - expected).abs();
    assert!(*diff.as_ref() < 1e-10);

    // Test with invalid input (negative area)
    let result = compute_radius(-1.0);
    assert!(result.is_err());

    Ok(())
}

#[test]
fn test_error_handling_match_variants() {
    use num_valid::functions::{Sqrt, SqrtRealErrors, SqrtRealInputErrors};

    // Pattern 2: Match on specific error variants
    fn sqrt_with_fallback(x: RealNative64StrictFinite) -> RealNative64StrictFinite {
        match x.try_sqrt() {
            Ok(result) => result,
            Err(SqrtRealErrors::Input {
                source: SqrtRealInputErrors::NegativeValue { .. },
            }) => {
                // Handle negative input: return sqrt(|x|)
                (-x).sqrt()
            }
            Err(e) => panic!("Unexpected error: {e}"),
        }
    }

    // Test with positive value
    let x = RealNative64StrictFinite::from_f64(4.0);
    let result = sqrt_with_fallback(x);
    assert_eq!(*result.as_ref(), 2.0);

    // Test with negative value - should return sqrt(|x|)
    let neg_x = RealNative64StrictFinite::from_f64(-9.0);
    let result = sqrt_with_fallback(neg_x);
    assert_eq!(*result.as_ref(), 3.0);
}