Skip to content

Commit e78060f

Browse files
committed
Implement Hash for f32 and f64 only.
The previous generic implementation was slow, and potentially incorrect for custom types. Fixes #142.
1 parent 428ab8c commit e78060f

File tree

1 file changed

+59
-45
lines changed

1 file changed

+59
-45
lines changed

src/lib.rs

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,6 @@ pub use num_traits::{Float, Pow};
3333
#[cfg(feature = "rand")]
3434
pub use impl_rand::{UniformNotNan, UniformOrdered};
3535

36-
// masks for the parts of the IEEE 754 float
37-
const SIGN_MASK: u64 = 0x8000000000000000u64;
38-
const EXP_MASK: u64 = 0x7ff0000000000000u64;
39-
const MAN_MASK: u64 = 0x000fffffffffffffu64;
40-
41-
// canonical raw bit patterns (for hashing)
42-
const CANONICAL_NAN_BITS: u64 = 0x7ff8000000000000u64;
43-
44-
#[inline(always)]
45-
fn canonicalize_signed_zero<T: FloatCore>(x: T) -> T {
46-
// -0.0 + 0.0 == +0.0 under IEEE754 roundTiesToEven rounding mode,
47-
// which Rust guarantees. Thus by adding a positive zero we
48-
// canonicalize signed zero without any branches in one instruction.
49-
x + T::zero()
50-
}
51-
5236
/// A wrapper around floats providing implementations of `Eq`, `Ord`, and `Hash`.
5337
///
5438
/// NaN is sorted as *greater* than all other values and *equal*
@@ -332,18 +316,6 @@ impl<T: FloatCore> PartialEq<T> for OrderedFloat<T> {
332316
}
333317
}
334318

335-
impl<T: FloatCore> Hash for OrderedFloat<T> {
336-
fn hash<H: Hasher>(&self, state: &mut H) {
337-
let bits = if self.is_nan() {
338-
CANONICAL_NAN_BITS
339-
} else {
340-
raw_double_bits(&canonicalize_signed_zero(self.0))
341-
};
342-
343-
bits.hash(state)
344-
}
345-
}
346-
347319
impl<T: fmt::Debug> fmt::Debug for OrderedFloat<T> {
348320
#[inline]
349321
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1350,14 +1322,6 @@ impl<T: FloatCore> Ord for NotNan<T> {
13501322
}
13511323
}
13521324

1353-
impl<T: FloatCore> Hash for NotNan<T> {
1354-
#[inline]
1355-
fn hash<H: Hasher>(&self, state: &mut H) {
1356-
let bits = raw_double_bits(&canonicalize_signed_zero(self.0));
1357-
bits.hash(state)
1358-
}
1359-
}
1360-
13611325
impl<T: fmt::Debug> fmt::Debug for NotNan<T> {
13621326
#[inline]
13631327
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1790,15 +1754,6 @@ impl From<FloatIsNan> for std::io::Error {
17901754
}
17911755
}
17921756

1793-
#[inline]
1794-
/// Used for hashing. Input must not be zero or NaN.
1795-
fn raw_double_bits<F: FloatCore>(f: &F) -> u64 {
1796-
let (man, exp, sign) = f.integer_decode();
1797-
let exp_u64 = exp as u16 as u64;
1798-
let sign_u64 = (sign > 0) as u64;
1799-
(man & MAN_MASK) | ((exp_u64 << 52) & EXP_MASK) | ((sign_u64 << 63) & SIGN_MASK)
1800-
}
1801-
18021757
impl<T: FloatCore> Zero for NotNan<T> {
18031758
#[inline]
18041759
fn zero() -> Self {
@@ -2039,6 +1994,65 @@ impl_float_const!(OrderedFloat, OrderedFloat);
20391994
// Float constants are not NaN.
20401995
impl_float_const!(NotNan, |x| unsafe { NotNan::new_unchecked(x) });
20411996

1997+
// canonical raw bit patterns (for hashing)
1998+
1999+
mod hash_internals {
2000+
pub trait SealedTrait: Copy + num_traits::float::FloatCore {
2001+
type Bits: core::hash::Hash;
2002+
2003+
const CANONICAL_NAN_BITS: Self::Bits;
2004+
2005+
fn canonical_bits(self) -> Self::Bits;
2006+
}
2007+
2008+
impl SealedTrait for f32 {
2009+
type Bits = u32;
2010+
2011+
const CANONICAL_NAN_BITS: u32 = f32::NAN.to_bits();
2012+
2013+
fn canonical_bits(self) -> u32 {
2014+
// -0.0 + 0.0 == +0.0 under IEEE754 roundTiesToEven rounding mode,
2015+
// which Rust guarantees. Thus by adding a positive zero we
2016+
// canonicalize signed zero without any branches in one instruction.
2017+
(self + 0.0).to_bits()
2018+
}
2019+
}
2020+
2021+
impl SealedTrait for f64 {
2022+
type Bits = u64;
2023+
2024+
const CANONICAL_NAN_BITS: u64 = f64::NAN.to_bits();
2025+
2026+
fn canonical_bits(self) -> u64 {
2027+
(self + 0.0).to_bits()
2028+
}
2029+
}
2030+
}
2031+
2032+
/// The built-in floating point types `f32` and `f64`.
2033+
///
2034+
/// This is a "sealed" trait that cannot be implemented for any other types.
2035+
pub trait PrimitiveFloat: hash_internals::SealedTrait {}
2036+
impl PrimitiveFloat for f32 {}
2037+
impl PrimitiveFloat for f64 {}
2038+
2039+
impl<T: PrimitiveFloat> Hash for OrderedFloat<T> {
2040+
fn hash<H: Hasher>(&self, hasher: &mut H) {
2041+
let bits = if self.0.is_nan() {
2042+
T::CANONICAL_NAN_BITS
2043+
} else {
2044+
self.0.canonical_bits()
2045+
};
2046+
bits.hash(hasher);
2047+
}
2048+
}
2049+
2050+
impl<T: PrimitiveFloat> Hash for NotNan<T> {
2051+
fn hash<H: Hasher>(&self, hasher: &mut H) {
2052+
self.0.canonical_bits().hash(hasher);
2053+
}
2054+
}
2055+
20422056
#[cfg(feature = "serde")]
20432057
mod impl_serde {
20442058
extern crate serde;

0 commit comments

Comments
 (0)