Skip to main content

polars_utils/
total_ord.rs

1use std::cmp::Ordering;
2use std::hash::{BuildHasher, Hash, Hasher};
3
4use bytemuck::TransparentWrapper;
5use num_traits::Zero;
6
7use crate::float16::pf16;
8use crate::hashing::{BytesHash, DirtyHash};
9use crate::nulls::IsNull;
10
11/// Converts an pf16 into a canonical form, where -0 == 0 and all NaNs map to
12/// the same value.
13#[inline]
14pub fn canonical_f16(x: pf16) -> pf16 {
15    // -0.0 + 0.0 becomes 0.0.
16    let convert_zero = x + pf16::zero(); // zero out the sign bit if the f16 is zero.
17    if convert_zero.is_nan() {
18        pf16(half::f16::from_bits(0x7e00)) // Canonical quiet NaN.
19    } else {
20        convert_zero
21    }
22}
23
24/// Converts an f32 into a canonical form, where -0 == 0 and all NaNs map to
25/// the same value.
26#[inline]
27pub fn canonical_f32(x: f32) -> f32 {
28    // -0.0 + 0.0 becomes 0.0.
29    let convert_zero = x + 0.0;
30    if convert_zero.is_nan() {
31        f32::from_bits(0x7fc00000) // Canonical quiet NaN.
32    } else {
33        convert_zero
34    }
35}
36
37/// Converts an f64 into a canonical form, where -0 == 0 and all NaNs map to
38/// the same value.
39#[inline]
40pub fn canonical_f64(x: f64) -> f64 {
41    // -0.0 + 0.0 becomes 0.0.
42    let convert_zero = x + 0.0;
43    if convert_zero.is_nan() {
44        f64::from_bits(0x7ff8000000000000) // Canonical quiet NaN.
45    } else {
46        convert_zero
47    }
48}
49
50/// Alternative trait for Eq. By consistently using this we can still be
51/// generic w.r.t Eq while getting a total ordering for floats.
52pub trait TotalEq {
53    fn tot_eq(&self, other: &Self) -> bool;
54
55    #[inline]
56    fn tot_ne(&self, other: &Self) -> bool {
57        !(self.tot_eq(other))
58    }
59}
60
61/// Alternative trait for Ord. By consistently using this we can still be
62/// generic w.r.t Ord while getting a total ordering for floats.
63pub trait TotalOrd: TotalEq {
64    fn tot_cmp(&self, other: &Self) -> Ordering;
65
66    #[inline]
67    fn tot_lt(&self, other: &Self) -> bool {
68        self.tot_cmp(other) == Ordering::Less
69    }
70
71    #[inline]
72    fn tot_gt(&self, other: &Self) -> bool {
73        self.tot_cmp(other) == Ordering::Greater
74    }
75
76    #[inline]
77    fn tot_le(&self, other: &Self) -> bool {
78        self.tot_cmp(other) != Ordering::Greater
79    }
80
81    #[inline]
82    fn tot_ge(&self, other: &Self) -> bool {
83        self.tot_cmp(other) != Ordering::Less
84    }
85}
86
87/// Alternative trait for Hash. By consistently using this we can still be
88/// generic w.r.t Hash while being able to hash floats.
89pub trait TotalHash {
90    fn tot_hash<H>(&self, state: &mut H)
91    where
92        H: Hasher;
93
94    fn tot_hash_slice<H>(data: &[Self], state: &mut H)
95    where
96        H: Hasher,
97        Self: Sized,
98    {
99        for piece in data {
100            piece.tot_hash(state)
101        }
102    }
103}
104
105pub trait BuildHasherTotalExt: BuildHasher {
106    fn tot_hash_one<T>(&self, x: T) -> u64
107    where
108        T: TotalHash,
109        Self: Sized,
110        <Self as BuildHasher>::Hasher: Hasher,
111    {
112        let mut hasher = self.build_hasher();
113        x.tot_hash(&mut hasher);
114        hasher.finish()
115    }
116}
117
118impl<T: BuildHasher> BuildHasherTotalExt for T {}
119
120#[derive(Debug)]
121#[cfg_attr(
122    feature = "serde",
123    derive(serde::Serialize, serde::Deserialize),
124    serde(transparent)
125)]
126#[cfg_attr(
127    feature = "dsl-schema",
128    derive(schemars::JsonSchema),
129    schemars(transparent)
130)]
131#[repr(transparent)]
132pub struct TotalOrdWrap<T>(pub T);
133unsafe impl<T> TransparentWrapper<T> for TotalOrdWrap<T> {}
134
135impl<T: TotalOrd> PartialOrd for TotalOrdWrap<T> {
136    #[inline(always)]
137    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
138        Some(self.cmp(other))
139    }
140
141    #[inline(always)]
142    fn lt(&self, other: &Self) -> bool {
143        self.0.tot_lt(&other.0)
144    }
145
146    #[inline(always)]
147    fn le(&self, other: &Self) -> bool {
148        self.0.tot_le(&other.0)
149    }
150
151    #[inline(always)]
152    fn gt(&self, other: &Self) -> bool {
153        self.0.tot_gt(&other.0)
154    }
155
156    #[inline(always)]
157    fn ge(&self, other: &Self) -> bool {
158        self.0.tot_ge(&other.0)
159    }
160}
161
162impl<T: TotalOrd> Ord for TotalOrdWrap<T> {
163    #[inline(always)]
164    fn cmp(&self, other: &Self) -> Ordering {
165        self.0.tot_cmp(&other.0)
166    }
167}
168
169impl<T: TotalEq> PartialEq for TotalOrdWrap<T> {
170    #[inline(always)]
171    fn eq(&self, other: &Self) -> bool {
172        self.0.tot_eq(&other.0)
173    }
174
175    #[inline(always)]
176    #[allow(clippy::partialeq_ne_impl)]
177    fn ne(&self, other: &Self) -> bool {
178        self.0.tot_ne(&other.0)
179    }
180}
181
182impl<T: TotalEq> Eq for TotalOrdWrap<T> {}
183
184impl<T: TotalHash> Hash for TotalOrdWrap<T> {
185    #[inline(always)]
186    fn hash<H: Hasher>(&self, state: &mut H) {
187        self.0.tot_hash(state);
188    }
189}
190
191impl<T: Clone> Clone for TotalOrdWrap<T> {
192    #[inline]
193    fn clone(&self) -> Self {
194        Self(self.0.clone())
195    }
196}
197
198impl<T: Copy> Copy for TotalOrdWrap<T> {}
199
200impl<T: IsNull> IsNull for TotalOrdWrap<T> {
201    const HAS_NULLS: bool = T::HAS_NULLS;
202    type Inner = T::Inner;
203
204    #[inline(always)]
205    fn is_null(&self) -> bool {
206        self.0.is_null()
207    }
208
209    #[inline(always)]
210    fn unwrap_inner(self) -> Self::Inner {
211        self.0.unwrap_inner()
212    }
213}
214
215impl DirtyHash for pf16 {
216    #[inline(always)]
217    fn dirty_hash(&self) -> u64 {
218        canonical_f16(*self).0.to_bits().dirty_hash()
219    }
220}
221
222impl DirtyHash for f32 {
223    #[inline(always)]
224    fn dirty_hash(&self) -> u64 {
225        canonical_f32(*self).to_bits().dirty_hash()
226    }
227}
228
229impl DirtyHash for f64 {
230    #[inline(always)]
231    fn dirty_hash(&self) -> u64 {
232        canonical_f64(*self).to_bits().dirty_hash()
233    }
234}
235
236impl<T: DirtyHash> DirtyHash for TotalOrdWrap<T> {
237    #[inline(always)]
238    fn dirty_hash(&self) -> u64 {
239        self.0.dirty_hash()
240    }
241}
242
243macro_rules! impl_trivial_total {
244    ($T: ty) => {
245        impl TotalEq for $T {
246            #[inline(always)]
247            fn tot_eq(&self, other: &Self) -> bool {
248                self == other
249            }
250
251            #[inline(always)]
252            fn tot_ne(&self, other: &Self) -> bool {
253                self != other
254            }
255        }
256
257        impl TotalOrd for $T {
258            #[inline(always)]
259            fn tot_cmp(&self, other: &Self) -> Ordering {
260                self.cmp(other)
261            }
262
263            #[inline(always)]
264            fn tot_lt(&self, other: &Self) -> bool {
265                self < other
266            }
267
268            #[inline(always)]
269            fn tot_gt(&self, other: &Self) -> bool {
270                self > other
271            }
272
273            #[inline(always)]
274            fn tot_le(&self, other: &Self) -> bool {
275                self <= other
276            }
277
278            #[inline(always)]
279            fn tot_ge(&self, other: &Self) -> bool {
280                self >= other
281            }
282        }
283
284        impl TotalHash for $T {
285            #[inline(always)]
286            fn tot_hash<H>(&self, state: &mut H)
287            where
288                H: Hasher,
289            {
290                self.hash(state);
291            }
292        }
293    };
294}
295
296// We can't do a blanket impl because Rust complains f32 might implement
297// Ord / Eq someday.
298impl_trivial_total!(());
299impl_trivial_total!(bool);
300impl_trivial_total!(u8);
301impl_trivial_total!(u16);
302impl_trivial_total!(u32);
303impl_trivial_total!(u64);
304impl_trivial_total!(u128);
305impl_trivial_total!(usize);
306impl_trivial_total!(i8);
307impl_trivial_total!(i16);
308impl_trivial_total!(i32);
309impl_trivial_total!(i64);
310impl_trivial_total!(i128);
311impl_trivial_total!(isize);
312impl_trivial_total!(char);
313impl_trivial_total!(&str);
314impl_trivial_total!(&[u8]);
315impl_trivial_total!(String);
316impl_trivial_total!(Vec<u8>);
317
318macro_rules! impl_float_eq_ord {
319    ($T:ty) => {
320        impl TotalEq for $T {
321            #[inline]
322            fn tot_eq(&self, other: &Self) -> bool {
323                if self.is_nan() {
324                    other.is_nan()
325                } else {
326                    self == other
327                }
328            }
329        }
330
331        impl TotalOrd for $T {
332            #[inline(always)]
333            fn tot_cmp(&self, other: &Self) -> Ordering {
334                if self.tot_lt(other) {
335                    Ordering::Less
336                } else if self.tot_gt(other) {
337                    Ordering::Greater
338                } else {
339                    Ordering::Equal
340                }
341            }
342
343            #[inline(always)]
344            fn tot_lt(&self, other: &Self) -> bool {
345                !self.tot_ge(other)
346            }
347
348            #[inline(always)]
349            fn tot_gt(&self, other: &Self) -> bool {
350                other.tot_lt(self)
351            }
352
353            #[inline(always)]
354            fn tot_le(&self, other: &Self) -> bool {
355                other.tot_ge(self)
356            }
357
358            #[inline(always)]
359            fn tot_ge(&self, other: &Self) -> bool {
360                // We consider all NaNs equal, and NaN is the largest possible
361                // value. Thus if self is NaN we always return true. Otherwise
362                // self >= other is correct. If other is not NaN it is trivially
363                // correct, and if it is we note that nothing can be greater or
364                // equal to NaN except NaN itself, which we already handled earlier.
365                self.is_nan() | (self >= other)
366            }
367        }
368    };
369}
370
371impl_float_eq_ord!(pf16);
372impl_float_eq_ord!(f32);
373impl_float_eq_ord!(f64);
374
375impl TotalHash for pf16 {
376    #[inline(always)]
377    fn tot_hash<H>(&self, state: &mut H)
378    where
379        H: Hasher,
380    {
381        canonical_f16(*self).to_bits().hash(state)
382    }
383}
384
385impl TotalHash for f32 {
386    #[inline(always)]
387    fn tot_hash<H>(&self, state: &mut H)
388    where
389        H: Hasher,
390    {
391        canonical_f32(*self).to_bits().hash(state)
392    }
393}
394
395impl TotalHash for f64 {
396    #[inline(always)]
397    fn tot_hash<H>(&self, state: &mut H)
398    where
399        H: Hasher,
400    {
401        canonical_f64(*self).to_bits().hash(state)
402    }
403}
404
405// Blanket implementations.
406impl<T: TotalEq> TotalEq for Option<T> {
407    #[inline(always)]
408    fn tot_eq(&self, other: &Self) -> bool {
409        match (self, other) {
410            (None, None) => true,
411            (Some(a), Some(b)) => a.tot_eq(b),
412            _ => false,
413        }
414    }
415
416    #[inline(always)]
417    fn tot_ne(&self, other: &Self) -> bool {
418        match (self, other) {
419            (None, None) => false,
420            (Some(a), Some(b)) => a.tot_ne(b),
421            _ => true,
422        }
423    }
424}
425
426impl<T: TotalOrd> TotalOrd for Option<T> {
427    #[inline(always)]
428    fn tot_cmp(&self, other: &Self) -> Ordering {
429        match (self, other) {
430            (None, None) => Ordering::Equal,
431            (None, Some(_)) => Ordering::Less,
432            (Some(_), None) => Ordering::Greater,
433            (Some(a), Some(b)) => a.tot_cmp(b),
434        }
435    }
436
437    #[inline(always)]
438    fn tot_lt(&self, other: &Self) -> bool {
439        match (self, other) {
440            (None, Some(_)) => true,
441            (Some(a), Some(b)) => a.tot_lt(b),
442            _ => false,
443        }
444    }
445
446    #[inline(always)]
447    fn tot_gt(&self, other: &Self) -> bool {
448        other.tot_lt(self)
449    }
450
451    #[inline(always)]
452    fn tot_le(&self, other: &Self) -> bool {
453        match (self, other) {
454            (Some(_), None) => false,
455            (Some(a), Some(b)) => a.tot_lt(b),
456            _ => true,
457        }
458    }
459
460    #[inline(always)]
461    fn tot_ge(&self, other: &Self) -> bool {
462        other.tot_le(self)
463    }
464}
465
466impl<T: TotalHash> TotalHash for Option<T> {
467    #[inline]
468    fn tot_hash<H>(&self, state: &mut H)
469    where
470        H: Hasher,
471    {
472        self.is_some().tot_hash(state);
473        if let Some(slf) = self {
474            slf.tot_hash(state)
475        }
476    }
477}
478
479impl<T: TotalEq + ?Sized> TotalEq for &T {
480    #[inline(always)]
481    fn tot_eq(&self, other: &Self) -> bool {
482        (*self).tot_eq(*other)
483    }
484
485    #[inline(always)]
486    fn tot_ne(&self, other: &Self) -> bool {
487        (*self).tot_ne(*other)
488    }
489}
490
491impl<T: TotalHash + ?Sized> TotalHash for &T {
492    #[inline(always)]
493    fn tot_hash<H>(&self, state: &mut H)
494    where
495        H: Hasher,
496    {
497        (*self).tot_hash(state)
498    }
499}
500
501impl<T: TotalEq, U: TotalEq> TotalEq for (T, U) {
502    #[inline]
503    fn tot_eq(&self, other: &Self) -> bool {
504        self.0.tot_eq(&other.0) && self.1.tot_eq(&other.1)
505    }
506}
507
508impl<T: TotalOrd, U: TotalOrd> TotalOrd for (T, U) {
509    #[inline]
510    fn tot_cmp(&self, other: &Self) -> Ordering {
511        self.0
512            .tot_cmp(&other.0)
513            .then_with(|| self.1.tot_cmp(&other.1))
514    }
515}
516
517impl TotalHash for BytesHash<'_> {
518    #[inline(always)]
519    fn tot_hash<H>(&self, state: &mut H)
520    where
521        H: Hasher,
522    {
523        self.hash(state)
524    }
525}
526
527impl TotalEq for BytesHash<'_> {
528    #[inline(always)]
529    fn tot_eq(&self, other: &Self) -> bool {
530        self == other
531    }
532}
533
534/// This elides creating a [`TotalOrdWrap`] for types that don't need it.
535pub trait ToTotalOrd {
536    type TotalOrdItem: Hash + Eq;
537    type SourceItem;
538
539    fn to_total_ord(&self) -> Self::TotalOrdItem;
540
541    fn peel_total_ord(ord_item: Self::TotalOrdItem) -> Self::SourceItem;
542}
543
544macro_rules! impl_to_total_ord_identity {
545    ($T: ty) => {
546        impl ToTotalOrd for $T {
547            type TotalOrdItem = $T;
548            type SourceItem = $T;
549
550            #[inline]
551            fn to_total_ord(&self) -> Self::TotalOrdItem {
552                self.clone()
553            }
554
555            #[inline]
556            fn peel_total_ord(ord_item: Self::TotalOrdItem) -> Self::SourceItem {
557                ord_item
558            }
559        }
560    };
561}
562
563impl_to_total_ord_identity!(bool);
564impl_to_total_ord_identity!(u8);
565impl_to_total_ord_identity!(u16);
566impl_to_total_ord_identity!(u32);
567impl_to_total_ord_identity!(u64);
568impl_to_total_ord_identity!(u128);
569impl_to_total_ord_identity!(usize);
570impl_to_total_ord_identity!(i8);
571impl_to_total_ord_identity!(i16);
572impl_to_total_ord_identity!(i32);
573impl_to_total_ord_identity!(i64);
574impl_to_total_ord_identity!(i128);
575impl_to_total_ord_identity!(isize);
576impl_to_total_ord_identity!(char);
577impl_to_total_ord_identity!(String);
578
579macro_rules! impl_to_total_ord_lifetimed_ref_identity {
580    ($T: ty) => {
581        impl<'a> ToTotalOrd for &'a $T {
582            type TotalOrdItem = &'a $T;
583            type SourceItem = &'a $T;
584
585            #[inline]
586            fn to_total_ord(&self) -> Self::TotalOrdItem {
587                *self
588            }
589
590            #[inline]
591            fn peel_total_ord(ord_item: Self::TotalOrdItem) -> Self::SourceItem {
592                ord_item
593            }
594        }
595    };
596}
597
598impl_to_total_ord_lifetimed_ref_identity!(str);
599impl_to_total_ord_lifetimed_ref_identity!([u8]);
600
601macro_rules! impl_to_total_ord_wrapped {
602    ($T: ty) => {
603        impl ToTotalOrd for $T {
604            type TotalOrdItem = TotalOrdWrap<$T>;
605            type SourceItem = $T;
606
607            #[inline]
608            fn to_total_ord(&self) -> Self::TotalOrdItem {
609                TotalOrdWrap(self.clone())
610            }
611
612            #[inline]
613            fn peel_total_ord(ord_item: Self::TotalOrdItem) -> Self::SourceItem {
614                ord_item.0
615            }
616        }
617    };
618}
619
620impl_to_total_ord_wrapped!(pf16);
621impl_to_total_ord_wrapped!(f32);
622impl_to_total_ord_wrapped!(f64);
623
624/// This is safe without needing to map the option value to TotalOrdWrap, since
625/// for example:
626/// `TotalOrdWrap<Option<T>>` implements `Eq + Hash`, iff:
627/// `Option<T>` implements `TotalEq + TotalHash`, iff:
628/// `T` implements `TotalEq + TotalHash`
629impl<T: Copy + TotalEq + TotalHash> ToTotalOrd for Option<T> {
630    type TotalOrdItem = TotalOrdWrap<Option<T>>;
631    type SourceItem = Option<T>;
632
633    #[inline]
634    fn to_total_ord(&self) -> Self::TotalOrdItem {
635        TotalOrdWrap(*self)
636    }
637
638    #[inline]
639    fn peel_total_ord(ord_item: Self::TotalOrdItem) -> Self::SourceItem {
640        ord_item.0
641    }
642}
643
644impl<T: ToTotalOrd> ToTotalOrd for &T {
645    type TotalOrdItem = T::TotalOrdItem;
646    type SourceItem = T::SourceItem;
647
648    #[inline]
649    fn to_total_ord(&self) -> Self::TotalOrdItem {
650        (*self).to_total_ord()
651    }
652
653    #[inline]
654    fn peel_total_ord(ord_item: Self::TotalOrdItem) -> Self::SourceItem {
655        T::peel_total_ord(ord_item)
656    }
657}
658
659impl<'a> ToTotalOrd for BytesHash<'a> {
660    type TotalOrdItem = BytesHash<'a>;
661    type SourceItem = BytesHash<'a>;
662
663    #[inline]
664    fn to_total_ord(&self) -> Self::TotalOrdItem {
665        *self
666    }
667
668    #[inline]
669    fn peel_total_ord(ord_item: Self::TotalOrdItem) -> Self::SourceItem {
670        ord_item
671    }
672}