Skip to main content

polars_utils/
min_max.rs

1use num_traits::Bounded;
2
3use crate::float::IsFloat;
4use crate::float16::pf16;
5
6// These min/max operators don't follow our total order strictly. Instead
7// if exactly one of the two arguments is NaN the skip_nan varieties returns
8// the non-nan argument, whereas the propagate_nan varieties give the nan
9// argument. If both/neither argument is NaN these extrema follow the normal
10// total order.
11//
12// They also violate the regular total order for Option<T>: on top of the
13// above rules None's are always ignored, so only if both arguments are
14// None is the output None.
15pub trait MinMax: Sized {
16    // Comparison operators that either consider nan to be the smallest, or the
17    // largest possible value. Use tot_eq for equality. Prefer directly using
18    // min/max, they're slightly faster.
19    fn nan_min_lt(&self, other: &Self) -> bool;
20    fn nan_max_lt(&self, other: &Self) -> bool;
21
22    #[inline(always)]
23    fn nan_min_gt(&self, other: &Self) -> bool {
24        other.nan_min_lt(self)
25    }
26
27    #[inline(always)]
28    fn nan_max_gt(&self, other: &Self) -> bool {
29        other.nan_max_lt(self)
30    }
31
32    // Binary operators that return either the minimum or maximum.
33    #[inline(always)]
34    fn min_propagate_nan(self, other: Self) -> Self {
35        if self.nan_min_lt(&other) { self } else { other }
36    }
37
38    #[inline(always)]
39    fn max_propagate_nan(self, other: Self) -> Self {
40        if self.nan_max_lt(&other) { other } else { self }
41    }
42
43    #[inline(always)]
44    fn min_ignore_nan(self, other: Self) -> Self {
45        if self.nan_max_lt(&other) { self } else { other }
46    }
47
48    #[inline(always)]
49    fn max_ignore_nan(self, other: Self) -> Self {
50        if self.nan_min_lt(&other) { other } else { self }
51    }
52}
53
54macro_rules! impl_trivial_min_max {
55    ($T: ty) => {
56        impl MinMax for $T {
57            #[inline(always)]
58            fn nan_min_lt(&self, other: &Self) -> bool {
59                self < other
60            }
61
62            #[inline(always)]
63            fn nan_max_lt(&self, other: &Self) -> bool {
64                self < other
65            }
66        }
67    };
68}
69
70// We can't do a blanket impl because Rust complains f32 might implement
71// Ord someday.
72impl_trivial_min_max!(bool);
73impl_trivial_min_max!(u8);
74impl_trivial_min_max!(u16);
75impl_trivial_min_max!(u32);
76impl_trivial_min_max!(u64);
77impl_trivial_min_max!(u128);
78impl_trivial_min_max!(usize);
79impl_trivial_min_max!(i8);
80impl_trivial_min_max!(i16);
81impl_trivial_min_max!(i32);
82impl_trivial_min_max!(i64);
83impl_trivial_min_max!(i128);
84impl_trivial_min_max!(isize);
85impl_trivial_min_max!(char);
86impl_trivial_min_max!(&str);
87impl_trivial_min_max!(&[u8]);
88impl_trivial_min_max!(String);
89
90macro_rules! impl_float_min_max {
91    ($T: ty) => {
92        impl MinMax for $T {
93            #[inline(always)]
94            fn nan_min_lt(&self, other: &Self) -> bool {
95                !(other.is_nan() | (self >= other))
96            }
97
98            #[inline(always)]
99            fn nan_max_lt(&self, other: &Self) -> bool {
100                !(self.is_nan() | (self >= other))
101            }
102
103            #[inline(always)]
104            fn min_ignore_nan(self, other: Self) -> Self {
105                <$T>::min(self, other)
106            }
107
108            #[inline(always)]
109            fn max_ignore_nan(self, other: Self) -> Self {
110                <$T>::max(self, other)
111            }
112
113            #[inline(always)]
114            fn min_propagate_nan(self, other: Self) -> Self {
115                if (self < other) | self.is_nan() {
116                    self
117                } else {
118                    other
119                }
120            }
121
122            #[inline(always)]
123            fn max_propagate_nan(self, other: Self) -> Self {
124                if (self > other) | self.is_nan() {
125                    self
126                } else {
127                    other
128                }
129            }
130        }
131    };
132}
133
134impl_float_min_max!(pf16);
135impl_float_min_max!(f32);
136impl_float_min_max!(f64);
137
138pub trait MinMaxPolicy {
139    // Is the first argument strictly better than the second, per the policy?
140    fn is_better<T: MinMax>(a: &T, b: &T) -> bool;
141    fn best<T: MinMax>(a: T, b: T) -> T;
142
143    /// Fold identity, including infinities for floating-point inputs.
144    #[inline]
145    fn identity<T: MinMax + IsFloat + Bounded>() -> T {
146        let (lo, hi) = if T::is_float() {
147            (T::neg_inf_value(), T::pos_inf_value())
148        } else {
149            (T::min_value(), T::max_value())
150        };
151        if Self::is_better(&lo, &hi) { hi } else { lo }
152    }
153}
154
155#[derive(Copy, Clone, Debug)]
156pub struct MinIgnoreNan;
157impl MinMaxPolicy for MinIgnoreNan {
158    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
159        T::nan_max_lt(a, b)
160    }
161
162    fn best<T: MinMax>(a: T, b: T) -> T {
163        T::min_ignore_nan(a, b)
164    }
165}
166
167#[derive(Copy, Clone, Debug)]
168pub struct MinPropagateNan;
169impl MinMaxPolicy for MinPropagateNan {
170    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
171        T::nan_min_lt(a, b)
172    }
173
174    fn best<T: MinMax>(a: T, b: T) -> T {
175        T::min_propagate_nan(a, b)
176    }
177}
178
179#[derive(Copy, Clone, Debug)]
180pub struct MaxIgnoreNan;
181impl MinMaxPolicy for MaxIgnoreNan {
182    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
183        T::nan_min_lt(b, a)
184    }
185
186    fn best<T: MinMax>(a: T, b: T) -> T {
187        T::max_ignore_nan(a, b)
188    }
189}
190
191#[derive(Copy, Clone, Debug)]
192pub struct MaxPropagateNan;
193impl MinMaxPolicy for MaxPropagateNan {
194    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
195        T::nan_max_lt(b, a)
196    }
197
198    fn best<T: MinMax>(a: T, b: T) -> T {
199        T::max_propagate_nan(a, b)
200    }
201}