| //! Tools for ULP-based comparison of floating point numbers. |
| use std::mem; |
| use std::borrow::*; |
| |
| /// Represents the result of an ULP-based comparison between two floating point numbers. |
| #[derive(Debug, Copy, Clone, PartialEq)] |
| pub enum UlpComparisonResult |
| { |
| /// Signifies an exact match between two floating point numbers. |
| ExactMatch, |
| /// The difference in ULP between two floating point numbers. |
| Difference(u64), |
| /// The two floating point numbers have different signs, |
| /// and cannot be compared in a meaningful way. |
| IncompatibleSigns, |
| /// One or both of the two floating point numbers is a NaN, |
| /// in which case the ULP comparison is not meaningful. |
| Nan |
| } |
| |
| /// Floating point types for which two instances can be compared for Unit in the Last Place (ULP) difference. |
| /// |
| /// Implementing this trait enables the usage of the `ulp` comparator in |
| /// [assert_matrix_eq!](../macro.assert_matrix_eq!.html) for the given type. |
| /// |
| /// The definition here leverages the fact that for two adjacent floating point numbers, |
| /// their integer representations are also adjacent. |
| /// |
| /// A somewhat accessible (but not exhaustive) guide on the topic is available in the popular article |
| /// [Comparing Floating Point Numbers, 2012 Edition] |
| /// (https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/). |
| /// |
| /// Implementations for `f32` and `f64` are already available, and so users should not normally |
| /// need to implement this. In the case when a custom implementation is necessary, |
| /// please see the possible return values for [UlpComparisonResult](ulp/enum.UlpComparisonResult.html). |
| /// Otherwise, we can recommend to read the source code of the included `f32` and `f64` implementations. |
| pub trait Ulp { |
| /// Returns the difference between two floating point numbers, measured in ULP. |
| fn ulp_diff(a: &Self, b: &Self) -> UlpComparisonResult; |
| } |
| |
| macro_rules! impl_float_ulp { |
| ($ftype:ty, $itype:ty) => { |
| impl Ulp for $ftype { |
| fn ulp_diff(a: &Self, b: &Self) -> UlpComparisonResult { |
| if a == b { |
| UlpComparisonResult::ExactMatch |
| } else if a.is_nan() || b.is_nan() { |
| // ULP comparison does not make much sense for NaN |
| UlpComparisonResult::Nan |
| } else if a.is_sign_positive() != b.is_sign_positive() { |
| // ULP is not meaningful when the signs of the two numbers differ |
| UlpComparisonResult::IncompatibleSigns |
| } else { |
| // Otherwise, we compute the ULP diff as the difference of the signed integer representations |
| let a_int = unsafe { mem::transmute::<$ftype, $itype>(a.to_owned()) }; |
| let b_int = unsafe { mem::transmute::<$ftype, $itype>(b.to_owned()) }; |
| UlpComparisonResult::Difference((b_int - a_int).abs() as u64) |
| } |
| } |
| } |
| } |
| } |
| |
| impl_float_ulp!(f32, i32); |
| impl_float_ulp!(f64, i64); |
| |
| #[cfg(test)] |
| mod tests { |
| use super::Ulp; |
| use super::UlpComparisonResult; |
| use std::mem; |
| use std::{f32, f64}; |
| use quickcheck::TestResult; |
| |
| #[test] |
| fn plus_minus_zero_is_exact_match_f32() { |
| assert!(f32::ulp_diff(&0.0, &0.0) == UlpComparisonResult::ExactMatch); |
| assert!(f32::ulp_diff(&-0.0, &-0.0) == UlpComparisonResult::ExactMatch); |
| assert!(f32::ulp_diff(&0.0, &-0.0) == UlpComparisonResult::ExactMatch); |
| assert!(f32::ulp_diff(&-0.0, &0.0) == UlpComparisonResult::ExactMatch); |
| } |
| |
| #[test] |
| fn plus_minus_zero_is_exact_match_f64() { |
| assert!(f64::ulp_diff(&0.0, &0.0) == UlpComparisonResult::ExactMatch); |
| assert!(f64::ulp_diff(&-0.0, &-0.0) == UlpComparisonResult::ExactMatch); |
| assert!(f64::ulp_diff(&0.0, &-0.0) == UlpComparisonResult::ExactMatch); |
| assert!(f64::ulp_diff(&-0.0, &0.0) == UlpComparisonResult::ExactMatch); |
| } |
| |
| #[test] |
| fn f32_double_nan() { |
| assert!(f32::ulp_diff(&f32::NAN, &f32::NAN) == UlpComparisonResult::Nan); |
| } |
| |
| #[test] |
| fn f64_double_nan() { |
| assert!(f64::ulp_diff(&f64::NAN, &f64::NAN) == UlpComparisonResult::Nan); |
| } |
| |
| quickcheck! { |
| fn property_exact_match_for_finite_f32_self_comparison(x: f32) -> TestResult { |
| if x.is_finite() { |
| TestResult::from_bool(f32::ulp_diff(&x, &x) == UlpComparisonResult::ExactMatch) |
| } else { |
| TestResult::discard() |
| } |
| } |
| } |
| |
| quickcheck! { |
| fn property_exact_match_for_finite_f64_self_comparison(x: f64) -> TestResult { |
| if x.is_finite() { |
| TestResult::from_bool(f64::ulp_diff(&x, &x) == UlpComparisonResult::ExactMatch) |
| } else { |
| TestResult::discard() |
| } |
| } |
| } |
| |
| quickcheck! { |
| fn property_recovers_ulp_diff_when_f32_constructed_from_i32(a: i32, b: i32) -> TestResult { |
| if a == b { |
| // Ignore self-comparisons, as it makes the below test have more complicated logic, |
| // and moreover we test self-comparisons in another property. |
| return TestResult::discard(); |
| } |
| |
| let x = unsafe { mem::transmute::<i32, f32>(a) }; |
| let y = unsafe { mem::transmute::<i32, f32>(b) }; |
| |
| // Discard the input if it's non-finite or has different signs |
| if x.is_finite() && y.is_finite() && x.signum() == y.signum() { |
| TestResult::from_bool(f32::ulp_diff(&x, &y) == UlpComparisonResult::Difference((b - a).abs() as u64)) |
| } else { |
| TestResult::discard() |
| } |
| } |
| } |
| |
| quickcheck! { |
| fn property_recovers_ulp_diff_when_f64_constructed_from_i64(a: i64, b: i64) -> TestResult { |
| if a == b { |
| // Ignore self-comparisons, as it makes the below test have more complicated logic, |
| // and moreover we test self-comparisons in another property. |
| return TestResult::discard(); |
| } |
| |
| let x = unsafe { mem::transmute::<i64, f64>(a) }; |
| let y = unsafe { mem::transmute::<i64, f64>(b) }; |
| |
| // Discard the input if it's non-finite or has different signs |
| if x.is_finite() && y.is_finite() && x.signum() == y.signum() { |
| TestResult::from_bool(f64::ulp_diff(&x, &y) == UlpComparisonResult::Difference((b - a).abs() as u64)) |
| } else { |
| TestResult::discard() |
| } |
| } |
| } |
| |
| quickcheck! { |
| fn property_f32_incompatible_signs_yield_corresponding_enum_value(x: f32, y: f32) -> TestResult { |
| if x.signum() == y.signum() { |
| TestResult::discard() |
| } else if x.is_nan() || y.is_nan() { |
| TestResult::discard() |
| } else { |
| TestResult::from_bool(f32::ulp_diff(&x, &y) == UlpComparisonResult::IncompatibleSigns) |
| } |
| } |
| } |
| |
| quickcheck! { |
| fn property_f64_incompatible_signs_yield_corresponding_enum_value(x: f64, y: f64) -> TestResult { |
| if x.signum() == y.signum() { |
| TestResult::discard() |
| } else if x.is_nan() || y.is_nan() { |
| TestResult::discard() |
| } else { |
| TestResult::from_bool(f64::ulp_diff(&x, &y) == UlpComparisonResult::IncompatibleSigns) |
| } |
| } |
| } |
| |
| quickcheck! { |
| fn property_f32_nan_gives_nan_enum_value(x: f32) -> bool { |
| f32::ulp_diff(&f32::NAN, &x) == UlpComparisonResult::Nan |
| && f32::ulp_diff(&x, &f32::NAN) == UlpComparisonResult::Nan |
| } |
| } |
| |
| quickcheck! { |
| fn property_f64_nan_gives_nan_enum_value(x: f64) -> bool { |
| f64::ulp_diff(&f64::NAN, &x) == UlpComparisonResult::Nan |
| && f64::ulp_diff(&x, &f64::NAN) == UlpComparisonResult::Nan |
| } |
| } |
| } |