Merge pull request #61 from mikedilger/ulps

ApproxEq trait enhanced with ULPs method of specifying closeness:
This commit is contained in:
Sébastien Crozet 2015-01-04 10:17:54 +01:00
commit 0e2563a88f
11 changed files with 174 additions and 0 deletions

View File

@ -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(

View File

@ -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> {

View File

@ -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> {

View File

@ -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)
}
}
)
);

View File

@ -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))
}
}
)
);

View File

@ -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> {

View File

@ -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)
}
}
)
);

View File

@ -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> {

View File

@ -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))*
}
}
)
);

View File

@ -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.

View File

@ -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);
}