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
#![deny(rustdoc::broken_intra_doc_links)]

//! Maximum and minimum comparison operations.
//!
//! This module provides [`Max`] and [`Min`] traits for comparing values using
//! reference-based semantics, avoiding the ownership issues of [`Ord::max`] and [`Ord::min`].

use crate::core::policies::{Native64RawRealStrictFinitePolicy, validate_in_debug};
use duplicate::duplicate_item;

/// A trait for finding the maximum of two values.
///
/// This trait provides a `max` method that returns a reference to the greater of
/// two values. It is bounded by [`PartialOrd`], which allows it to handle types
/// with partial ordering.
///
/// # Implementations
///
/// - For types that already guarantee validity, like [`RealValidated`](crate::kernels::RealValidated),
///   the default implementation is sufficient and safe because `PartialOrd` is already correctly implemented.
/// - A specialized implementation is provided for `f64` to add validation checks in **debug builds**,
///   ensuring inputs are finite and normal, and panicking if they are not.
///
/// # Behavior with NaN
///
/// For `f64`, if either input is `NaN`, the comparison `self >= other`
/// is `false`, which means `other` will be returned. Note that in debug builds,
/// providing a `NaN` input will cause a panic due to the validation checks.
/// `RealValidated` types should not contain `NaN` values by contract.
pub trait Max: PartialOrd + Sized {
    /// Returns a reference to the maximum of `self` and `other`.
    ///
    /// # Examples
    ///
    /// ```
    /// use num_valid::functions::Max;
    ///
    /// assert_eq!(Max::max_by_ref(&5.0, &3.0), &5.0);
    /// assert_eq!(Max::max_by_ref(&-10.0, &-5.0), &-5.0);
    /// ```
    fn max_by_ref<'a>(&'a self, other: &'a Self) -> &'a Self {
        if self >= other { self } else { other }
    }

    /// Returns the maximum of `self` and `other`, consuming both values.
    ///
    /// # Examples
    ///
    /// ```
    /// use num_valid::functions::Max;
    ///
    /// assert_eq!(5.0f64.max_by_value(3.0), 5.0);
    /// assert_eq!((-10.0f64).max_by_value(-5.0), -5.0);
    /// ```
    fn max_by_value(self, other: Self) -> Self {
        if self >= other { self } else { other }
    }
}

/// A trait for finding the minimum of two values.
///
/// This trait provides a `min` method that returns a reference to the lesser of
/// two values. It is bounded by [`PartialOrd`].
///
/// # Implementations
///
/// - For types that already guarantee validity, like [`RealValidated`](crate::kernels::RealValidated),
///   the default implementation is sufficient and safe.
/// - A specialized implementation is provided for `f64` to add validation checks in **debug builds**.
///
/// # Behavior with NaN
///
/// For `f64`, if either input is `NaN`, the comparison `self <= other`
/// is `false`, which means `other` will be returned. Note that in debug builds,
/// providing a `NaN` input will cause a panic.
/// `RealValidated` types should not contain `NaN` values by contract.
pub trait Min: PartialOrd + Sized {
    /// Returns a reference to the minimum of `self` and `other`.
    ///
    /// # Examples
    ///
    /// ```
    /// use num_valid::functions::Min;
    ///
    /// assert_eq!(Min::min_by_ref(&5.0, &3.0), &3.0);
    /// assert_eq!(Min::min_by_ref(&-10.0, &-5.0), &-10.0);
    /// ```
    fn min_by_ref<'a>(&'a self, other: &'a Self) -> &'a Self {
        if self <= other { self } else { other }
    }

    /// Returns the minimum of `self` and `other`, consuming both values.
    ///
    /// # Examples
    ///
    /// ```
    /// use num_valid::functions::Min;
    ///
    /// assert_eq!(5.0f64.min_by_value(3.0), 3.0);
    /// assert_eq!((-10.0f64).min_by_value(-5.0), -10.0);
    /// ```
    fn min_by_value(self, other: Self) -> Self {
        if self <= other { self } else { other }
    }
}

//------------------------------------------------------------------------------
/// Specialized implementation of [`Max`] and [`Min`] for `f64
#[duplicate_item(
    trait_name func_by_ref  func_by_value  implementation;
    [Max]      [max_by_ref] [max_by_value] [if self >= other { self } else { other }];
    [Min]      [min_by_ref] [min_by_value] [if self <= other { self } else { other }];
)]
// Specialized implementation for f64 to add debug-only validation.
impl trait_name for f64 {
    fn func_by_ref<'a>(&'a self, other: &'a Self) -> &'a Self {
        // These calls are compiled out in release builds, incurring no performance cost.
        validate_in_debug::<Native64RawRealStrictFinitePolicy>(self, "left");
        validate_in_debug::<Native64RawRealStrictFinitePolicy>(other, "right");

        implementation
    }

    fn func_by_value(self, other: Self) -> Self {
        // These calls are compiled out in release builds, incurring no performance cost.
        validate_in_debug::<Native64RawRealStrictFinitePolicy>(&self, "left");
        validate_in_debug::<Native64RawRealStrictFinitePolicy>(&other, "right");

        implementation
    }
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;

    mod max {
        use super::*;

        mod native64 {
            use super::*;

