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)]
20#[cfg_attr(
21 feature = "bevy_reflect",
22 derive(Reflect),
23 reflect(Debug, PartialEq, Hash, Clone)
24)]
25pub struct FloatOrd(pub f32);
26
27impl 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 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
49impl 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
65impl 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
75impl Eq for FloatOrd {}
76
77impl Hash for FloatOrd {
78 fn hash<H: Hasher>(&self, state: &mut H) {
79 if self.0.is_nan() {
80 state.write(&f32::to_ne_bytes(f32::NAN));
82 } else if self.0 == 0.0 {
83 state.write(&f32::to_ne_bytes(0.0f32));
85 } else {
86 state.write(&f32::to_ne_bytes(self.0));
87 }
88 }
89}
90
91impl Neg for FloatOrd {
92 type Output = FloatOrd;
93
94 fn neg(self) -> Self::Output {
95 FloatOrd(-self.0)
96 }
97}
98
99#[cfg(test)]
100mod 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}