#[macro_export]
macro_rules! assert_fp_eq {
($(@$method:tt,)? $a:expr, $b:expr) => {
assert_fp_eq!($(@$method,)? $a, $b, 1e-8); };
($(@relative,)? $a:expr, $b:expr, $epsilon:expr) => {
let (eq, vals) = $crate::math::NearlyEqual::nearly_equal(
&$a,
&$b,
&$crate::math::NearlyEqualStrategy::Relative { eps: $epsilon }
);
if !eq {
if let Some((a, b)) = vals {
panic!("assertion failed: `(found ≈ expected)`\n left:\t`{:?}`,\n right:\t`{:?}`,\n eps:\t`{}`,\n\n Caused by the comparison of:\n left:\t`{:?}`,\n right:\t`{:?}`", $a, $b, $epsilon, a, b)
} else {
panic!("assertion failed: `(found ≈ expected)`\n left:\t`{:?}`,\n right:\t`{:?}`,\n eps:\t`{}`", $a, $b, $epsilon)
}
}
};
(@absolute, $a:expr, $b:expr, $epsilon:expr) => {
let (eq, vals) = $crate::math::NearlyEqual::nearly_equal(
&$a,
&$b,
&$crate::math::NearlyEqualStrategy::Absolute { eps: $epsilon }
);
if !eq {
if let Some((a, b)) = vals {
panic!("assertion failed: `(found ≈ expected)`\n left:\t`{:?}`,\n right:\t`{:?}`,\n eps:\t`{}`,\n\n Caused by the comparison of:\n left:\t`{:?}`,\n right:\t`{:?}`", $a, $b, $epsilon, a, b)
} else {
panic!("assertion failed: `(found ≈ expected)`\n left:\t`{:?}`,\n right:\t`{:?}`,\n eps:\t`{}`", $a, $b, $epsilon)
}
}
};
(@custom, $a:expr, $b:expr, $closure:expr) => {
let (eq, vals) = $crate::math::NearlyEqual::nearly_equal(&$a, &$b, &$closure);
if !eq {
if let Some((a, b)) = vals {
panic!("assertion failed: `(found ≈ expected)`\n left:\t`{:?}`,\n right:\t`{:?}`,\n\n Caused by the comparison of:\n left:\t`{:?}`,\n right:\t`{:?}`", $a, $b, a, b)
} else {
panic!("assertion failed: `(found ≈ expected)`\n left:\t`{:?}`,\n right:\t`{:?}`", $a, $b)
}
}
};
}
#[macro_export]
macro_rules! is_fp_eq {
($(@$method:tt,)? $a:expr, $b:expr) => {
is_fp_eq!($(@$method,)? $a, $b, 1e-8); };
($(@relative,)? $a:expr, $b:expr, $epsilon:expr) => {
$crate::math::NearlyEqual::nearly_equal(
&$a,
&$b,
&$crate::math::NearlyEqualStrategy::Relative { eps: $epsilon }
).0
};
(@absolute, $a:expr, $b:expr, $epsilon:expr) => {
$crate::math::NearlyEqual::nearly_equal(
&$a,
&$b,
&$crate::math::NearlyEqualStrategy::Absolute { eps: $epsilon }
).0
};
(@custom, $a:expr, $b:expr, $closure:expr) => {
$crate::math::NearlyEqual::nearly_equal(&$a, &$b, &$closure).0
};
}
#[doc(hidden)]
pub trait NearlyEqualMethod<T> {
fn nearly_equal_float(&self, a: T, b: T) -> bool;
}
#[doc(hidden)]
pub enum NearlyEqualStrategy<T> {
Relative { eps: T },
Absolute { eps: T },
}
impl<T: Float> NearlyEqualMethod<T> for NearlyEqualStrategy<T> {
fn nearly_equal_float(&self, a: T, b: T) -> bool {
match self {
NearlyEqualStrategy::Relative { eps } => {
let abs_a = a.abs();
let abs_b = b.abs();
let diff = (a - b).abs();
if a == b {
true
} else if a == T::ZERO || b == T::ZERO || (abs_a + abs_b < T::MIN_POSITIVE) {
diff < *eps * T::MIN_POSITIVE
} else {
diff / (abs_a + abs_b).min(T::MAX) < *eps
}
}
NearlyEqualStrategy::Absolute { eps } => a == b || (a - b).abs() < *eps,
}
}
}
impl<T: Float, F: Fn(T, T) -> bool> NearlyEqualMethod<T> for F {
fn nearly_equal_float(&self, a: T, b: T) -> bool {
self(a, b)
}
}
#[doc(hidden)]
pub trait NearlyEqual<T> {
fn nearly_equal<M: NearlyEqualMethod<T>>(&self, b: &Self, method: &M) -> (bool, Option<(T, T)>);
}
macro_rules! impl_float_nearly_equal {
{$($ty:ty),* } => {
$(
impl NearlyEqual<$ty> for $ty {
#[inline]
#[allow(clippy::float_cmp)]
fn nearly_equal<M: NearlyEqualMethod<$ty>>(&self, b: &Self, strategy: &M) -> (bool, Option<($ty, $ty)>) {
if strategy.nearly_equal_float(*self, *b) {
(true, None)
} else {
(false, Some((*self, *b)))
}
}
}
)*
}
}
impl_float_nearly_equal!(f32, f64);
impl<T, S: NearlyEqual<T>> NearlyEqual<T> for Option<S> {
#[inline]
fn nearly_equal<M: NearlyEqualMethod<T>>(&self, b: &Self, strategy: &M) -> (bool, Option<(T, T)>) {
match (self, b) {
(Some(x), Some(y)) => x.nearly_equal(y, strategy),
(None, None) => (true, None),
_ => (false, None),
}
}
}
impl<T: Copy, S: NearlyEqual<T>, const N: usize> NearlyEqual<T> for [S; N] {
#[inline]
fn nearly_equal<M: NearlyEqualMethod<T>>(&self, b: &Self, strategy: &M) -> (bool, Option<(T, T)>) {
for (eq, vals) in self.iter().zip(b).map(|(x, y)| x.nearly_equal(y, strategy)) {
if !eq {
return (false, vals);
}
}
(true, None)
}
}
impl<T: Copy, S: NearlyEqual<T>> NearlyEqual<T> for &[S] {
#[inline]
fn nearly_equal<M: NearlyEqualMethod<T>>(&self, b: &Self, strategy: &M) -> (bool, Option<(T, T)>) {
if self.len() == b.len() {
for (eq, vals) in self.iter().zip(b.iter()).map(|(x, y)| x.nearly_equal(y, strategy)) {
if !eq {
return (false, vals);
}
}
(true, None)
} else {
(false, None)
}
}
}
impl<T: Copy, S: NearlyEqual<T>> NearlyEqual<T> for Vec<S> {
#[inline]
fn nearly_equal<M: NearlyEqualMethod<T>>(&self, b: &Self, strategy: &M) -> (bool, Option<(T, T)>) {
self.as_slice().nearly_equal(&b.as_slice(), strategy)
}
}
use super::Float;