axiolid_predicates/
expansion.rs

1//! Error-free transformations: the arithmetic exact predicates are built from.
2//!
3//! Each operation here returns the rounded result AND the exact rounding error,
4//! so no information is lost. Chaining them lets a determinant be evaluated
5//! exactly in f64 arithmetic, which is what makes a sign certifiable rather
6//! than merely plausible.
7//!
8//! These are the classical error-free transformations (Dekker, Knuth,
9//! Shewchuk). They are exact for all finite inputs with no overflow.
10
11/// Sum of `a` and `b`, plus the exact rounding error.
12///
13/// Knuth's two-sum: `a + b == sum + error` exactly, for any finite inputs.
14/// Unlike the faster `fast_two_sum`, this needs no ordering precondition.
15#[inline]
16#[must_use]
17pub fn two_sum(a: f64, b: f64) -> (f64, f64) {
18    let sum = a + b;
19    let b_virtual = sum - a;
20    let a_virtual = sum - b_virtual;
21    let b_roundoff = b - b_virtual;
22    let a_roundoff = a - a_virtual;
23    (sum, a_roundoff + b_roundoff)
24}
25
26/// Difference of `a` and `b`, plus the exact rounding error.
27///
28/// `a - b == difference + error` exactly, for any finite inputs.
29#[inline]
30#[must_use]
31pub fn two_diff(a: f64, b: f64) -> (f64, f64) {
32    let difference = a - b;
33    let b_virtual = a - difference;
34    let a_virtual = difference + b_virtual;
35    let b_roundoff = b_virtual - b;
36    let a_roundoff = a - a_virtual;
37    (difference, a_roundoff + b_roundoff)
38}
39
40/// Splitter constant `2^27 + 1` for the 53-bit binary64 significand.
41///
42/// Dekker's split needs the significand cut into two halves whose product is
43/// representable; 27 = ceil(53/2) is the only correct choice for binary64.
44const SPLITTER: f64 = 134_217_729.0;
45
46/// Split `value` into high and low halves with non-overlapping significands.
47#[inline]
48#[must_use]
49fn split(value: f64) -> (f64, f64) {
50    let c = SPLITTER * value;
51    let big = c - value;
52    let high = c - big;
53    (high, value - high)
54}
55
56/// Product of `a` and `b`, plus the exact rounding error.
57///
58/// `a * b == product + error` exactly. This is the operation that makes an
59/// exact determinant possible: the naive `a * b` discards precisely the
60/// information a near-degenerate configuration depends on.
61#[inline]
62#[must_use]
63pub fn two_product(a: f64, b: f64) -> (f64, f64) {
64    let product = a * b;
65    let (a_high, a_low) = split(a);
66    let (b_high, b_low) = split(b);
67    // Recover the discarded bits by subtracting the partial products that the
68    // rounded result could not represent.
69    let error = a_low * b_low - (((product - a_high * b_high) - a_low * b_high) - a_high * b_low);
70    (product, error)
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    /// The defining property: no information is lost. If this fails, every
78    /// exact predicate built on it silently degrades to a floating-point guess.
79    #[test]
80    fn two_sum_is_exact_where_plain_addition_is_not() {
81        // 1.0 + 2^-60 is not representable: plain addition returns 1.0 and the
82        // addend vanishes. The transformation must recover it exactly.
83        let a = 1.0;
84        let b = 2.0_f64.powi(-60);
85        assert_eq!(a + b, 1.0, "precondition: the naive sum loses b entirely");
86
87        let (sum, error) = two_sum(a, b);
88        assert_eq!(sum, 1.0);
89        assert_eq!(error, b, "the lost addend must survive as the error term");
90    }
91
92    #[test]
93    fn two_diff_is_exact_where_plain_subtraction_is_not() {
94        let a = 1.0;
95        let b = 2.0_f64.powi(-60);
96        assert_eq!(a - b, 1.0, "precondition: the naive difference loses b");
97
98        let (difference, error) = two_diff(a, b);
99        assert_eq!(difference, 1.0);
100        assert_eq!(error, -b);
101    }
102
103    /// Products are where naive evaluation loses the most: the exact product of
104    /// two 53-bit values needs 106 bits.
105    #[test]
106    fn two_product_recovers_the_bits_a_single_f64_cannot_hold() {
107        // Both operands need the full significand, so the exact product does
108        // not fit in one f64 and the naive result is provably incomplete.
109        let a = 1.0 + 2.0_f64.powi(-52);
110        let b = 1.0 + 2.0_f64.powi(-52);
111        let (product, error) = two_product(a, b);
112
113        assert_ne!(error, 0.0, "a rounded product must report its lost bits");
114        // Exactness check that does not itself round: the recovered pair must
115        // reproduce the true product 1 + 2^-51 + 2^-104.
116        assert_eq!(product, 1.0 + 2.0_f64.powi(-51));
117        assert_eq!(error, 2.0_f64.powi(-104));
118    }
119
120    #[test]
121    fn splitting_produces_non_overlapping_halves() {
122        let value = 1.0 + 2.0_f64.powi(-52);
123        let (high, low) = split(value);
124        assert_eq!(high + low, value, "the split must be lossless");
125    }
126
127    /// Exactness must hold for arbitrary inputs, not just the crafted ones.
128    #[test]
129    fn transformations_are_exact_across_many_magnitudes() {
130        let mut state = 0x2545_F491_4F6C_DD1D_u64;
131        let mut next = || {
132            state ^= state << 13;
133            state ^= state >> 7;
134            state ^= state << 17;
135            // Bounded exponent range keeps products finite, so any failure is a
136            // real exactness bug rather than an overflow artefact.
137            let mantissa = f64::from(((state >> 32) as u32) as i32) / f64::from(i32::MAX);
138            let exponent = ((state >> 8) % 40) as i32 - 20;
139            mantissa * 2.0_f64.powi(exponent)
140        };
141
142        for _ in 0..2_000 {
143            let (a, b) = (next(), next());
144
145            let (sum, error) = two_sum(a, b);
146            assert_eq!(sum + error, a + b);
147
148            let (product, perror) = two_product(a, b);
149            assert!(product.is_finite() && perror.is_finite());
150            // Re-associating an exact pair cannot change its value.
151            assert_eq!(product + perror, a * b + (perror + (product - a * b)));
152        }
153    }
154}