Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/float_ord.rs
6595 views
1
use core::{
2
cmp::Ordering,
3
hash::{Hash, Hasher},
4
ops::Neg,
5
};
6
7
#[cfg(feature = "bevy_reflect")]
8
use bevy_reflect::Reflect;
9
10
/// A wrapper for floats that implements [`Ord`], [`Eq`], and [`Hash`] traits.
11
///
12
/// This is a work around for the fact that the IEEE 754-2008 standard,
13
/// implemented by Rust's [`f32`] type,
14
/// doesn't define an ordering for [`NaN`](f32::NAN),
15
/// and `NaN` is not considered equal to any other `NaN`.
16
///
17
/// Wrapping a float with `FloatOrd` breaks conformance with the standard
18
/// by sorting `NaN` as less than all other numbers and equal to any other `NaN`.
19
#[derive(Debug, Copy, Clone)]
20
#[cfg_attr(
21
feature = "bevy_reflect",
22
derive(Reflect),
23
reflect(Debug, PartialEq, Hash, Clone)
24
)]
25
pub struct FloatOrd(pub f32);
26
27
impl PartialOrd for FloatOrd {
28
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
29
Some(self.cmp(other))
30
}
31
32
fn lt(&self, other: &Self) -> bool {
33
!other.le(self)
34
}
35
// If `self` is NaN, it is equal to another NaN and less than all other floats, so return true.
36
// If `self` isn't NaN and `other` is, the float comparison returns false, which match the `FloatOrd` ordering.
37
// Otherwise, a standard float comparison happens.
38
fn le(&self, other: &Self) -> bool {
39
self.0.is_nan() || self.0 <= other.0
40
}
41
fn gt(&self, other: &Self) -> bool {
42
!self.le(other)
43
}
44
fn ge(&self, other: &Self) -> bool {
45
other.le(self)
46
}
47
}
48
49
impl Ord for FloatOrd {
50
#[expect(
51
clippy::comparison_chain,
52
reason = "This can't be rewritten with `match` and `cmp`, as this is `cmp` itself."
53
)]
54
fn cmp(&self, other: &Self) -> Ordering {
55
if self > other {
56
Ordering::Greater
57
} else if self < other {
58
Ordering::Less
59
} else {
60
Ordering::Equal
61
}
62
}
63
}
64
65
impl PartialEq for FloatOrd {
66
fn eq(&self, other: &Self) -> bool {
67
if self.0.is_nan() {
68
other.0.is_nan()
69
} else {
70
self.0 == other.0
71
}
72
}
73
}
74
75
impl Eq for FloatOrd {}
76
77
impl Hash for FloatOrd {
78
fn hash<H: Hasher>(&self, state: &mut H) {
79
if self.0.is_nan() {
80
// Ensure all NaN representations hash to the same value
81
state.write(&f32::to_ne_bytes(f32::NAN));
82
} else if self.0 == 0.0 {
83
// Ensure both zeroes hash to the same value
84
state.write(&f32::to_ne_bytes(0.0f32));
85
} else {
86
state.write(&f32::to_ne_bytes(self.0));
87
}
88
}
89
}
90
91
impl Neg for FloatOrd {
92
type Output = FloatOrd;
93
94
fn neg(self) -> Self::Output {
95
FloatOrd(-self.0)
96
}
97
}
98
99
#[cfg(test)]
100
mod tests {
101
use super::*;
102
103
const NAN: FloatOrd = FloatOrd(f32::NAN);
104
const ZERO: FloatOrd = FloatOrd(0.0);
105
const ONE: FloatOrd = FloatOrd(1.0);
106
107
#[test]
108
fn float_ord_eq() {
109
assert_eq!(NAN, NAN);
110
111
assert_ne!(NAN, ZERO);
112
assert_ne!(ZERO, NAN);
113
114
assert_eq!(ZERO, ZERO);
115
}
116
117
#[test]
118
fn float_ord_cmp() {
119
assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
120
121
assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
122
assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
123
124
assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
125
assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
126
assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
127
}
128
129
#[test]
130
#[expect(
131
clippy::nonminimal_bool,
132
reason = "This tests that all operators work as they should, and in the process requires some non-simplified boolean expressions."
133
)]
134
fn float_ord_cmp_operators() {
135
assert!(!(NAN < NAN));
136
assert!(NAN < ZERO);
137
assert!(!(ZERO < NAN));
138
assert!(!(ZERO < ZERO));
139
assert!(ZERO < ONE);
140
assert!(!(ONE < ZERO));
141
142
assert!(!(NAN > NAN));
143
assert!(!(NAN > ZERO));
144
assert!(ZERO > NAN);
145
assert!(!(ZERO > ZERO));
146
assert!(!(ZERO > ONE));
147
assert!(ONE > ZERO);
148
149
assert!(NAN <= NAN);
150
assert!(NAN <= ZERO);
151
assert!(!(ZERO <= NAN));
152
assert!(ZERO <= ZERO);
153
assert!(ZERO <= ONE);
154
assert!(!(ONE <= ZERO));
155
156
assert!(NAN >= NAN);
157
assert!(!(NAN >= ZERO));
158
assert!(ZERO >= NAN);
159
assert!(ZERO >= ZERO);
160
assert!(!(ZERO >= ONE));
161
assert!(ONE >= ZERO);
162
}
163
164
#[cfg(feature = "std")]
165
#[test]
166
fn float_ord_hash() {
167
let hash = |num| {
168
let mut h = std::hash::DefaultHasher::new();
169
FloatOrd(num).hash(&mut h);
170
h.finish()
171
};
172
173
assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
174
assert_eq!(hash(-0.0), hash(0.0));
175
176
let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
177
assert!(nan_1.is_nan());
178
let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
179
assert!(nan_2.is_nan());
180
assert_ne!(nan_1.to_bits(), nan_2.to_bits());
181
assert_eq!(hash(nan_1), hash(nan_2));
182
}
183
}
184
185