lilliput_float/
cmp.rs

1use std::cmp::Ordering;
2
3use crate::{
4    bits::FpToBits,
5    floats::{F16, F24, F32, F40, F48, F56, F64, F8},
6    repr::FpRepr,
7};
8
9// Adapted from rustc's compiler-builtins:
10// https://github.com/rust-lang/compiler-builtins/blob/3dea633a80d32da75e923a940d16ce98cce74822/src/float/cmp.rs
11
12macro_rules! impl_float_partial_eq_and_ord {
13    ($t:ty => unsigned: $unsigned:ty, signed: $signed:ty) => {
14        impl PartialEq for $t {
15            fn eq(&self, other: &Self) -> bool {
16                self.partial_cmp(other) == Some(Ordering::Equal)
17            }
18        }
19
20        impl PartialOrd for $t {
21            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
22                let sign_bit = <$t>::SIGN_MASK;
23                let abs_mask = sign_bit - 1;
24                let exponent_mask = <$t>::EXPONENT_MASK;
25                let inf_rep = exponent_mask;
26
27                let lhs = *self;
28                let rhs = *other;
29
30                let lhs_bits = lhs.to_bits();
31                let rhs_bits = rhs.to_bits();
32                let lhs_abs = lhs_bits & abs_mask;
33                let rhs_abs = rhs_bits & abs_mask;
34
35                // If either a or b is NaN, they are unordered.
36                if lhs_abs > inf_rep || rhs_abs > inf_rep {
37                    return None;
38                }
39
40                // If a and b are both zeros, they are equal.
41                if (lhs_abs | rhs_abs) == 0 {
42                    return Some(Ordering::Equal);
43                }
44
45                let lhs_srep: $signed = lhs_bits as $signed;
46                let rhs_srep: $signed = rhs_bits as $signed;
47
48                // If at least one of a and b is positive, we get the same result comparing
49                // a and b as signed integers as we would with a fp_ting-point compare.
50                if (lhs_srep & rhs_srep) >= 0 {
51                    if lhs_srep < rhs_srep {
52                        Some(Ordering::Less)
53                    } else if lhs_srep == rhs_srep {
54                        Some(Ordering::Equal)
55                    } else {
56                        Some(Ordering::Greater)
57                    }
58                    // Otherwise, both are negative, so we need to flip the sense of the
59                    // comparison to get the correct result.  (This assumes a twos- or ones-
60                    // complement integer representation; if integers are represented in a
61                    // sign-magnitude representation, then this flip is incorrect).
62                } else if lhs_srep > rhs_srep {
63                    Some(Ordering::Less)
64                } else if lhs_srep == rhs_srep {
65                    Some(Ordering::Equal)
66                } else {
67                    Some(Ordering::Greater)
68                }
69            }
70        }
71    };
72}
73
74impl_float_partial_eq_and_ord!(F8 => unsigned: u8, signed: i8);
75impl_float_partial_eq_and_ord!(F16 => unsigned: u16, signed: i16);
76impl_float_partial_eq_and_ord!(F24 => unsigned: u32, signed: i32);
77impl_float_partial_eq_and_ord!(F32 => unsigned: u32, signed: i32);
78impl_float_partial_eq_and_ord!(F40 => unsigned: u64, signed: i64);
79impl_float_partial_eq_and_ord!(F48 => unsigned: u64, signed: i64);
80impl_float_partial_eq_and_ord!(F56 => unsigned: u64, signed: i64);
81impl_float_partial_eq_and_ord!(F64 => unsigned: u64, signed: i64);
82
83#[cfg(test)]
84mod tests {
85    use proptest::prelude::*;
86
87    use super::*;
88
89    proptest! {
90        #[test]
91        fn f32_matches_native_behavior(native_lhs in f32::arbitrary(), native_rhs in f32::arbitrary()) {
92            let (lhs, rhs) = (F32::from(native_lhs), F32::from(native_rhs));
93            let actual = lhs.partial_cmp(&rhs);
94            let expected = native_lhs.partial_cmp(&native_rhs);
95            prop_assert_eq!(actual, expected);
96        }
97
98        #[test]
99        fn f64_matches_native_behavior(native_lhs in f64::arbitrary(), native_rhs in f64::arbitrary()) {
100            let (lhs, rhs) = (F64::from(native_lhs), F64::from(native_rhs));
101            let actual = lhs.partial_cmp(&rhs);
102            let expected = native_lhs.partial_cmp(&native_rhs);
103            prop_assert_eq!(actual, expected);
104        }
105    }
106}