use crate::{
Vector, Scalar, Point, Radians, Float, Degrees, Matrix, SphericalPos, SphericalDir, Space, HcMatrix,
};
pub trait ApproxEq {
type Tolerance;
fn approx_eq_abs(self, other: Self, abs_tolerance: Self::Tolerance) -> bool;
fn approx_eq_ulps(self, other: Self, max_steps_apart: u32) -> bool;
fn approx_eq_rel(self, other: Self, rel_tolerance: Self::Tolerance) -> bool;
}
macro_rules! impl_for_float {
($ty:ident) => {
impl ApproxEq for $ty {
type Tolerance = Self;
fn approx_eq_abs(self, other: Self, abs_tolerance: Self::Tolerance) -> bool {
self == other || (self - other).abs() <= abs_tolerance
}
fn approx_eq_rel(self, other: Self, rel_tolerance: Self::Tolerance) -> bool {
if self.is_sign_positive() != other.is_sign_positive() {
return self == other;
}
let max = Self::max(self.abs(), other.abs());
Self::approx_eq_abs(self, other, rel_tolerance * max)
}
fn approx_eq_ulps(self, other: Self, max_steps_apart: u32) -> bool {
if self.is_nan() || other.is_nan() {
return false;
}
if self.is_sign_positive() != other.is_sign_positive() {
return self == other;
}
self.to_bits().abs_diff(other.to_bits()) <= max_steps_apart.into()
}
}
};
}
impl_for_float!(f32);
impl_for_float!(f64);
macro_rules! impl_for_vec_point {
($ty:ident) => {
impl<T, const N: usize, S: Space> ApproxEq for $ty<T, N, S>
where
T: ApproxEq<Tolerance = T> + Scalar,
{
type Tolerance = T;
fn approx_eq_abs(self, other: Self, abs_tolerance: Self::Tolerance) -> bool {
(0..N).all(|i| T::approx_eq_abs(self[i], other[i], abs_tolerance))
}
fn approx_eq_rel(self, other: Self, rel_tolerance: Self::Tolerance) -> bool {
(0..N).all(|i| T::approx_eq_rel(self[i], other[i], rel_tolerance))
}
fn approx_eq_ulps(self, other: Self, max_steps_apart: u32) -> bool {
(0..N).all(|i| T::approx_eq_ulps(self[i], other[i], max_steps_apart))
}
}
};
}
impl_for_vec_point!(Vector);
impl_for_vec_point!(Point);
macro_rules! impl_for_angles {
($ty:ident) => {
impl<T: ApproxEq<Tolerance = T> + Float> ApproxEq for $ty<T> {
type Tolerance = Self;
fn approx_eq_abs(self, other: Self, abs_tolerance: Self::Tolerance) -> bool {
T::approx_eq_abs(self.0, other.0, abs_tolerance.0)
}
fn approx_eq_rel(self, other: Self, rel_tolerance: Self::Tolerance) -> bool {
T::approx_eq_rel(self.0, other.0, rel_tolerance.0)
}
fn approx_eq_ulps(self, other: Self, max_steps_apart: u32) -> bool {
T::approx_eq_ulps(self.0, other.0, max_steps_apart)
}
}
};
}
impl_for_angles!(Radians);
impl_for_angles!(Degrees);
macro_rules! impl_for_mats {
($ty:ident) => {
impl<T, const C: usize, const R: usize, Src, Dst> ApproxEq for $ty<T, C, R, Src, Dst>
where
T: ApproxEq<Tolerance = T> + Scalar,
Src: Space,
Dst: Space,
{
type Tolerance = T;
fn approx_eq_abs(self, other: Self, abs_tolerance: Self::Tolerance) -> bool {
self.iter().zip(other.iter()).all(|(a, b)| T::approx_eq_abs(a, b, abs_tolerance))
}
fn approx_eq_rel(self, other: Self, rel_tolerance: Self::Tolerance) -> bool {
self.iter().zip(other.iter()).all(|(a, b)| T::approx_eq_rel(a, b, rel_tolerance))
}
fn approx_eq_ulps(self, other: Self, max_steps_apart: u32) -> bool {
self.iter().zip(other.iter()).all(|(a, b)| T::approx_eq_ulps(a, b, max_steps_apart))
}
}
}
}
impl_for_mats!(Matrix);
impl_for_mats!(HcMatrix);
impl<T: ApproxEq<Tolerance = T> + Float, S: Space> ApproxEq for SphericalPos<T, S> {
type Tolerance = T;
fn approx_eq_abs(self, other: Self, abs_tolerance: Self::Tolerance) -> bool {
let c = |a, b| T::approx_eq_abs(a, b, abs_tolerance);
c(self.theta.0, other.theta.0) && c(self.phi.0, other.phi.0) && c(self.r, other.r)
}
fn approx_eq_rel(self, other: Self, rel_tolerance: Self::Tolerance) -> bool {
let c = |a, b| T::approx_eq_rel(a, b, rel_tolerance);
c(self.theta.0, other.theta.0) && c(self.phi.0, other.phi.0) && c(self.r, other.r)
}
fn approx_eq_ulps(self, other: Self, max_steps_apart: u32) -> bool {
let c = |a, b| T::approx_eq_ulps(a, b, max_steps_apart);
c(self.theta.0, other.theta.0) && c(self.phi.0, other.phi.0) && c(self.r, other.r)
}
}
impl<T: ApproxEq<Tolerance = T> + Float, S: Space> ApproxEq for SphericalDir<T, S> {
type Tolerance = T;
fn approx_eq_abs(self, other: Self, abs_tolerance: Self::Tolerance) -> bool {
let c = |a, b| T::approx_eq_abs(a, b, abs_tolerance);
c(self.theta.0, other.theta.0) && c(self.phi.0, other.phi.0)
}
fn approx_eq_rel(self, other: Self, rel_tolerance: Self::Tolerance) -> bool {
let c = |a, b| T::approx_eq_rel(a, b, rel_tolerance);
c(self.theta.0, other.theta.0) && c(self.phi.0, other.phi.0)
}
fn approx_eq_ulps(self, other: Self, max_steps_apart: u32) -> bool {
let c = |a, b| T::approx_eq_ulps(a, b, max_steps_apart);
c(self.theta.0, other.theta.0) && c(self.phi.0, other.phi.0)
}
}
#[macro_export]
macro_rules! assert_approx_eq {
($mode:ident <= $v:expr; $a:expr, $b:expr $(,)?) => {{
let a = $a;
let b = $b;
if !$crate::assert_approx_eq!(@imp $mode <= $v; a, b) {
panic!(
"assert_approx_eq failed!\n\
left: {:#?}\n\
right: {:#?}\n",
a,
b,
);
}
}};
(@imp abs <= $v:expr; $a:ident, $b:ident) => {
$crate::ApproxEq::approx_eq_abs($a, $b, $v)
};
(@imp rel <= $v:expr; $a:ident, $b:ident) => {
$crate::ApproxEq::approx_eq_rel($a, $b, $v)
};
(@imp ulps <= $v:expr; $a:ident, $b:ident) => {
$crate::ApproxEq::approx_eq_ulps($a, $b, $v)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn special_values() {
assert!(ApproxEq::approx_eq_abs(0.0, -0.0, 0.0));
assert!(ApproxEq::approx_eq_rel(0.0, -0.0, 0.0));
assert!(ApproxEq::approx_eq_ulps(0.0, -0.0, 0));
assert!(!ApproxEq::approx_eq_abs(f32::NEG_INFINITY, f32::INFINITY, 100.0));
assert!(!ApproxEq::approx_eq_rel(f32::NEG_INFINITY, f32::INFINITY, 1.0));
assert!(!ApproxEq::approx_eq_ulps(f32::NEG_INFINITY, f32::INFINITY, 10));
assert!(!ApproxEq::approx_eq_abs(3.14, f32::NAN, 100.0));
assert!(!ApproxEq::approx_eq_rel(3.14, f32::NAN, 1.0));
assert!(!ApproxEq::approx_eq_ulps(3.14, f32::NAN, 10));
assert!(!ApproxEq::approx_eq_abs(f32::NAN, 3.14, 100.0));
assert!(!ApproxEq::approx_eq_rel(f32::NAN, 3.14, 1.0));
assert!(!ApproxEq::approx_eq_ulps(f32::NAN, 3.14, 10));
}
#[test]
fn floats() {
assert!(!ApproxEq::approx_eq_abs(0.6000000000000001, 0.6, 0.0));
assert!( ApproxEq::approx_eq_abs(0.6000000000000001, 0.6, 0.0000000000000002));
assert!(!ApproxEq::approx_eq_rel(0.6000000000000001, 0.6, 0.0));
assert!( ApproxEq::approx_eq_rel(0.6000000000000001, 0.6, f64::EPSILON));
assert!(!ApproxEq::approx_eq_ulps(0.6000000000000001, 0.6, 0));
assert!( ApproxEq::approx_eq_ulps(0.6000000000000001, 0.6, 1));
}
}