numaxiom 0.0.2

Lightweight numeric marker traits for ranges/signs plus constants and ops; std by default, no_std optional.
Documentation
//! Const-friendly numeric traits for expressing ranges, signs, constants, and
//! basic operations at the type level. Designed to stay lightweight and usable
//! in `no_std` contexts.

#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::module_inception)]

mod non_negative;
pub use non_negative::NonNegative;

mod non_positive;
pub use non_positive::NonPositive;

mod any_sign;
pub use any_sign::AnySign;

mod positive;
pub use positive::Positive;

mod negative;
pub use negative::Negative;

mod integer;
pub use integer::*;

mod min_max;
pub use min_max::*;

mod abs;
pub use abs::Abs;

mod signum;
pub use signum::Signum;

mod reciprocal;
pub use reciprocal::Reciprocal;

mod sqrt;
pub use sqrt::Sqrt;

mod exponent;
pub use exponent::*;

mod log;
pub use log::*;

mod rounding;
pub use rounding::*;

mod trig;
pub use trig::*;

mod nan;
pub use nan::*;

mod infinity;
pub use infinity::*;

mod epsilon;
pub use epsilon::*;

mod clamp;
pub use clamp::Clamp;

mod saturating_ops;
pub use saturating_ops::*;

mod checked_ops;
pub use checked_ops::*;

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

    #[test]
    fn one_trait_returns_one() {
        assert_eq!(<i32 as One>::one(), 1);
        assert_eq!(<f64 as One>::one(), 1.0);
    }

    #[test]
    fn two_trait_returns_two() {
        assert_eq!(<i32 as Two>::two(), 2);
        assert_eq!(<f64 as Two>::two(), 2.0);
    }

    #[test]
    fn abs_trait_matches_std() {
        assert_eq!(<i32 as Abs>::abs(-5), 5);
        assert_eq!(<u32 as Abs>::abs(7), 7);
        assert_eq!(<f32 as Abs>::abs(-1.5), 1.5);
    }

    #[test]
    fn signum_trait_matches_std() {
        assert_eq!(<i32 as Signum>::signum(10), 1);
        assert_eq!(<i32 as Signum>::signum(-10), -1);
        assert_eq!(<f32 as Signum>::signum(-0.25), -1.0);
        assert_eq!(<f32 as Signum>::signum(0.25), 1.0);
        assert_eq!(<f32 as Signum>::signum(0.0), 1.0);
    }

    #[test]
    fn epsilon_trait_matches_consts() {
        assert_eq!(<f32 as Epsilon>::epsilon(), f32::EPSILON);
        assert_eq!(<f64 as Epsilon>::epsilon(), f64::EPSILON);
    }

    #[test]
    fn epsilon_markers_hold() {
        fn assert_non_epsilon<T: NonEpsilon>() {}
        fn assert_has_epsilon<T: HasEpsilon>() {}
        assert_non_epsilon::<i32>();
        assert_non_epsilon::<u64>();
        assert_has_epsilon::<f32>();
        assert_has_epsilon::<f64>();
    }

    #[test]
    fn positive_trait_applies_to_nonzero_unsigned() {
        fn assert_positive<T: Positive>() {}
        assert_positive::<core::num::NonZeroU8>();
        assert_positive::<core::num::NonZeroUsize>();
    }

    #[test]
    fn reciprocal_trait_inverts_floats() {
        assert!((<f32 as Reciprocal>::reciprocal(4.0) - 0.25).abs() < f32::EPSILON);
    }

    #[test]
    fn clamp_trait_limits_values() {
        #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
        struct Wrap(f32);
        impl Clamp for Wrap {
            fn clamp(self, min: Self, max: Self) -> Self {
                debug_assert!(min <= max, "invalid clamp bounds");
                if self < min {
                    min
                } else if self > max {
                    max
                } else {
                    self
                }
            }
        }

        let value = Wrap(5.0);
        let min = Wrap(0.0);
        let max = Wrap(3.0);

        assert_eq!(value.clamp(min, max), Wrap(3.0));
        assert_eq!(Wrap(-1.0).clamp(min, max), Wrap(0.0));
        assert_eq!(Wrap(2.0).clamp(min, max), Wrap(2.0));
    }

    #[test]
    fn saturating_add_clamps_unsigned_overflow() {
        let value = <u8 as SaturatingAdd>::saturating_add(250, 10);
        assert_eq!(value, u8::MAX);
    }

    #[test]
    fn saturating_sub_clamps_signed_underflow() {
        let value = <i32 as SaturatingSub>::saturating_sub(i32::MIN, 1);
        assert_eq!(value, i32::MIN);
    }

    #[test]
    fn saturating_mul_clamps_positive_overflow() {
        let value = <u8 as SaturatingMul>::saturating_mul(20, 20);
        assert_eq!(value, u8::MAX);
    }

    #[test]
    fn checked_sub_returns_none_on_underflow() {
        let value = <u8 as CheckedSub>::checked_sub(1, 2);
        assert_eq!(value, None);
        let ok = <u8 as CheckedSub>::checked_sub(5, 2);
        assert_eq!(ok, Some(3));
    }

    #[test]
    fn checked_add_returns_none_on_overflow() {
        let value = <u8 as CheckedAdd>::checked_add(u8::MAX, 1);
        assert_eq!(value, None);
    }

    #[test]
    fn checked_mul_returns_none_on_overflow() {
        let value = <u8 as CheckedMul>::checked_mul(20, 20);
        assert_eq!(value, None);
    }

    #[test]
    fn checked_div_returns_none_on_zero_division() {
        let value = <u8 as CheckedDiv>::checked_div(1, 0);
        assert_eq!(value, None);
        let ok = <u8 as CheckedDiv>::checked_div(8, 2);
        assert_eq!(ok, Some(4));
    }

    #[test]
    fn checked_rem_returns_none_on_zero_division() {
        let value = <u8 as CheckedRem>::checked_rem(1, 0);
        assert_eq!(value, None);
        let ok = <u8 as CheckedRem>::checked_rem(7, 4);
        assert_eq!(ok, Some(3));
    }

    #[test]
    fn checked_neg_returns_none_on_min_value() {
        let value = <i32 as CheckedNeg>::checked_neg(i32::MIN);
        assert_eq!(value, None);
        let ok = <i32 as CheckedNeg>::checked_neg(5);
        assert_eq!(ok, Some(-5));
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn sqrt_matches_std() {
        assert_eq!(<f32 as Sqrt>::sqrt(4.0), 2.0);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn pow_matches_std() {
        assert_eq!(<f32 as Pow>::pow(2.0, 3.0), 8.0);
        assert_eq!(<i32 as Pow<u32>>::pow(2, 3), 8);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn root_matches_std() {
        let result = <f32 as Root>::root(27.0, 3.0);
        assert!((result - 3.0).abs() < 1e-3);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn exp_matches_std() {
        assert!((<f32 as Exp>::exp(1.0) - core::f32::consts::E).abs() < 1e-3);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn log_matches_std() {
        let v = <f32 as Log>::ln(core::f32::consts::E);
        assert!((v - 1.0).abs() < 1e-3);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn log_variants_match_std() {
        let v2 = <f32 as Log2>::log2(8.0);
        assert!((v2 - 3.0).abs() < 1e-6);
        let v10 = <f32 as Log10>::log10(100.0);
        assert!((v10 - 2.0).abs() < 1e-6);
        let v_base = <f32 as LogBase>::log_base(27.0, 3.0);
        assert!((v_base - 3.0).abs() < 1e-6);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn cbrt_matches_std() {
        let v = <f64 as Cbrt>::cbrt(27.0);
        assert!((v - 3.0).abs() < 1e-9);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn hypot_matches_std() {
        let v = <f32 as Hypot>::hypot(3.0, 4.0);
        assert!((v - 5.0).abs() < 1e-6);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn rounding_matches_std() {
        assert_eq!(<f32 as Floor>::floor(1.9), 1.0);
        assert_eq!(<f32 as Ceil>::ceil(1.1), 2.0);
        assert_eq!(<f32 as Round>::round(1.5), 2.0);
        assert_eq!(<f32 as Trunc>::trunc(-1.9), -1.0);
    }

    #[cfg(any(feature = "std", feature = "libm"))]
    #[test]
    fn trig_matches_std() {
        let v = <f32 as Sin>::sin(core::f32::consts::FRAC_PI_2);
        assert!((v - 1.0).abs() < 1e-6);
        let v = <f32 as Cos>::cos(core::f32::consts::PI);
        assert!((v + 1.0).abs() < 1e-6);
        let v = <f32 as Tan>::tan(0.0);
        assert!(v.abs() < 1e-6);
        let v = <f32 as Asin>::asin(0.0);
        assert!(v.abs() < 1e-6);
        let v = <f32 as Acos>::acos(1.0);
        assert!(v.abs() < 1e-6);
        let v = <f32 as Atan>::atan(1.0);
        assert!((v - core::f32::consts::FRAC_PI_4).abs() < 1e-6);
        let v = <f32 as Atan2>::atan2(0.0, 1.0);
        assert!(v.abs() < 1e-6);
    }

    #[test]
    fn nan_markers_hold() {
        fn assert_non_nan<T: NonNan>() {}
        fn assert_has_nan<T: HasNan>() {}
        assert_non_nan::<i32>();
        assert_non_nan::<u64>();
        assert_has_nan::<f32>();
        assert_has_nan::<f64>();
        assert!(<f32 as Nan>::nan().is_nan());
    }

    #[test]
    fn infinity_markers_hold() {
        fn assert_non_inf<T: NonAnySignInfinity>() {}
        fn assert_non_pos_inf<T: NonPositiveInfinity>() {}
        fn assert_non_neg_inf<T: NonNegativeInfinity>() {}
        fn assert_pos_inf<T: HasPositiveInfinity>() {}
        fn assert_neg_inf<T: HasNegativeInfinity>() {}
        assert_non_inf::<i32>();
        assert_non_inf::<u64>();
        assert_non_inf::<f32>();
        assert_non_inf::<f64>();
        assert_non_pos_inf::<i32>();
        assert_non_pos_inf::<u64>();
        assert_non_neg_inf::<i32>();
        assert_non_neg_inf::<u64>();
        assert_pos_inf::<f32>();
        assert_pos_inf::<f64>();
        assert_neg_inf::<f32>();
        assert_neg_inf::<f64>();
        assert!(<f32 as PositiveInfinity>::positive_infinity().is_infinite());
        assert!(<f32 as NegativeInfinity>::negative_infinity().is_infinite());
    }
}