1use core::{
2 cmp::Ordering,
3 hash::{Hash, Hasher},
4 ops::Neg,
5};
6
7#[cfg(feature = "bevy_reflect")]
8use bevy_reflect::Reflect;
9
10#[derive(Debug, Copy, Clone, Default)]
24#[cfg_attr(
25 feature = "bevy_reflect",
26 derive(Reflect),
27 reflect(Debug, PartialEq, Hash, Clone)
28)]
29pub struct FloatOrd(pub f32);
30
31impl PartialOrd for FloatOrd {
32 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33 Some(self.cmp(other))
34 }
35
36 fn lt(&self, other: &Self) -> bool {
37 !other.le(self)
38 }
39 fn le(&self, other: &Self) -> bool {
43 self.0.is_nan() || self.0 <= other.0
44 }
45 fn gt(&self, other: &Self) -> bool {
46 !self.le(other)
47 }
48 fn ge(&self, other: &Self) -> bool {
49 other.le(self)
50 }
51}
52
53impl Ord for FloatOrd {
54 #[expect(
55 clippy::comparison_chain,
56 reason = "This can't be rewritten with `match` and `cmp`, as this is `cmp` itself."
57 )]
58 fn cmp(&self, other: &Self) -> Ordering {
59 if self > other {
60 Ordering::Greater
61 } else if self < other {
62 Ordering::Less
63 } else {
64 Ordering::Equal
65 }
66 }
67}
68
69impl PartialEq for FloatOrd {
70 fn eq(&self, other: &Self) -> bool {
71 if self.0.is_nan() {
72 other.0.is_nan()
73 } else {
74 self.0 == other.0
75 }
76 }
77}
78
79impl Eq for FloatOrd {}
80
81impl Hash for FloatOrd {
82 fn hash<H: Hasher>(&self, state: &mut H) {
83 if self.0.is_nan() {
84 state.write(&f32::to_ne_bytes(f32::NAN));
86 } else if self.0 == 0.0 {
87 state.write(&f32::to_ne_bytes(0.0f32));
89 } else {
90 state.write(&f32::to_ne_bytes(self.0));
91 }
92 }
93}
94
95impl Neg for FloatOrd {
96 type Output = FloatOrd;
97
98 fn neg(self) -> Self::Output {
99 FloatOrd(-self.0)
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 const NAN: FloatOrd = FloatOrd(f32::NAN);
108 const ZERO: FloatOrd = FloatOrd(0.0);
109 const ONE: FloatOrd = FloatOrd(1.0);
110
111 #[test]
112 fn float_ord_eq() {
113 assert_eq!(NAN, NAN);
114
115 assert_ne!(NAN, ZERO);
116 assert_ne!(ZERO, NAN);
117
118 assert_eq!(ZERO, ZERO);
119 }
120
121 #[test]
122 fn float_ord_cmp() {
123 assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
124
125 assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
126 assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
127
128 assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
129 assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
130 assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
131 }
132
133 #[test]
134 #[expect(
135 clippy::nonminimal_bool,
136 reason = "This tests that all operators work as they should, and in the process requires some non-simplified boolean expressions."
137 )]
138 fn float_ord_cmp_operators() {
139 assert!(!(NAN < NAN));
140 assert!(NAN < ZERO);
141 assert!(!(ZERO < NAN));
142 assert!(!(ZERO < ZERO));
143 assert!(ZERO < ONE);
144 assert!(!(ONE < ZERO));
145
146 assert!(!(NAN > NAN));
147 assert!(!(NAN > ZERO));
148 assert!(ZERO > NAN);
149 assert!(!(ZERO > ZERO));
150 assert!(!(ZERO > ONE));
151 assert!(ONE > ZERO);
152
153 assert!(NAN <= NAN);
154 assert!(NAN <= ZERO);
155 assert!(!(ZERO <= NAN));
156 assert!(ZERO <= ZERO);
157 assert!(ZERO <= ONE);
158 assert!(!(ONE <= ZERO));
159
160 assert!(NAN >= NAN);
161 assert!(!(NAN >= ZERO));
162 assert!(ZERO >= NAN);
163 assert!(ZERO >= ZERO);
164 assert!(!(ZERO >= ONE));
165 assert!(ONE >= ZERO);
166 }
167
168 #[cfg(feature = "std")]
169 #[test]
170 fn float_ord_hash() {
171 let hash = |num| {
172 let mut h = std::hash::DefaultHasher::new();
173 FloatOrd(num).hash(&mut h);
174 h.finish()
175 };
176
177 assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
178 assert_eq!(hash(-0.0), hash(0.0));
179
180 let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
181 assert!(nan_1.is_nan());
182 let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
183 assert!(nan_2.is_nan());
184 assert_ne!(nan_1.to_bits(), nan_2.to_bits());
185 assert_eq!(hash(nan_1), hash(nan_2));
186 }
187}