almost/
imp.rs

1
2// This is gross but it's also a big pain to write this via a trait...
3
4macro_rules! impl_equals {
5    ($fp:ident, $bits:ident, $SIGNIFICAND_SIZE:expr) => {
6        const SIGNIFICAND_SIZE: $bits = $SIGNIFICAND_SIZE;
7        const EXPONENT_SIZE: $bits = (core::mem::size_of::<$fp>() as $bits) * 8 - SIGNIFICAND_SIZE - 1;
8        const EXPONENT_MASK: $bits = ((1 << EXPONENT_SIZE) - 1) << SIGNIFICAND_SIZE;
9        const EXPONENT_BIAS: $bits = (1 << (EXPONENT_SIZE - 1)) - 1;
10
11        const SIGN_BIT: $bits = 1 << (core::mem::size_of::<$fp>() as $bits * 8 - 1);
12
13        // abs requires std? ugh.
14        #[inline]
15        pub(crate) fn abs(f: $fp) -> $fp {
16            $fp::from_bits(f.to_bits() & !SIGN_BIT)
17        }
18
19        #[inline]
20        pub(crate) fn eq_with_tol_impl(lhs: $fp, rhs: $fp, tol: $fp) -> bool {
21            let left_mag = abs(lhs);
22            let right_mag = abs(rhs);
23            if !((left_mag < core::$fp::INFINITY) & (right_mag < core::$fp::INFINITY)) {
24                handle_not_finite(lhs, rhs, tol)
25            } else {
26                let scale = if left_mag > right_mag {
27                    left_mag
28                } else {
29                    right_mag
30                };
31                // If both left_mag and right_mag are subnormal, rescale to
32                // MIN_POSITIVE instead, which is what they round against anyway.
33                let scale = if scale > core::$fp::MIN_POSITIVE {
34                    scale
35                } else {
36                    core::$fp::MIN_POSITIVE
37                };
38                let abs_tol = tol * scale;
39                abs(lhs - rhs) < abs_tol
40            }
41        }
42
43        #[cold]
44        #[inline(never)]
45        fn handle_not_finite(lhs: $fp, rhs: $fp, tol: $fp) -> bool {
46            if lhs.is_nan() || rhs.is_nan() {
47                false
48            } else if lhs.is_infinite() && rhs.is_infinite() {
49                lhs == rhs
50            } else {
51                // One of `rhs` or `lhs` are infinite, and the other is not.
52                // They still might be within the requested tolerance, so we
53                // rescale both so that we can do that.
54
55                // ensure lhs is the infinite one.
56                let (lhs, rhs) = if lhs.is_infinite() { (lhs, rhs) } else { (rhs, lhs) };
57                debug_assert!(rhs.is_finite() && lhs.is_infinite(), "logic bug {} {} {:x} {:x}", lhs, rhs, lhs.to_bits(), rhs.to_bits());
58                let rbits = rhs.to_bits();
59                if (rbits & EXPONENT_MASK) == 0 {
60                    // subnormal, so clearly not equal to infinity, and would
61                    // otherwise need special casing below.
62                    return false;
63                }
64                // XXX: does rust turn this into a constant like it should?
65                let max_float_binade_bits = core::$fp::MAX.to_bits() & EXPONENT_MASK;
66                // copysign requires std, so just build directly.
67                let new_lhs = $fp::from_bits(max_float_binade_bits | (lhs.to_bits() & SIGN_BIT));
68
69                let rhs_rescale = $fp::from_bits((EXPONENT_BIAS - 1) << SIGNIFICAND_SIZE);
70                let new_rhs = rhs * rhs_rescale;
71
72                eq_with_tol_impl(new_lhs, new_rhs, tol)
73            }
74        }
75
76    };
77}
78
79pub(crate) mod f32 {
80    impl_equals!(f32, u32, 23);
81}
82
83
84pub(crate) mod f64 {
85    impl_equals!(f64, u64, 52);
86}