Merge pull request #61 from mikedilger/ulps
ApproxEq trait enhanced with ULPs method of specifying closeness:
This commit is contained in:
commit
0e2563a88f
|
@ -13,6 +13,21 @@ macro_rules! assert_approx_eq_eps(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Asserts approximate equality within a given tolerance of two values with the
|
||||||
|
/// `ApproxEq` trait, with tolerance specified in ULPs.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_approx_eq_ulps(
|
||||||
|
($given: expr, $expected: expr, $ulps: expr) => ({
|
||||||
|
let ulps = $ulps;
|
||||||
|
let (given_val, expected_val) = (&($given), &($expected));
|
||||||
|
if !ApproxEq::approx_eq_ulps(given_val, expected_val, ulps) {
|
||||||
|
panic!("assertion failed: `left ≈ right` (left: `{}`, right: `{}`, tolerance: `{}`)",
|
||||||
|
*given_val, *expected_val, ulps
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
/// Asserts approximate equality of two values with the `ApproxEq` trait.
|
/// Asserts approximate equality of two values with the `ApproxEq` trait.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_approx_eq(
|
macro_rules! assert_approx_eq(
|
||||||
|
|
|
@ -605,11 +605,22 @@ impl<N: ApproxEq<N>> ApproxEq<N> for DMat<N> {
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_ulps(_: Option<DMat<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq_eps(&self, other: &DMat<N>, epsilon: &N) -> bool {
|
fn approx_eq_eps(&self, other: &DMat<N>, epsilon: &N) -> bool {
|
||||||
let zip = self.mij.iter().zip(other.mij.iter());
|
let zip = self.mij.iter().zip(other.mij.iter());
|
||||||
zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon))
|
zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, other: &DMat<N>, ulps: u32) -> bool {
|
||||||
|
let zip = self.mij.iter().zip(other.mij.iter());
|
||||||
|
zip.all(|(a, b)| ApproxEq::approx_eq_ulps(a, b, ulps))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Show + Copy> Show for DMat<N> {
|
impl<N: Show + Copy> Show for DMat<N> {
|
||||||
|
|
|
@ -300,11 +300,22 @@ macro_rules! dvec_impl(
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_ulps(_: Option<$dvec<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq_eps(&self, other: &$dvec<N>, epsilon: &N) -> bool {
|
fn approx_eq_eps(&self, other: &$dvec<N>, epsilon: &N) -> bool {
|
||||||
let zip = self.as_slice().iter().zip(other.as_slice().iter());
|
let zip = self.as_slice().iter().zip(other.as_slice().iter());
|
||||||
zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon))
|
zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, other: &$dvec<N>, ulps: u32) -> bool {
|
||||||
|
let zip = self.as_slice().iter().zip(other.as_slice().iter());
|
||||||
|
zip.all(|(a, b)| ApproxEq::approx_eq_ulps(a, b, ulps))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Copy + Mul<N, N> + Zero> Mul<N, $dvec<N>> for $dvec<N> {
|
impl<N: Copy + Mul<N, N> + Zero> Mul<N, $dvec<N>> for $dvec<N> {
|
||||||
|
|
|
@ -316,11 +316,22 @@ macro_rules! approx_eq_impl(
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq_eps(&self, other: &$t<N>, epsilon: &N) -> bool {
|
fn approx_eq_eps(&self, other: &$t<N>, epsilon: &N) -> bool {
|
||||||
ApproxEq::approx_eq_eps(&self.rotation, &other.rotation, epsilon) &&
|
ApproxEq::approx_eq_eps(&self.rotation, &other.rotation, epsilon) &&
|
||||||
ApproxEq::approx_eq_eps(&self.translation, &other.translation, epsilon)
|
ApproxEq::approx_eq_eps(&self.translation, &other.translation, epsilon)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, other: &$t<N>, ulps: u32) -> bool {
|
||||||
|
ApproxEq::approx_eq_ulps(&self.rotation, &other.rotation, ulps) &&
|
||||||
|
ApproxEq::approx_eq_ulps(&self.translation, &other.translation, ulps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -622,11 +622,22 @@ macro_rules! approx_eq_impl(
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq_eps(&self, other: &$t<N>, epsilon: &N) -> bool {
|
fn approx_eq_eps(&self, other: &$t<N>, epsilon: &N) -> bool {
|
||||||
let zip = self.iter().zip(other.iter());
|
let zip = self.iter().zip(other.iter());
|
||||||
zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon))
|
zip.all(|(a, b)| ApproxEq::approx_eq_eps(a, b, epsilon))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, other: &$t<N>, ulps: u32) -> bool {
|
||||||
|
let zip = self.iter().zip(other.iter());
|
||||||
|
zip.all(|(a, b)| ApproxEq::approx_eq_ulps(a, b, ulps))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -291,10 +291,20 @@ impl<N: ApproxEq<N>> ApproxEq<N> for UnitQuat<N> {
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_ulps(_: Option<UnitQuat<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq_eps(&self, other: &UnitQuat<N>, eps: &N) -> bool {
|
fn approx_eq_eps(&self, other: &UnitQuat<N>, eps: &N) -> bool {
|
||||||
ApproxEq::approx_eq_eps(&self.q, &other.q, eps)
|
ApproxEq::approx_eq_eps(&self.q, &other.q, eps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, other: &UnitQuat<N>, ulps: u32) -> bool {
|
||||||
|
ApproxEq::approx_eq_ulps(&self.q, &other.q, ulps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: BaseFloat + ApproxEq<N>> Div<UnitQuat<N>, UnitQuat<N>> for UnitQuat<N> {
|
impl<N: BaseFloat + ApproxEq<N>> Div<UnitQuat<N>, UnitQuat<N>> for UnitQuat<N> {
|
||||||
|
|
|
@ -250,6 +250,11 @@ macro_rules! approx_eq_impl(
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq(&self, other: &$t<N>) -> bool {
|
fn approx_eq(&self, other: &$t<N>) -> bool {
|
||||||
ApproxEq::approx_eq(&self.submat, &other.submat)
|
ApproxEq::approx_eq(&self.submat, &other.submat)
|
||||||
|
@ -259,6 +264,11 @@ macro_rules! approx_eq_impl(
|
||||||
fn approx_eq_eps(&self, other: &$t<N>, epsilon: &N) -> bool {
|
fn approx_eq_eps(&self, other: &$t<N>, epsilon: &N) -> bool {
|
||||||
ApproxEq::approx_eq_eps(&self.submat, &other.submat, epsilon)
|
ApproxEq::approx_eq_eps(&self.submat, &other.submat, epsilon)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, other: &$t<N>, ulps: u32) -> bool {
|
||||||
|
ApproxEq::approx_eq_ulps(&self.submat, &other.submat, ulps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -205,10 +205,19 @@ impl<N: ApproxEq<N>> ApproxEq<N> for vec::Vec0<N> {
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn approx_ulps(_: Option<vec::Vec0<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq_eps(&self, _: &vec::Vec0<N>, _: &N) -> bool {
|
fn approx_eq_eps(&self, _: &vec::Vec0<N>, _: &N) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, _: &vec::Vec0<N>, _: u32) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: One> One for vec::Vec0<N> {
|
impl<N: One> One for vec::Vec0<N> {
|
||||||
|
|
|
@ -609,6 +609,11 @@ macro_rules! approx_eq_impl(
|
||||||
ApproxEq::approx_epsilon(None::<N>)
|
ApproxEq::approx_epsilon(None::<N>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||||
|
ApproxEq::approx_ulps(None::<N>)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq(&self, other: &$t<N>) -> bool {
|
fn approx_eq(&self, other: &$t<N>) -> bool {
|
||||||
ApproxEq::approx_eq(&self.$comp0, &other.$comp0)
|
ApproxEq::approx_eq(&self.$comp0, &other.$comp0)
|
||||||
|
@ -620,6 +625,12 @@ macro_rules! approx_eq_impl(
|
||||||
ApproxEq::approx_eq_eps(&self.$comp0, &other.$comp0, eps)
|
ApproxEq::approx_eq_eps(&self.$comp0, &other.$comp0, eps)
|
||||||
$(&& ApproxEq::approx_eq_eps(&self.$compN, &other.$compN, eps))*
|
$(&& ApproxEq::approx_eq_eps(&self.$compN, &other.$compN, eps))*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn approx_eq_ulps(&self, other: &$t<N>, ulps: u32) -> bool {
|
||||||
|
ApproxEq::approx_eq_ulps(&self.$comp0, &other.$comp0, ulps)
|
||||||
|
$(&& ApproxEq::approx_eq_ulps(&self.$compN, &other.$compN, ulps))*
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -158,6 +158,12 @@ pub trait ApproxEq<Eps>: Sized {
|
||||||
/// Tests approximate equality using a custom epsilon.
|
/// Tests approximate equality using a custom epsilon.
|
||||||
fn approx_eq_eps(&self, other: &Self, epsilon: &Eps) -> bool;
|
fn approx_eq_eps(&self, other: &Self, epsilon: &Eps) -> bool;
|
||||||
|
|
||||||
|
/// Default ULPs for approximation.
|
||||||
|
fn approx_ulps(unused_self: Option<Self>) -> u32;
|
||||||
|
|
||||||
|
/// Tests approximate equality using units in the last place (ULPs)
|
||||||
|
fn approx_eq_ulps(&self, other: &Self, ulps: u32) -> bool;
|
||||||
|
|
||||||
/// Tests approximate equality.
|
/// Tests approximate equality.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn approx_eq(&self, other: &Self) -> bool {
|
fn approx_eq(&self, other: &Self) -> bool {
|
||||||
|
@ -175,6 +181,25 @@ impl ApproxEq<f32> for f32 {
|
||||||
fn approx_eq_eps(&self, other: &f32, epsilon: &f32) -> bool {
|
fn approx_eq_eps(&self, other: &f32, epsilon: &f32) -> bool {
|
||||||
::abs(&(*self - *other)) < *epsilon
|
::abs(&(*self - *other)) < *epsilon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn approx_ulps(_: Option<f32>) -> u32 {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn approx_eq_ulps(&self, other: &f32, ulps: u32) -> bool {
|
||||||
|
// Handle -0 == +0
|
||||||
|
if *self == *other { return true; }
|
||||||
|
|
||||||
|
// Otherwise, differing signs should be not-equal, even if within ulps
|
||||||
|
if self.signum() != other.signum() { return false; }
|
||||||
|
|
||||||
|
// IEEE754 floats are in the same order as 2s complement ints
|
||||||
|
// so this trick (subtracting the ints) works.
|
||||||
|
let iself: i32 = unsafe { ::std::mem::transmute(*self) };
|
||||||
|
let iother: i32 = unsafe { ::std::mem::transmute(*other) };
|
||||||
|
|
||||||
|
(iself - iother).abs() < ulps as i32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApproxEq<f64> for f64 {
|
impl ApproxEq<f64> for f64 {
|
||||||
|
@ -187,6 +212,23 @@ impl ApproxEq<f64> for f64 {
|
||||||
fn approx_eq_eps(&self, other: &f64, approx_epsilon: &f64) -> bool {
|
fn approx_eq_eps(&self, other: &f64, approx_epsilon: &f64) -> bool {
|
||||||
::abs(&(*self - *other)) < *approx_epsilon
|
::abs(&(*self - *other)) < *approx_epsilon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn approx_ulps(_: Option<f64>) -> u32 {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn approx_eq_ulps(&self, other: &f64, ulps: u32) -> bool {
|
||||||
|
// Handle -0 == +0
|
||||||
|
if *self == *other { return true; }
|
||||||
|
|
||||||
|
// Otherwise, differing signs should be not-equal, even if within ulps
|
||||||
|
if self.signum() != other.signum() { return false; }
|
||||||
|
|
||||||
|
let iself: i64 = unsafe { ::std::mem::transmute(*self) };
|
||||||
|
let iother: i64 = unsafe { ::std::mem::transmute(*other) };
|
||||||
|
|
||||||
|
(iself - iother).abs() < ulps as i64
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait of objects having an absolute value.
|
/// Trait of objects having an absolute value.
|
||||||
|
|
|
@ -33,3 +33,36 @@ fn assert_approx_eq_eps_f32() {
|
||||||
fn assert_approx_eq_eps_f64_fail() {
|
fn assert_approx_eq_eps_f64_fail() {
|
||||||
assert_approx_eq_eps!(1.0f64, 1.1, 0.05);
|
assert_approx_eq_eps!(1.0f64, 1.1, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_approx_eq_ulps_f32() {
|
||||||
|
let x = 1000000_f32;
|
||||||
|
let y = 1000000.1_f32;
|
||||||
|
assert!(x != y);
|
||||||
|
assert_approx_eq_ulps!(x, y, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_fail]
|
||||||
|
fn assert_approx_eq_ulps_f32_fail() {
|
||||||
|
let x = 1000000_f32;
|
||||||
|
let y = 1000000.1_f32;
|
||||||
|
assert_approx_eq_ulps!(x, y, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_approx_eq_ulps_f64() {
|
||||||
|
let x = 1000000_f64;
|
||||||
|
let y = 1000000.0000000003_f64;
|
||||||
|
assert!(x != y);
|
||||||
|
assert_approx_eq_ulps!(x, y, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_fail]
|
||||||
|
fn assert_approx_eq_ulps_f64_fail() {
|
||||||
|
let x = 1000000_f64;
|
||||||
|
let y = 1000000.0000000003_f64;
|
||||||
|
assert!(x != y);
|
||||||
|
assert_approx_eq_ulps!(x, y, 3);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue