diff --git a/src/lib.rs b/src/lib.rs index d17ce5a..5a512e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,14 @@ const MAN_MASK: u64 = 0x000fffffffffffffu64; // canonical raw bit patterns (for hashing) const CANONICAL_NAN_BITS: u64 = 0x7ff8000000000000u64; -const CANONICAL_ZERO_BITS: u64 = 0x0u64; + +#[inline(always)] +fn canonicalize_signed_zero(x: T) -> T { + // -0.0 + 0.0 == +0.0 under IEEE754 roundTiesToEven rounding mode, + // which Rust guarantees. Thus by adding a positive zero we + // canonicalize signed zero without any branches in one instruction. + x + T::zero() +} /// A wrapper around floats providing implementations of `Eq`, `Ord`, and `Hash`. /// @@ -116,25 +123,43 @@ impl PartialOrd for OrderedFloat { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } + + #[inline] + fn lt(&self, other: &Self) -> bool { + !self.ge(other) + } + + #[inline] + fn le(&self, other: &Self) -> bool { + other.ge(self) + } + + #[inline] + fn gt(&self, other: &Self) -> bool { + !other.ge(self) + } + + #[inline] + fn ge(&self, other: &Self) -> bool { + // We consider all NaNs equal, and NaN is the largest possible + // value. Thus if self is NaN we always return true. Otherwise + // self >= other is correct. If other is also not NaN it is trivially + // correct, and if it is we note that nothing can be greater or + // equal to NaN except NaN itself, which we already handled earlier. + self.0.is_nan() | (self.0 >= other.0) + } } impl Ord for OrderedFloat { + #[inline] fn cmp(&self, other: &Self) -> Ordering { - let lhs = &self.0; - let rhs = &other.0; - match lhs.partial_cmp(rhs) { - Some(ordering) => ordering, - None => { - if lhs.is_nan() { - if rhs.is_nan() { - Ordering::Equal - } else { - Ordering::Greater - } - } else { - Ordering::Less - } - } + #[allow(clippy::comparison_chain)] + if self < other { + Ordering::Less + } else if self > other { + Ordering::Greater + } else { + Ordering::Equal } } } @@ -161,10 +186,8 @@ impl Hash for OrderedFloat { fn hash(&self, state: &mut H) { let bits = if self.is_nan() { CANONICAL_NAN_BITS - } else if self.is_zero() { - CANONICAL_ZERO_BITS } else { - raw_double_bits(&self.0) + raw_double_bits(&canonicalize_signed_zero(self.0)) }; bits.hash(state) @@ -1150,12 +1173,7 @@ impl Ord for NotNan { impl Hash for NotNan { #[inline] fn hash(&self, state: &mut H) { - let bits = if self.is_zero() { - CANONICAL_ZERO_BITS - } else { - raw_double_bits(&self.0) - }; - + let bits = raw_double_bits(&canonicalize_signed_zero(self.0)); bits.hash(state) } } diff --git a/tests/test.rs b/tests/test.rs index 69dfd6d..5626d1c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -21,6 +21,32 @@ fn not_nan(x: T) -> NotNan { NotNan::new(x).unwrap() } +#[test] +fn test_total_order() { + let numberline = [ + (-f32::INFINITY, 0), + (-1.0, 1), + (-0.0, 2), + (0.0, 2), + (1.0, 3), + (f32::INFINITY, 4), + (f32::NAN, 5), + (-f32::NAN, 5), + ]; + + for &(fi, i) in &numberline { + for &(fj, j) in &numberline { + assert_eq!(OrderedFloat(fi) < OrderedFloat(fj), i < j); + assert_eq!(OrderedFloat(fi) > OrderedFloat(fj), i > j); + assert_eq!(OrderedFloat(fi) <= OrderedFloat(fj), i <= j); + assert_eq!(OrderedFloat(fi) >= OrderedFloat(fj), i >= j); + assert_eq!(OrderedFloat(fi) == OrderedFloat(fj), i == j); + assert_eq!(OrderedFloat(fi) != OrderedFloat(fj), i != j); + assert_eq!(OrderedFloat(fi).cmp(&OrderedFloat(fj)), i.cmp(&j)); + } + } +} + #[test] fn ordered_f32_compare_regular_floats() { assert_eq!(OrderedFloat(7.0f32).cmp(&OrderedFloat(7.0)), Equal);