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    // Binary operators that return either the minimum or maximum.
20    #[inline(always)]
21    fn min_propagate_nan(self, other: Self) -> Self {
22        if self.nan_min_lt(&other) { self } else { other }
23    }
24
25    #[inline(always)]
26    fn max_propagate_nan(self, other: Self) -> Self {
27        if self.nan_max_lt(&other) { other } else { self }
28    }
29
30    #[inline(always)]
31    fn min_ignore_nan(self, other: Self) -> Self {
32        if self.nan_max_lt(&other) { self } else { other }
33    }
34
35    #[inline(always)]
36    fn max_ignore_nan(self, other: Self) -> Self {
37        if self.nan_min_lt(&other) { other } else { self }
38    }
39}
40
41macro_rules! impl_trivial_min_max {
42    ($T: ty) => {
43        impl MinMax for $T {
44            #[inline(always)]
45            fn nan_min_lt(&self, other: &Self) -> bool {
46                self < other
47            }
48
49            #[inline(always)]
50            fn nan_max_lt(&self, other: &Self) -> bool {
51                self < other
52            }
53        }
54    };
55}
56
57// We can't do a blanket impl because Rust complains f32 might implement
58// Ord someday.
59impl_trivial_min_max!(bool);
60impl_trivial_min_max!(u8);
61impl_trivial_min_max!(u16);
62impl_trivial_min_max!(u32);
63impl_trivial_min_max!(u64);
64impl_trivial_min_max!(u128);
65impl_trivial_min_max!(usize);
66impl_trivial_min_max!(i8);
67impl_trivial_min_max!(i16);
68impl_trivial_min_max!(i32);
69impl_trivial_min_max!(i64);
70impl_trivial_min_max!(i128);
71impl_trivial_min_max!(isize);
72impl_trivial_min_max!(char);
73impl_trivial_min_max!(&str);
74impl_trivial_min_max!(&[u8]);
75impl_trivial_min_max!(String);
76
77macro_rules! impl_float_min_max {
78    ($T: ty) => {
79        impl MinMax for $T {
80            #[inline(always)]
81            fn nan_min_lt(&self, other: &Self) -> bool {
82                !(other.is_nan() | (self >= other))
83            }
84
85            #[inline(always)]
86            fn nan_max_lt(&self, other: &Self) -> bool {
87                !(self.is_nan() | (self >= other))
88            }
89
90            #[inline(always)]
91            fn min_ignore_nan(self, other: Self) -> Self {
92                <$T>::min(self, other)
93            }
94
95            #[inline(always)]
96            fn max_ignore_nan(self, other: Self) -> Self {
97                <$T>::max(self, other)
98            }
99
100            #[inline(always)]
101            fn min_propagate_nan(self, other: Self) -> Self {
102                if (self < other) | self.is_nan() {
103                    self
104                } else {
105                    other
106                }
107            }
108
109            #[inline(always)]
110            fn max_propagate_nan(self, other: Self) -> Self {
111                if (self > other) | self.is_nan() {
112                    self
113                } else {
114                    other
115                }
116            }
117        }
118    };
119}
120
121impl_float_min_max!(pf16);
122impl_float_min_max!(f32);
123impl_float_min_max!(f64);
124
125pub trait MinMaxPolicy {
126    // Is the first argument strictly better than the second, per the policy?
127    fn is_better<T: MinMax>(a: &T, b: &T) -> bool;
128    fn best<T: MinMax>(a: T, b: T) -> T;
129}
130
131#[derive(Copy, Clone, Debug)]
132pub struct MinIgnoreNan;
133impl MinMaxPolicy for MinIgnoreNan {
134    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
135        T::nan_max_lt(a, b)
136    }
137
138    fn best<T: MinMax>(a: T, b: T) -> T {
139        T::min_ignore_nan(a, b)
140    }
141}
142
143#[derive(Copy, Clone, Debug)]
144pub struct MinPropagateNan;
145impl MinMaxPolicy for MinPropagateNan {
146    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
147        T::nan_min_lt(a, b)
148    }
149
150    fn best<T: MinMax>(a: T, b: T) -> T {
151        T::min_propagate_nan(a, b)
152    }
153}
154
155#[derive(Copy, Clone, Debug)]
156pub struct MaxIgnoreNan;
157impl MinMaxPolicy for MaxIgnoreNan {
158    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
159        T::nan_min_lt(b, a)
160    }
161
162    fn best<T: MinMax>(a: T, b: T) -> T {
163        T::max_ignore_nan(a, b)
164    }
165}
166
167#[derive(Copy, Clone, Debug)]
168pub struct MaxPropagateNan;
169impl MinMaxPolicy for MaxPropagateNan {
170    fn is_better<T: MinMax>(a: &T, b: &T) -> bool {
171        T::nan_max_lt(b, a)
172    }
173
174    fn best<T: MinMax>(a: T, b: T) -> T {
175        T::max_propagate_nan(a, b)
176    }
177}