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.
|
||||
#[macro_export]
|
||||
macro_rules! assert_approx_eq(
|
||||
|
@ -605,11 +605,22 @@ impl<N: ApproxEq<N>> ApproxEq<N> for DMat<N> {
|
||||
ApproxEq::approx_epsilon(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_ulps(_: Option<DMat<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &DMat<N>, epsilon: &N) -> bool {
|
||||
let zip = self.mij.iter().zip(other.mij.iter());
|
||||
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> {
|
||||
|
@ -300,11 +300,22 @@ macro_rules! dvec_impl(
|
||||
ApproxEq::approx_epsilon(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_ulps(_: Option<$dvec<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &$dvec<N>, epsilon: &N) -> bool {
|
||||
let zip = self.as_slice().iter().zip(other.as_slice().iter());
|
||||
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> {
|
||||
|
@ -316,11 +316,22 @@ macro_rules! approx_eq_impl(
|
||||
ApproxEq::approx_epsilon(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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.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>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &$t<N>, epsilon: &N) -> bool {
|
||||
let zip = self.iter().zip(other.iter());
|
||||
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>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_ulps(_: Option<UnitQuat<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, other: &UnitQuat<N>, eps: &N) -> bool {
|
||||
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> {
|
||||
|
@ -250,6 +250,11 @@ macro_rules! approx_eq_impl(
|
||||
ApproxEq::approx_epsilon(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &$t<N>) -> bool {
|
||||
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 {
|
||||
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>)
|
||||
}
|
||||
|
||||
fn approx_ulps(_: Option<vec::Vec0<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_eps(&self, _: &vec::Vec0<N>, _: &N) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq_ulps(&self, _: &vec::Vec0<N>, _: u32) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: One> One for vec::Vec0<N> {
|
||||
|
@ -609,6 +609,11 @@ macro_rules! approx_eq_impl(
|
||||
ApproxEq::approx_epsilon(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_ulps(_: Option<$t<N>>) -> u32 {
|
||||
ApproxEq::approx_ulps(None::<N>)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn approx_eq(&self, other: &$t<N>) -> bool {
|
||||
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.$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.
|
||||
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.
|
||||
#[inline]
|
||||
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 {
|
||||
::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 {
|
||||
@ -187,6 +212,23 @@ impl ApproxEq<f64> for f64 {
|
||||
fn approx_eq_eps(&self, other: &f64, approx_epsilon: &f64) -> bool {
|
||||
::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.
|
||||
|
@ -33,3 +33,36 @@ fn assert_approx_eq_eps_f32() {
|
||||
fn assert_approx_eq_eps_f64_fail() {
|
||||
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
Block a user