2024-09-27 00:59:59 +00:00
|
|
|
use core::{
|
2020-06-10 22:35:23 +00:00
|
|
|
cmp::Ordering,
|
|
|
|
hash::{Hash, Hasher},
|
2020-06-24 18:35:01 +00:00
|
|
|
ops::Neg,
|
2020-06-10 22:35:23 +00:00
|
|
|
};
|
|
|
|
|
2024-05-27 18:18:10 +00:00
|
|
|
#[cfg(feature = "bevy_reflect")]
|
|
|
|
use bevy_reflect::Reflect;
|
|
|
|
|
2022-07-11 14:11:27 +00:00
|
|
|
/// A wrapper for floats that implements [`Ord`], [`Eq`], and [`Hash`] traits.
|
|
|
|
///
|
|
|
|
/// This is a work around for the fact that the IEEE 754-2008 standard,
|
|
|
|
/// implemented by Rust's [`f32`] type,
|
|
|
|
/// doesn't define an ordering for [`NaN`](f32::NAN),
|
|
|
|
/// and `NaN` is not considered equal to any other `NaN`.
|
|
|
|
///
|
|
|
|
/// Wrapping a float with `FloatOrd` breaks conformance with the standard
|
|
|
|
/// by sorting `NaN` as less than all other numbers and equal to any other `NaN`.
|
2024-03-27 00:26:56 +00:00
|
|
|
#[derive(Debug, Copy, Clone)]
|
2024-05-27 18:18:10 +00:00
|
|
|
#[cfg_attr(
|
|
|
|
feature = "bevy_reflect",
|
|
|
|
derive(Reflect),
|
|
|
|
reflect(Debug, PartialEq, Hash)
|
|
|
|
)]
|
2020-06-10 22:35:23 +00:00
|
|
|
pub struct FloatOrd(pub f32);
|
|
|
|
|
2024-03-27 00:26:56 +00:00
|
|
|
impl PartialOrd for FloatOrd {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
Some(self.cmp(other))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lt(&self, other: &Self) -> bool {
|
|
|
|
!other.le(self)
|
|
|
|
}
|
|
|
|
// If `self` is NaN, it is equal to another NaN and less than all other floats, so return true.
|
|
|
|
// If `self` isn't NaN and `other` is, the float comparison returns false, which match the `FloatOrd` ordering.
|
|
|
|
// Otherwise, a standard float comparison happens.
|
|
|
|
fn le(&self, other: &Self) -> bool {
|
|
|
|
self.0.is_nan() || self.0 <= other.0
|
|
|
|
}
|
|
|
|
fn gt(&self, other: &Self) -> bool {
|
|
|
|
!self.le(other)
|
|
|
|
}
|
|
|
|
fn ge(&self, other: &Self) -> bool {
|
|
|
|
other.le(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 22:35:23 +00:00
|
|
|
impl Ord for FloatOrd {
|
2024-03-27 00:26:56 +00:00
|
|
|
#[allow(clippy::comparison_chain)]
|
2020-06-10 22:35:23 +00:00
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
2024-03-27 00:26:56 +00:00
|
|
|
if self > other {
|
|
|
|
Ordering::Greater
|
|
|
|
} else if self < other {
|
|
|
|
Ordering::Less
|
|
|
|
} else {
|
|
|
|
Ordering::Equal
|
|
|
|
}
|
2020-06-10 22:35:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for FloatOrd {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
2024-03-27 00:26:56 +00:00
|
|
|
if self.0.is_nan() {
|
|
|
|
other.0.is_nan()
|
2020-06-10 22:35:23 +00:00
|
|
|
} else {
|
|
|
|
self.0 == other.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for FloatOrd {}
|
|
|
|
|
|
|
|
impl Hash for FloatOrd {
|
|
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
2020-10-03 19:56:25 +00:00
|
|
|
if self.0.is_nan() {
|
|
|
|
// Ensure all NaN representations hash to the same value
|
2022-04-27 18:02:05 +00:00
|
|
|
state.write(&f32::to_ne_bytes(f32::NAN));
|
2020-10-03 19:56:25 +00:00
|
|
|
} else if self.0 == 0.0 {
|
|
|
|
// Ensure both zeroes hash to the same value
|
2022-04-27 18:02:05 +00:00
|
|
|
state.write(&f32::to_ne_bytes(0.0f32));
|
2020-10-03 19:56:25 +00:00
|
|
|
} else {
|
2022-04-27 18:02:05 +00:00
|
|
|
state.write(&f32::to_ne_bytes(self.0));
|
2020-10-03 19:56:25 +00:00
|
|
|
}
|
2020-06-10 22:35:23 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-24 18:35:01 +00:00
|
|
|
|
|
|
|
impl Neg for FloatOrd {
|
|
|
|
type Output = FloatOrd;
|
2020-07-28 21:24:03 +00:00
|
|
|
|
2020-06-24 18:35:01 +00:00
|
|
|
fn neg(self) -> Self::Output {
|
|
|
|
FloatOrd(-self.0)
|
|
|
|
}
|
2020-07-10 08:37:06 +00:00
|
|
|
}
|
2024-03-27 00:26:56 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
const NAN: FloatOrd = FloatOrd(f32::NAN);
|
|
|
|
const ZERO: FloatOrd = FloatOrd(0.0);
|
|
|
|
const ONE: FloatOrd = FloatOrd(1.0);
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn float_ord_eq() {
|
|
|
|
assert_eq!(NAN, NAN);
|
|
|
|
|
|
|
|
assert_ne!(NAN, ZERO);
|
|
|
|
assert_ne!(ZERO, NAN);
|
|
|
|
|
|
|
|
assert_eq!(ZERO, ZERO);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn float_ord_cmp() {
|
|
|
|
assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
|
|
|
|
|
|
|
|
assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
|
|
|
|
assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
|
|
|
|
|
|
|
|
assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
|
|
|
|
assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
|
|
|
|
assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[allow(clippy::nonminimal_bool)]
|
|
|
|
fn float_ord_cmp_operators() {
|
|
|
|
assert!(!(NAN < NAN));
|
|
|
|
assert!(NAN < ZERO);
|
|
|
|
assert!(!(ZERO < NAN));
|
|
|
|
assert!(!(ZERO < ZERO));
|
|
|
|
assert!(ZERO < ONE);
|
|
|
|
assert!(!(ONE < ZERO));
|
|
|
|
|
|
|
|
assert!(!(NAN > NAN));
|
|
|
|
assert!(!(NAN > ZERO));
|
|
|
|
assert!(ZERO > NAN);
|
|
|
|
assert!(!(ZERO > ZERO));
|
|
|
|
assert!(!(ZERO > ONE));
|
|
|
|
assert!(ONE > ZERO);
|
|
|
|
|
|
|
|
assert!(NAN <= NAN);
|
|
|
|
assert!(NAN <= ZERO);
|
|
|
|
assert!(!(ZERO <= NAN));
|
|
|
|
assert!(ZERO <= ZERO);
|
|
|
|
assert!(ZERO <= ONE);
|
|
|
|
assert!(!(ONE <= ZERO));
|
|
|
|
|
|
|
|
assert!(NAN >= NAN);
|
|
|
|
assert!(!(NAN >= ZERO));
|
|
|
|
assert!(ZERO >= NAN);
|
|
|
|
assert!(ZERO >= ZERO);
|
|
|
|
assert!(!(ZERO >= ONE));
|
|
|
|
assert!(ONE >= ZERO);
|
|
|
|
}
|
|
|
|
|
2024-12-03 17:14:51 +00:00
|
|
|
#[cfg(feature = "std")]
|
2024-03-27 00:26:56 +00:00
|
|
|
#[test]
|
|
|
|
fn float_ord_hash() {
|
|
|
|
let hash = |num| {
|
2024-12-03 17:14:51 +00:00
|
|
|
let mut h = std::hash::DefaultHasher::new();
|
2024-03-27 00:26:56 +00:00
|
|
|
FloatOrd(num).hash(&mut h);
|
|
|
|
h.finish()
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
|
|
|
|
assert_eq!(hash(-0.0), hash(0.0));
|
|
|
|
|
|
|
|
let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
|
|
|
|
assert!(nan_1.is_nan());
|
|
|
|
let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
|
|
|
|
assert!(nan_2.is_nan());
|
|
|
|
assert_ne!(nan_1.to_bits(), nan_2.to_bits());
|
|
|
|
assert_eq!(hash(nan_1), hash(nan_2));
|
|
|
|
}
|
|
|
|
}
|