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)
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 #[allow(clippy::comparison_chain)]
51 fn cmp(&self, other: &Self) -> Ordering {
52 if self > other {
53 Ordering::Greater
54 } else if self < other {
55 Ordering::Less
56 } else {
57 Ordering::Equal
58 }
59 }
60}
61
62impl PartialEq for FloatOrd {
63 fn eq(&self, other: &Self) -> bool {
64 if self.0.is_nan() {
65 other.0.is_nan()
66 } else {
67 self.0 == other.0
68 }
69 }
70}
71
72impl Eq for FloatOrd {}
73
74impl Hash for FloatOrd {
75 fn hash<H: Hasher>(&self, state: &mut H) {
76 if self.0.is_nan() {
77 state.write(&f32::to_ne_bytes(f32::NAN));
79 } else if self.0 == 0.0 {
80 state.write(&f32::to_ne_bytes(0.0f32));
82 } else {
83 state.write(&f32::to_ne_bytes(self.0));
84 }
85 }
86}
87
88impl Neg for FloatOrd {
89 type Output = FloatOrd;
90
91 fn neg(self) -> Self::Output {
92 FloatOrd(-self.0)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use std::hash::DefaultHasher;
99
100 use super::*;
101
102 const NAN: FloatOrd = FloatOrd(f32::NAN);
103 const ZERO: FloatOrd = FloatOrd(0.0);
104 const ONE: FloatOrd = FloatOrd(1.0);
105
106 #[test]
107 fn float_ord_eq() {
108 assert_eq!(NAN, NAN);
109
110 assert_ne!(NAN, ZERO);
111 assert_ne!(ZERO, NAN);
112
113 assert_eq!(ZERO, ZERO);
114 }
115
116 #[test]
117 fn float_ord_cmp() {
118 assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
119
120 assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
121 assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
122
123 assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
124 assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
125 assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
126 }
127
128 #[test]
129 #[allow(clippy::nonminimal_bool)]
130 fn float_ord_cmp_operators() {
131 assert!(!(NAN < NAN));
132 assert!(NAN < ZERO);
133 assert!(!(ZERO < NAN));
134 assert!(!(ZERO < ZERO));
135 assert!(ZERO < ONE);
136 assert!(!(ONE < ZERO));
137
138 assert!(!(NAN > NAN));
139 assert!(!(NAN > ZERO));
140 assert!(ZERO > NAN);
141 assert!(!(ZERO > ZERO));
142 assert!(!(ZERO > ONE));
143 assert!(ONE > ZERO);
144
145 assert!(NAN <= NAN);
146 assert!(NAN <= ZERO);
147 assert!(!(ZERO <= NAN));
148 assert!(ZERO <= ZERO);
149 assert!(ZERO <= ONE);
150 assert!(!(ONE <= ZERO));
151
152 assert!(NAN >= NAN);
153 assert!(!(NAN >= ZERO));
154 assert!(ZERO >= NAN);
155 assert!(ZERO >= ZERO);
156 assert!(!(ZERO >= ONE));
157 assert!(ONE >= ZERO);
158 }
159
160 #[test]
161 fn float_ord_hash() {
162 let hash = |num| {
163 let mut h = DefaultHasher::new();
164 FloatOrd(num).hash(&mut h);
165 h.finish()
166 };
167
168 assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
169 assert_eq!(hash(-0.0), hash(0.0));
170
171 let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
172 assert!(nan_1.is_nan());
173 let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
174 assert!(nan_2.is_nan());
175 assert_ne!(nan_1.to_bits(), nan_2.to_bits());
176 assert_eq!(hash(nan_1), hash(nan_2));
177 }
178}