axiolid_predicates/
expansion.rs1#[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#[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
40const SPLITTER: f64 = 134_217_729.0;
45
46#[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#[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 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 #[test]
80 fn two_sum_is_exact_where_plain_addition_is_not() {
81 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 #[test]
106 fn two_product_recovers_the_bits_a_single_f64_cannot_hold() {
107 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 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 #[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 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 assert_eq!(product + perror, a * b + (perror + (product - a * b)));
152 }
153 }
154}