polars_utils/
min_max.rs

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