            #[test]
            fn test_f64_max_valid() {
                let a = 3.0;
                let b = 5.0;
                let expected_result = 5.0;
                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
                assert_eq!(Max::max_by_value(a, b), expected_result);
            }

            #[test]
            fn test_f64_max_equal_values() {
                let a = 3.0;
                let b = 3.0;
                let expected_result = 3.0;
                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
                assert_eq!(Max::max_by_value(a, b), expected_result);
            }

            #[test]
            fn test_f64_max_negative_values() {
                let a = -3.0;
                let b = -5.0;
                let expected_result = -3.0;
                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
                assert_eq!(Max::max_by_value(a, b), expected_result);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(expected = "Debug validation of left value failed: Value is NaN")]
            fn test_f64_max_nan_value() {
                let a = f64::NAN;
                let b = 5.0;
                let _result = Max::max_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(expected = "Debug validation of right value failed: Value is NaN")]
            fn test_f64_max_nan_other() {
                let a = 3.0;
                let b = f64::NAN;
                let _result = Max::max_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(
                expected = "Debug validation of left value failed: Value is positive infinity"
            )]
            fn test_f64_max_infinite_value() {
                let a = f64::INFINITY;
                let b = 5.0;
                let _result = Max::max_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(
                expected = "Debug validation of right value failed: Value is positive infinity"
            )]
            fn test_f64_max_infinite_other() {
                let a = 3.0;
                let b = f64::INFINITY;
                let _result = Max::max_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(
                expected = "Debug validation of left value failed: Value is negative infinity"
            )]
            fn test_f64_max_neg_infinite_value() {
                let a = f64::NEG_INFINITY;
                let b = 5.0;
                let _result = Max::max_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(
                expected = "Debug validation of right value failed: Value is negative infinity"
            )]
            fn test_f64_max_neg_infinite_other() {
                let a = 3.0;
                let b = f64::NEG_INFINITY;
                let _result = Max::max_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(expected = "Debug validation of left value failed: Value is subnormal")]
            fn test_f64_max_subnormal_value() {
                let a = f64::MIN_POSITIVE / 2.0;
                let b = 5.0;
                let _result = Max::max_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(expected = "Debug validation of right value failed: Value is subnormal")]
            fn test_f64_max_subnormal_other() {
                let a = 3.0;
                let b = f64::MIN_POSITIVE / 2.0;
                let _result = Max::max_by_ref(&a, &b);
            }
        }

        mod native64_strict_finite {
            use try_create::TryNew;

            use super::*;
            use crate::backends::native64::validated::RealNative64StrictFinite;

            #[test]
            fn test_f64_max_validated() {
                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
                let b = RealNative64StrictFinite::try_new(5.0).unwrap();
                let expected_result = RealNative64StrictFinite::try_new(5.0).unwrap();
                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
                assert_eq!(Max::max_by_value(a, b), expected_result);
            }

            #[test]
            fn test_f64_max_validated_equal() {
                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
                let b = RealNative64StrictFinite::try_new(3.0).unwrap();
                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
                assert_eq!(Max::max_by_ref(&a, &b), &expected_result);
                assert_eq!(Max::max_by_value(a, b), expected_result);
            }
        }
    }

    mod min {
        use super::*;

        mod native64 {
            use super::*;

            #[test]
            fn test_f64_min_valid() {
                let a = 3.0;
                let b = 5.0;
                let expected_result = 3.0;
                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
                assert_eq!(Min::min_by_value(a, b), expected_result);
            }

            #[test]
            fn test_f64_min_equal_values() {
                let a = 3.0;
                let b = 3.0;
                let expected_result = 3.0;
                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
                assert_eq!(Min::min_by_value(a, b), expected_result);
            }

            #[test]
            fn test_f64_min_negative_values() {
                let a = -3.0;
                let b = -5.0;
                let expected_result = -5.0;
                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
                assert_eq!(Min::min_by_value(a, b), expected_result);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(expected = "Debug validation of left value failed: Value is NaN")]
            fn test_f64_min_nan_value() {
                let a = f64::NAN;
                let b = 5.0;
                let _result = Min::min_by_ref(&a, &b);
            }

            #[test]
            #[cfg(debug_assertions)]
            #[should_panic(expected = "Debug validation of right value failed: Value is NaN")]
            fn test_f64_min_nan_other() {
                let a = 3.0;
                let b = f64::NAN;
                let _result = Min::min_by_ref(&a, &b);
            }
        }

        mod native64_strict_finite {
            use try_create::TryNew;

            use super::*;
            use crate::backends::native64::validated::RealNative64StrictFinite;

            #[test]
            fn test_f64_min_validated() {
                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
                let b = RealNative64StrictFinite::try_new(5.0).unwrap();
                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
                assert_eq!(Min::min_by_value(a, b), expected_result);
            }

            #[test]
            fn test_f64_min_validated_equal() {
                let a = RealNative64StrictFinite::try_new(3.0).unwrap();
                let b = RealNative64StrictFinite::try_new(3.0).unwrap();
                let expected_result = RealNative64StrictFinite::try_new(3.0).unwrap();
                assert_eq!(Min::min_by_ref(&a, &b), &expected_result);
                assert_eq!(Min::min_by_value(a, b), expected_result);
            }
        }
    }
}
//------------------------------------------------------------------------------