diffable 0.4.0

a differential geometry framework for rust
Documentation
//! Tolerance-aware floating-point real fields for tests and numerical geometry.
//!
//! [`R32`] and [`R64`] give ordinary floating-point arithmetic an approximate
//! equality suitable for the property-test macros, while
//! [`ExactCmp`](crate::traits::ExactCmp) remains available when an algorithm
//! needs the underlying strict order.

use core::ops::Neg;
use num_derive::{Float, FromPrimitive, Num, NumCast, NumOps, One, Signed, ToPrimitive, Zero};
use num_traits::{Euclid, Float, Inv};

use crate::traits::NonZero;

// This newtype over floating points identifies point which are close to each other,
// This allows the library to pretend that all numbers it uses are actually real numbers.
// This is only approximate, but the tests pass, so it's pretty good.

/// Defines a tolerance-comparison scalar newtype over `$inner`, with a
/// `PartialEq` that treats values within `$epsilon` (relative for large
/// magnitudes, absolute for small ones) as equal -- see
/// [`RealNum`](crate::traits::RealNum) for why this exists and what it means
/// for the resulting equality to be
/// reflexive and symmetric but not transitive.
///
/// Takes a doc string as its first argument (spliced onto the generated
/// struct via `#[doc = $doc]`) because a macro cannot otherwise attach a
/// distinct `///` comment per invocation -- a `///` written inside the
/// macro body would be identical for every instantiation, and a `///`
/// written before the invocation itself is simply discarded.
///
macro_rules! define_epsilon_metric {
    ($name:ident, $inner:ty, $epsilon:expr, $doc:expr) => {
        #[doc = $doc]
        #[derive(
            Debug,
            Clone,
            Copy,
            Float,
            Num,
            Signed,
            Zero,
            One,
            NumOps,
            NumCast,
            ToPrimitive,
            FromPrimitive,
        )]
        pub struct $name(pub $inner);

        impl PartialEq for $name {
            fn eq(&self, other: &Self) -> bool {
                let a = self.0;
                let b = other.0;
                // relative epsilon for large values, absolute epsilon for small values
                let diff = (a - b).abs();
                let magnitude = a.abs().max(b.abs());
                diff < $epsilon || diff < magnitude * $epsilon
            }
        }

        impl PartialOrd for $name {
            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
                if self == other {
                    // uses our epsilon PartialEq
                    Some(core::cmp::Ordering::Equal)
                } else {
                    self.0.partial_cmp(&other.0)
                }
            }
        }

        impl Euclid for $name {
            fn div_euclid(&self, v: &Self) -> Self {
                Self(<$inner as Euclid>::div_euclid(&self.0, &v.0))
            }

            fn rem_euclid(&self, v: &Self) -> Self {
                Self(<$inner as Euclid>::rem_euclid(&self.0, &v.0))
            }
        }

        impl Neg for $name {
            type Output = Self;

            fn neg(self) -> Self::Output {
                Self(-self.0)
            }
        }

        impl core::fmt::Display for $name {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                write!(f, "{}", self.0)
            }
        }

        impl Inv for NonZero<$name> {
            type Output = Self;

            fn inv(self) -> Self {
                Self::new_unchecked(self.0.recip())
            }
        }
    };
}

define_epsilon_metric!(
    R64,
    f64,
    1e-12,
    "A tolerance-comparison `f64`, treating values within `1e-12` as equal."
);
define_epsilon_metric!(
    R32,
    f32,
    1e-5,
    "A tolerance-comparison `f32`, treating values within `1e-5` as equal."
);