1
//! Utilities to compare floating-point numbers.
2

            
3
use float_cmp::ApproxEq;
4

            
5
// The following are copied from cairo/src/{cairo-fixed-private.h,
6
// cairo-fixed-type-private.h}
7

            
8
const CAIRO_FIXED_FRAC_BITS: u64 = 8;
9
const CAIRO_MAGIC_NUMBER_FIXED: f64 = (1u64 << (52 - CAIRO_FIXED_FRAC_BITS)) as f64 * 1.5;
10

            
11
const CAIRO_FIXED_MAX: i32 = i32::MAX;
12
const CAIRO_FIXED_MIN: i32 = i32::MIN;
13

            
14
/// The double that corresponds to (the number one in fixed-point representation)
15
const CAIRO_FIXED_ONE_DOUBLE: f64 = (1 << CAIRO_FIXED_FRAC_BITS) as f64;
16

            
17
/// The largest representable fixed-point number, as a double.  This is a bit over 8 million.
18
pub const CAIRO_FIXED_MAX_DOUBLE: f64 = (CAIRO_FIXED_MAX as f64) / CAIRO_FIXED_ONE_DOUBLE;
19

            
20
/// The most negative representable fixed-point number, as a double.  This is a bit less than -8 million.
21
pub const CAIRO_FIXED_MIN_DOUBLE: f64 = (CAIRO_FIXED_MIN as f64) / CAIRO_FIXED_ONE_DOUBLE;
22

            
23
6
fn cairo_magic_double(d: f64) -> f64 {
24
6
    d + CAIRO_MAGIC_NUMBER_FIXED
25
6
}
26

            
27
6
fn cairo_fixed_from_double(d: f64) -> i32 {
28
6
    let bits = cairo_magic_double(d).to_bits();
29
6
    let lower = bits & 0xffffffff;
30
6
    lower as i32
31
6
}
32

            
33
/// Implements a method to check whether two `f64` numbers would have
34
/// the same fixed-point representation in Cairo.
35
///
36
/// This generally means that the absolute difference between them,
37
/// when taken as floating-point numbers, is less than the smallest
38
/// representable fraction that Cairo can represent in fixed-point.
39
///
40
/// Implementation detail: Cairo fixed-point numbers use 24 bits for
41
/// the integral part, and 8 bits for the fractional part.  That is,
42
/// the smallest fraction they can represent is 1/256.
43
pub trait FixedEqCairo {
44
    #[allow(dead_code)] // https://github.com/rust-lang/rust/issues/120770
45
    fn fixed_eq_cairo(&self, other: &Self) -> bool;
46
}
47

            
48
impl FixedEqCairo for f64 {
49
3
    fn fixed_eq_cairo(&self, other: &f64) -> bool {
50
        // FIXME: Here we have the same problem as Cairo itself: we
51
        // don't check for overflow in the conversion of double to
52
        // fixed-point.
53
3
        cairo_fixed_from_double(*self) == cairo_fixed_from_double(*other)
54
3
    }
55
}
56

            
57
/// Checks whether two floating-point numbers are approximately equal,
58
/// considering Cairo's limitations on numeric representation.
59
///
60
/// Cairo uses fixed-point numbers internally.  We implement this
61
/// trait for `f64`, so that two numbers can be considered "close
62
/// enough to equal" if their absolute difference is smaller than the
63
/// smallest fixed-point fraction that Cairo can represent.
64
///
65
/// Note that this trait is reliable even if the given numbers are
66
/// outside of the range that Cairo's fixed-point numbers can
67
/// represent.  In that case, we check for the absolute difference,
68
/// and finally allow a difference of 1 unit-in-the-last-place (ULP)
69
/// for very large f64 values.
70
pub trait ApproxEqCairo: ApproxEq {
71
    fn approx_eq_cairo(self, other: Self) -> bool;
72
}
73

            
74
impl ApproxEqCairo for f64 {
75
2956102
    fn approx_eq_cairo(self, other: f64) -> bool {
76
2956102
        let cairo_smallest_fraction = 1.0 / f64::from(1 << CAIRO_FIXED_FRAC_BITS);
77
2956102
        self.approx_eq(other, (cairo_smallest_fraction, 1))
78
2956102
    }
79
}
80

            
81
// Macro for usage in unit tests
82
#[doc(hidden)]
83
#[macro_export]
84
macro_rules! assert_approx_eq_cairo {
85
    ($left:expr, $right:expr) => {{
86
        match ($left, $right) {
87
2
            (l, r) => {
88
2
                if !l.approx_eq_cairo(r) {
89
1
                    panic!(
90
                        r#"assertion failed: `(left == right)`
91
  left: `{:?}`,
92
 right: `{:?}`"#,
93
                        l, r
94
                    )
95
                }
96
            }
97
        }
98
    }};
99
}
100

            
101
#[cfg(test)]
102
mod tests {
103
    use super::*;
104

            
105
    #[test]
106
2
    fn numbers_equal_in_cairo_fixed_point() {
107
1
        assert!(1.0_f64.fixed_eq_cairo(&1.0_f64));
108

            
109
1
        assert!(1.0_f64.fixed_eq_cairo(&1.001953125_f64)); // 1 + 1/512 - cairo rounds to 1
110

            
111
1
        assert!(!1.0_f64.fixed_eq_cairo(&1.00390625_f64)); // 1 + 1/256 - cairo can represent it
112
2
    }
113

            
114
    #[test]
115
2
    fn numbers_approx_equal() {
116
        // 0 == 1/256 - cairo can represent it, so not equal
117
1
        assert!(!0.0_f64.approx_eq_cairo(0.00390635_f64));
118

            
119
        // 1 == 1 + 1/256 - cairo can represent it, so not equal
120
1
        assert!(!1.0_f64.approx_eq_cairo(1.00390635_f64));
121

            
122
        // 0 == 1/256 - cairo can represent it, so not equal
123
1
        assert!(!0.0_f64.approx_eq_cairo(-0.00390635_f64));
124

            
125
        // 1 == 1 - 1/256 - cairo can represent it, so not equal
126
1
        assert!(!1.0_f64.approx_eq_cairo(0.99609365_f64));
127

            
128
        // 0 == 1/512 - cairo approximates to 0, so equal
129
1
        assert!(0.0_f64.approx_eq_cairo(0.001953125_f64));
130

            
131
        // 1 == 1 + 1/512 - cairo approximates to 1, so equal
132
1
        assert!(1.0_f64.approx_eq_cairo(1.001953125_f64));
133

            
134
        // 0 == -1/512 - cairo approximates to 0, so equal
135
1
        assert!(0.0_f64.approx_eq_cairo(-0.001953125_f64));
136

            
137
        // 1 == 1 - 1/512 - cairo approximates to 1, so equal
138
1
        assert!(1.0_f64.approx_eq_cairo(0.998046875_f64));
139

            
140
        // This is 2^53 compared to (2^53 + 2).  When represented as
141
        // f64, they are 1 unit-in-the-last-place (ULP) away from each
142
        // other, since the mantissa has 53 bits (52 bits plus 1
143
        // "hidden" bit).  The first number is an exact double, and
144
        // the second one is the next biggest double.  We consider a
145
        // difference of 1 ULP to mean that numbers are "equal", to
146
        // account for slight imprecision in floating-point
147
        // calculations.  Most of the time, for small values, we will
148
        // be using the cairo_smallest_fraction from the
149
        // implementation of approx_eq_cairo() above.  For large
150
        // values, we want the ULPs.
151
        //
152
        // In the second assertion, we compare 2^53 with (2^53 + 4).  Those are
153
        // 2 ULPs away, and we don't consider them equal.
154
1
        assert!(9_007_199_254_740_992.0.approx_eq_cairo(9_007_199_254_740_994.0));
155
1
        assert!(!9_007_199_254_740_992.0.approx_eq_cairo(9_007_199_254_740_996.0));
156
2
    }
157

            
158
    #[test]
159
2
    fn assert_approx_eq_cairo_should_not_panic() {
160
        assert_approx_eq_cairo!(42_f64, 42_f64);
161
2
    }
162

            
163
    #[test]
164
    #[should_panic]
165
2
    fn assert_approx_eq_cairo_should_panic() {
166
        assert_approx_eq_cairo!(3_f64, 42_f64);
167
1
    }
168
}