Skip to content

Commit 4e29b08

Browse files
authored
Merge pull request #144 from orlp/faster-ord
Optimize ord implementation and signed zero canonicalization
2 parents f4ca84b + 71770b3 commit 4e29b08

File tree

2 files changed

+69
-25
lines changed

2 files changed

+69
-25
lines changed

src/lib.rs

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,14 @@ const MAN_MASK: u64 = 0x000fffffffffffffu64;
4141

4242
// canonical raw bit patterns (for hashing)
4343
const CANONICAL_NAN_BITS: u64 = 0x7ff8000000000000u64;
44-
const CANONICAL_ZERO_BITS: u64 = 0x0u64;
44+
45+
#[inline(always)]
46+
fn canonicalize_signed_zero<T: FloatCore>(x: T) -> T {
47+
// -0.0 + 0.0 == +0.0 under IEEE754 roundTiesToEven rounding mode,
48+
// which Rust guarantees. Thus by adding a positive zero we
49+
// canonicalize signed zero without any branches in one instruction.
50+
x + T::zero()
51+
}
4552

4653
/// A wrapper around floats providing implementations of `Eq`, `Ord`, and `Hash`.
4754
///
@@ -116,25 +123,43 @@ impl<T: FloatCore> PartialOrd for OrderedFloat<T> {
116123
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
117124
Some(self.cmp(other))
118125
}
126+
127+
#[inline]
128+
fn lt(&self, other: &Self) -> bool {
129+
!self.ge(other)
130+
}
131+
132+
#[inline]
133+
fn le(&self, other: &Self) -> bool {
134+
other.ge(self)
135+
}
136+
137+
#[inline]
138+
fn gt(&self, other: &Self) -> bool {
139+
!other.ge(self)
140+
}
141+
142+
#[inline]
143+
fn ge(&self, other: &Self) -> bool {
144+
// We consider all NaNs equal, and NaN is the largest possible
145+
// value. Thus if self is NaN we always return true. Otherwise
146+
// self >= other is correct. If other is also not NaN it is trivially
147+
// correct, and if it is we note that nothing can be greater or
148+
// equal to NaN except NaN itself, which we already handled earlier.
149+
self.0.is_nan() | (self.0 >= other.0)
150+
}
119151
}
120152

121153
impl<T: FloatCore> Ord for OrderedFloat<T> {
154+
#[inline]
122155
fn cmp(&self, other: &Self) -> Ordering {
123-
let lhs = &self.0;
124-
let rhs = &other.0;
125-
match lhs.partial_cmp(rhs) {
126-
Some(ordering) => ordering,
127-
None => {
128-
if lhs.is_nan() {
129-
if rhs.is_nan() {
130-
Ordering::Equal
131-
} else {
132-
Ordering::Greater
133-
}
134-
} else {
135-
Ordering::Less
136-
}
137-
}
156+
#[allow(clippy::comparison_chain)]
157+
if self < other {
158+
Ordering::Less
159+
} else if self > other {
160+
Ordering::Greater
161+
} else {
162+
Ordering::Equal
138163
}
139164
}
140165
}
@@ -161,10 +186,8 @@ impl<T: FloatCore> Hash for OrderedFloat<T> {
161186
fn hash<H: Hasher>(&self, state: &mut H) {
162187
let bits = if self.is_nan() {
163188
CANONICAL_NAN_BITS
164-
} else if self.is_zero() {
165-
CANONICAL_ZERO_BITS
166189
} else {
167-
raw_double_bits(&self.0)
190+
raw_double_bits(&canonicalize_signed_zero(self.0))
168191
};
169192

170193
bits.hash(state)
@@ -1150,12 +1173,7 @@ impl<T: FloatCore> Ord for NotNan<T> {
11501173
impl<T: FloatCore> Hash for NotNan<T> {
11511174
#[inline]
11521175
fn hash<H: Hasher>(&self, state: &mut H) {
1153-
let bits = if self.is_zero() {
1154-
CANONICAL_ZERO_BITS
1155-
} else {
1156-
raw_double_bits(&self.0)
1157-
};
1158-
1176+
let bits = raw_double_bits(&canonicalize_signed_zero(self.0));
11591177
bits.hash(state)
11601178
}
11611179
}

tests/test.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,32 @@ fn not_nan<T: FloatCore>(x: T) -> NotNan<T> {
2121
NotNan::new(x).unwrap()
2222
}
2323

24+
#[test]
25+
fn test_total_order() {
26+
let numberline = [
27+
(-f32::INFINITY, 0),
28+
(-1.0, 1),
29+
(-0.0, 2),
30+
(0.0, 2),
31+
(1.0, 3),
32+
(f32::INFINITY, 4),
33+
(f32::NAN, 5),
34+
(-f32::NAN, 5),
35+
];
36+
37+
for &(fi, i) in &numberline {
38+
for &(fj, j) in &numberline {
39+
assert_eq!(OrderedFloat(fi) < OrderedFloat(fj), i < j);
40+
assert_eq!(OrderedFloat(fi) > OrderedFloat(fj), i > j);
41+
assert_eq!(OrderedFloat(fi) <= OrderedFloat(fj), i <= j);
42+
assert_eq!(OrderedFloat(fi) >= OrderedFloat(fj), i >= j);
43+
assert_eq!(OrderedFloat(fi) == OrderedFloat(fj), i == j);
44+
assert_eq!(OrderedFloat(fi) != OrderedFloat(fj), i != j);
45+
assert_eq!(OrderedFloat(fi).cmp(&OrderedFloat(fj)), i.cmp(&j));
46+
}
47+
}
48+
}
49+
2450
#[test]
2551
fn ordered_f32_compare_regular_floats() {
2652
assert_eq!(OrderedFloat(7.0f32).cmp(&OrderedFloat(7.0)), Equal);

0 commit comments

Comments
 (0)