emath/
ordered_float.rs

1//! Total order on floating point types.
2//! Can be used for sorting, min/max computation, and other collection algorithms.
3
4use std::cmp::Ordering;
5use std::hash::{Hash, Hasher};
6
7/// Wraps a floating-point value to add total order and hash.
8/// Possible types for `T` are `f32` and `f64`.
9///
10/// All NaNs are considered equal to each other.
11/// The size of zero is ignored.
12///
13/// See also [`Float`].
14#[derive(Clone, Copy)]
15pub struct OrderedFloat<T>(pub T);
16
17impl<T: Float + Copy> OrderedFloat<T> {
18    #[inline]
19    pub fn into_inner(self) -> T {
20        self.0
21    }
22}
23
24impl<T: Float> Eq for OrderedFloat<T> {}
25
26impl<T: Float> PartialEq<Self> for OrderedFloat<T> {
27    #[inline]
28    fn eq(&self, other: &Self) -> bool {
29        // NaNs are considered equal (equivalent) when it comes to ordering
30        if self.0.is_nan() {
31            other.0.is_nan()
32        } else {
33            self.0 == other.0
34        }
35    }
36}
37
38impl<T: Float> PartialOrd<Self> for OrderedFloat<T> {
39    #[inline]
40    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
41        Some(self.cmp(other))
42    }
43}
44
45impl<T: Float> Ord for OrderedFloat<T> {
46    #[inline]
47    fn cmp(&self, other: &Self) -> Ordering {
48        match self.0.partial_cmp(&other.0) {
49            Some(ord) => ord,
50            None => self.0.is_nan().cmp(&other.0.is_nan()),
51        }
52    }
53}
54
55impl<T: Float> Hash for OrderedFloat<T> {
56    fn hash<H: Hasher>(&self, state: &mut H) {
57        self.0.hash(state);
58    }
59}
60
61impl<T> From<T> for OrderedFloat<T> {
62    #[inline]
63    fn from(val: T) -> Self {
64        Self(val)
65    }
66}
67
68// ----------------------------------------------------------------------------
69
70/// Extension trait to provide `ord()` method.
71///
72/// Example with `f64`:
73/// ```
74/// use emath::Float as _;
75///
76/// let array = [1.0, 2.5, 2.0];
77/// let max = array.iter().max_by_key(|val| val.ord());
78///
79/// assert_eq!(max, Some(&2.5));
80/// ```
81pub trait Float: PartialOrd + PartialEq + private::FloatImpl {
82    /// Type to provide total order, useful as key in sorted contexts.
83    fn ord(self) -> OrderedFloat<Self>
84    where
85        Self: Sized;
86}
87
88impl Float for f32 {
89    #[inline]
90    fn ord(self) -> OrderedFloat<Self> {
91        OrderedFloat(self)
92    }
93}
94
95impl Float for f64 {
96    #[inline]
97    fn ord(self) -> OrderedFloat<Self> {
98        OrderedFloat(self)
99    }
100}
101
102// Keep this trait in private module, to avoid exposing its methods as extensions in user code
103mod private {
104    use super::{Hash, Hasher};
105
106    pub trait FloatImpl {
107        fn is_nan(&self) -> bool;
108
109        fn hash<H: Hasher>(&self, state: &mut H);
110    }
111
112    impl FloatImpl for f32 {
113        #[inline]
114        fn is_nan(&self) -> bool {
115            Self::is_nan(*self)
116        }
117
118        #[inline]
119        fn hash<H: Hasher>(&self, state: &mut H) {
120            if *self == 0.0 {
121                state.write_u8(0);
122            } else if self.is_nan() {
123                state.write_u8(1);
124            } else {
125                self.to_bits().hash(state);
126            }
127        }
128    }
129
130    impl FloatImpl for f64 {
131        #[inline]
132        fn is_nan(&self) -> bool {
133            Self::is_nan(*self)
134        }
135
136        #[inline]
137        fn hash<H: Hasher>(&self, state: &mut H) {
138            if *self == 0.0 {
139                state.write_u8(0);
140            } else if self.is_nan() {
141                state.write_u8(1);
142            } else {
143                self.to_bits().hash(state);
144            }
145        }
146    }
147}