un_algebra 0.9.0

Simple implementations of selected abstract algebraic structures--including groups, rings, and fields. Intended for self-study of abstract algebra concepts and not for production use.
//!
//! Numeric equality tests.
//!
//! Traits and implementations of _numeric_ equality--that is, equality
//! with an "epsilon" or error term. Usually, this means equality of
//! floating point values.
//!
use crate::helpers::*;


///
/// Numeric equality predicates.
///
pub trait NumEq: PartialEq {

  /// Epsilon comparison type.
  type Eps;


  /// Are two values _numerically_ equal, within `eps`?
  fn num_eq(&self, other: &Self, _eps: &Self::Eps) -> bool {
    self == other
  }


  /// Are two values _numerically_ unequal, within `eps`?
  fn num_ne(&self, other: &Self, eps: &Self::Eps) -> bool {
    !self.num_eq(other, eps)
  }
}


///
/// Define `NumEq` implementations for floating point types.
///
/// Reference:
/// <https://floating-point-gui.de/errors/comparison/>
///
macro_rules! float_num_eq {
  ($type:ty, $min:expr) => {
    impl NumEq for $type {

      /// Epsilon type is the same floating point type.
      type Eps = Self;


      /// Equality within _eps_ error bounds.
      fn num_eq(&self, other: &Self, eps: &$type) -> bool {
        let delta = (*self - *other).abs();

        // Check for simple equality first.
        if self == other {
          return true
        };

        // Comparisons with zero use an absolute error comparison.
        if *self == 0.0 || *other == 0.0 {
          return delta < *eps
        }

        // Very small values use an absolute error comparison.
        if self.abs() + other.abs() <= $min {
          return delta < *eps
        }

        // Otherwise we use a relative error comparison.
        delta / (self.abs() + other.abs()) < *eps
      }


      /// Inequality within _eps_ error bounds.
      fn num_ne(&self, other: &Self, eps: &$type) -> bool {
        !self.num_eq(other, eps)
      }
    }
  };
}


// 32 and 64 bit IEEE floating point equality.
float_num_eq! {f32, std::f32::MIN_POSITIVE}
float_num_eq! {f64, std::f64::MIN_POSITIVE}


///
/// Define `NumEq` implementations for homogeneous tuple types (T,), (T,
/// T), etc.
///
macro_rules! tuple_num_equal {
  ($tuple:ty) => {
    impl<T: NumEq> NumEq for $tuple {

      /// Epsilon type is just T's epsilon type.
      type Eps = T::Eps;


      /// Numeric equality is by matching element.
      fn num_eq(&self, other: &Self, eps: &Self::Eps) -> bool {
        self.all_with(other, &|x, y| x.num_eq(y, eps))
      }


      /// Numeric inequality is by matching element.
      fn num_ne(&self, other: &Self, eps: &Self::Eps) -> bool {
        self.any_with(other, &|x, y| x.num_ne(y, eps))
      }
    }
  };

  ($tuple:ty, $($others:ty),+) => {
    tuple_num_equal! {$tuple}
    tuple_num_equal! {$($others),+}
  };
}


// Tuple numeric ordering implementations.
tuple_num_equal! {(T, ), (T, T), (T, T, T), (T, T, T, T)}


///
/// Define `NumEq` implementations for arrays. This macro may not be
/// needed if Rust had _const_ _generics_.
///
macro_rules! array_num_eq {
  ($size:expr) => {
    impl<T: NumEq + Copy> NumEq for [T; $size] {

      /// Epsilon type is the element epsilon type.
      type Eps = T::Eps;


      /// Equality is by matching numeric equality.
      fn num_eq(&self, other: &Self, eps: &Self::Eps) -> bool {
        self.all_with(other, &|x, y| x.num_eq(y, eps))
      }


      /// Inequality is by matching numeric inequality.
      fn num_ne(&self, other: &Self, eps: &Self::Eps) -> bool {
        self.any_with(other, &|x, y| x.num_ne(y, eps))
      }
    }
  };

  ($size:expr, $($others:expr),+) => {
    array_num_eq! {$size}
    array_num_eq! {$($others),+}
  };
}


// Array numeric equality types.
array_num_eq! {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
}