implicit_predicates_core/eft.rs
1//! Error-free transformations: floating-point sums and products together with
2//! their exact rounding errors.
3//!
4//! Every function returns a pair `(x, y)` where `x` is the correctly rounded
5//! floating-point result of the operation and `y` its rounding error, so that
6//! `x + y` equals the real result exactly. This holds under the requirement
7//! documented on each function, assuming the default round-to-nearest-even
8//! mode. NaN and infinite inputs are not supported.
9//!
10//! The algorithms are those of Knuth, Dekker and Veltkamp as presented by
11//! Shewchuk, "Adaptive Precision Floating-Point Arithmetic and Fast Robust
12//! Geometric Predicates" (1997). When the target has a hardware fused
13//! multiply-add (see [`HAS_FMA`]), the exact product uses it instead.
14
15// On 32-bit x86 without SSE2, `f64` arithmetic runs on the x87 unit, whose
16// excess precision and double rounding break every error-free
17// transformation here.
18#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
19compile_error!(
20 "implicit-predicates-core needs SSE2 on 32-bit x86: x87 floating-point arithmetic \
21 uses excess precision, which breaks the error-free transformations"
22);
23
24/// Selects code by whether the target has a hardware fused multiply-add that
25/// the crate can use: x86_64 compiled with the `fma` target feature, or
26/// aarch64 with the `neon` target feature. This is the single definition of
27/// that condition.
28///
29/// - `fma_target!(has)` is `true` or `false` according to the condition;
30/// - `fma_target!(item)` keeps `item` only when the condition holds;
31/// - `fma_target!(if { a } else { b })` evaluates `a` when the condition
32/// holds and `b` otherwise; only the chosen expression is compiled.
33macro_rules! fma_target {
34 (@with [$pred:meta] has) => {
35 cfg!($pred)
36 };
37 (@with [$pred:meta] if { $yes:expr } else { $no:expr }) => {{
38 #[cfg($pred)]
39 let selected = $yes;
40 #[cfg(not($pred))]
41 let selected = $no;
42 selected
43 }};
44 (@with [$pred:meta] $item:item) => {
45 #[cfg($pred)]
46 $item
47 };
48 ($($input:tt)*) => {
49 fma_target! {
50 @with [any(all(target_arch = "x86_64", target_feature = "fma"), all(target_arch = "aarch64", target_feature = "neon"))]
51 $($input)*
52 }
53 };
54}
55
56/// True when `two_product` uses a fused multiply-add on this target.
57///
58/// That is the case on x86_64 when compiled with the `fma` target feature
59/// (for example with `-C target-feature=+fma` or `-C target-cpu=native` on a
60/// CPU that has it), and on aarch64 with the `neon` target feature, which
61/// every aarch64 target enables by default except the soft-float ones.
62///
63/// # Examples
64///
65/// ```
66/// use implicit_predicates_core::eft::HAS_FMA;
67///
68/// if HAS_FMA {
69/// // `two_product` and `two_square` use `two_product_fma`.
70/// }
71/// ```
72pub const HAS_FMA: bool = fma_target!(has);
73
74/// `2^-480`, the smallest nonzero magnitude in the product domain. See
75/// [`in_product_domain`].
76///
77/// # Examples
78///
79/// ```
80/// use implicit_predicates_core::eft::PRODUCT_OPERAND_MIN;
81///
82/// assert_eq!(PRODUCT_OPERAND_MIN, 2f64.powi(-480));
83/// ```
84pub const PRODUCT_OPERAND_MIN: f64 = f64::from_bits((1023 - 480) << 52);
85
86/// `2^480`, the largest magnitude in the product domain. See
87/// [`in_product_domain`].
88///
89/// # Examples
90///
91/// ```
92/// use implicit_predicates_core::eft::PRODUCT_OPERAND_MAX;
93///
94/// assert_eq!(PRODUCT_OPERAND_MAX, 2f64.powi(480));
95/// ```
96pub const PRODUCT_OPERAND_MAX: f64 = f64::from_bits((1023 + 480) << 52);
97
98/// Returns `true` if `x` lies in the *product domain*: it is zero of either
99/// sign, or its magnitude is in
100/// [`PRODUCT_OPERAND_MIN`]` ..= `[`PRODUCT_OPERAND_MAX`], that is
101/// `2^-480 ..= 2^480`.
102///
103/// Every operand of the exact products ([`two_product`],
104/// [`two_product_dekker`], `two_product_fma` and [`two_square`]) must lie in
105/// this domain, where neither the product nor its rounding error can
106/// overflow or underflow. Outside it the error they return can be inexact,
107/// without any other symptom. Debug builds check the domain on every exact
108/// product and panic outside it; release builds do not check.
109///
110/// # Examples
111///
112/// ```
113/// use implicit_predicates_core::eft::{PRODUCT_OPERAND_MAX, PRODUCT_OPERAND_MIN, in_product_domain};
114///
115/// assert!(in_product_domain(-0.0));
116/// assert!(in_product_domain(PRODUCT_OPERAND_MIN) && in_product_domain(-PRODUCT_OPERAND_MAX));
117/// assert!(!in_product_domain(PRODUCT_OPERAND_MIN / 2.0));
118/// assert!(!in_product_domain(f64::MAX));
119/// assert!(!in_product_domain(f64::NAN));
120/// ```
121#[inline]
122#[must_use]
123pub fn in_product_domain(x: f64) -> bool {
124 x == 0.0 || (PRODUCT_OPERAND_MIN..=PRODUCT_OPERAND_MAX).contains(&x.abs())
125}
126
127/// `2^27 + 1`: multiplying by it splits an `f64` into two halves of at most
128/// 26 significant bits each (Veltkamp's splitting).
129const SPLITTER: f64 = 134_217_729.0;
130
131/// Computes `a + b` and its rounding error (Knuth's two-sum).
132///
133/// Requirement: `a + b` does not overflow.
134///
135/// # Examples
136///
137/// ```
138/// use implicit_predicates_core::eft::two_sum;
139///
140/// let (x, y) = two_sum(1.0, 1e-20);
141/// assert_eq!(x, 1.0);
142/// assert_eq!(y, 1e-20);
143/// ```
144#[inline]
145#[must_use]
146pub fn two_sum(a: f64, b: f64) -> (f64, f64) {
147 let x = a + b;
148 let b_virtual = x - a;
149 let a_virtual = x - b_virtual;
150 let b_round = b - b_virtual;
151 let a_round = a - a_virtual;
152 (x, a_round + b_round)
153}
154
155/// Computes `a + b` and its rounding error, faster than [`two_sum`] when the
156/// magnitudes are known to be ordered (Dekker's fast-two-sum).
157///
158/// Requirement: `|a| >= |b|` or `a == 0`, and `a + b` does not overflow.
159///
160/// # Examples
161///
162/// ```
163/// use implicit_predicates_core::eft::fast_two_sum;
164///
165/// let (x, y) = fast_two_sum(1.0, 1e-20);
166/// assert_eq!(x, 1.0);
167/// assert_eq!(y, 1e-20);
168/// ```
169#[inline]
170#[must_use]
171pub fn fast_two_sum(a: f64, b: f64) -> (f64, f64) {
172 let x = a + b;
173 let b_virtual = x - a;
174 (x, b - b_virtual)
175}
176
177/// Computes `a - b` and its rounding error.
178///
179/// Requirement: `a - b` does not overflow.
180///
181/// # Examples
182///
183/// ```
184/// use implicit_predicates_core::eft::two_diff;
185///
186/// let (x, y) = two_diff(1.0, 1e-20);
187/// assert_eq!(x, 1.0);
188/// assert_eq!(y, -1e-20);
189/// ```
190#[inline]
191#[must_use]
192pub fn two_diff(a: f64, b: f64) -> (f64, f64) {
193 // Negation is exact, and `a + (-b)` rounds exactly like `a - b`.
194 two_sum(a, -b)
195}
196
197/// Computes `a * b` and its rounding error with Dekker's algorithm, using
198/// only additions and multiplications.
199///
200/// Requirement: `a` and `b` lie in the product domain (see
201/// [`in_product_domain`]); debug builds panic if they do not.
202///
203/// # Examples
204///
205/// ```
206/// use implicit_predicates_core::eft::two_product_dekker;
207///
208/// let a = 1.0 + f64::EPSILON;
209/// let (x, y) = two_product_dekker(a, a);
210/// assert_eq!(x, 1.0 + 2.0 * f64::EPSILON);
211/// assert_eq!(y, f64::EPSILON * f64::EPSILON);
212/// ```
213#[inline]
214#[must_use]
215pub fn two_product_dekker(a: f64, b: f64) -> (f64, f64) {
216 debug_check_product_domain(a, b);
217 let x = a * b;
218 let (a_hi, a_lo) = split(a);
219 let (b_hi, b_lo) = split(b);
220 let err1 = x - a_hi * b_hi;
221 let err2 = err1 - a_lo * b_hi;
222 let err3 = err2 - a_hi * b_lo;
223 (x, a_lo * b_lo - err3)
224}
225
226fma_target! {
227 /// Computes `a * b` and its rounding error with one multiplication and one
228 /// fused multiply-add.
229 ///
230 /// Only available when [`HAS_FMA`] is true. It returns a pair equal in
231 /// value to that of [`two_product_dekker`]; the two may differ in the
232 /// sign of a zero error (Dekker's algorithm can give `-0.0` where this
233 /// one gives `+0.0`).
234 ///
235 /// Requirement: `a` and `b` lie in the product domain (see
236 /// [`in_product_domain`]); debug builds panic if they do not.
237 ///
238 /// # Examples
239 ///
240 /// ```
241 /// # #[cfg(any(all(target_arch = "x86_64", target_feature = "fma"), all(target_arch = "aarch64", target_feature = "neon")))]
242 /// # {
243 /// use implicit_predicates_core::eft::two_product_fma;
244 ///
245 /// let a = 1.0 + f64::EPSILON;
246 /// let (x, y) = two_product_fma(a, a);
247 /// assert_eq!(x, 1.0 + 2.0 * f64::EPSILON);
248 /// assert_eq!(y, f64::EPSILON * f64::EPSILON);
249 /// # }
250 /// ```
251 #[inline]
252 #[must_use]
253 // The `core::arch` intrinsics carry `#[target_feature]`, so calling them
254 // from a function without that attribute needs `unsafe`.
255 #[allow(unsafe_code)]
256 pub fn two_product_fma(a: f64, b: f64) -> (f64, f64) {
257 debug_check_product_domain(a, b);
258 let x = a * b;
259 // `a * b - x` is representable in the domain, so the fused operation
260 // computes it without rounding.
261 // SAFETY: this function only exists when the target has the features
262 // the intrinsics need, enabled at compile time: `fma` on x86_64,
263 // which implies the `sse2` the other intrinsics need (through
264 // `avx`), and `neon` on aarch64.
265 let y = unsafe {
266 #[cfg(target_arch = "x86_64")]
267 let y = {
268 use core::arch::x86_64::{_mm_cvtsd_f64, _mm_fmsub_sd, _mm_set_sd};
269 _mm_cvtsd_f64(_mm_fmsub_sd(_mm_set_sd(a), _mm_set_sd(b), _mm_set_sd(x)))
270 };
271 #[cfg(target_arch = "aarch64")]
272 let y = {
273 use core::arch::aarch64::{vdup_n_f64, vfma_f64, vget_lane_f64};
274 // `vfma_f64(c, a, b)` is `c + a * b` with a single rounding.
275 vget_lane_f64::<0>(vfma_f64(vdup_n_f64(-x), vdup_n_f64(a), vdup_n_f64(b)))
276 };
277 y
278 };
279 (x, y)
280 }
281}
282
283/// Computes `a * b` and its rounding error with the crate's selected
284/// implementation: `two_product_fma` when [`HAS_FMA`] is true, and
285/// [`two_product_dekker`] otherwise. Both give equal values.
286///
287/// Requirement: `a` and `b` lie in the product domain (see
288/// [`in_product_domain`]); debug builds panic if they do not.
289///
290/// # Examples
291///
292/// ```
293/// use implicit_predicates_core::eft::two_product;
294///
295/// let a = 1.0 + f64::EPSILON;
296/// let (x, y) = two_product(a, 1.0 - f64::EPSILON);
297/// assert_eq!(x, 1.0);
298/// assert_eq!(y, -f64::EPSILON * f64::EPSILON);
299/// ```
300#[inline]
301#[must_use]
302pub fn two_product(a: f64, b: f64) -> (f64, f64) {
303 fma_target!(if { two_product_fma(a, b) } else { two_product_dekker(a, b) })
304}
305
306/// Computes `a * a` and its rounding error: with `two_product_fma` when
307/// [`HAS_FMA`] is true, and otherwise with fewer operations than
308/// [`two_product_dekker`].
309///
310/// Requirement: `a` lies in the product domain (see [`in_product_domain`]);
311/// debug builds panic if it does not.
312///
313/// # Examples
314///
315/// ```
316/// use implicit_predicates_core::eft::two_square;
317///
318/// let a = 1.0 + f64::EPSILON;
319/// let (x, y) = two_square(a);
320/// assert_eq!(x, 1.0 + 2.0 * f64::EPSILON);
321/// assert_eq!(y, f64::EPSILON * f64::EPSILON);
322/// ```
323#[inline]
324#[must_use]
325pub fn two_square(a: f64) -> (f64, f64) {
326 debug_check_product_domain(a, a);
327 fma_target!(if { two_product_fma(a, a) } else {
328 {
329 let x = a * a;
330 let (hi, lo) = split(a);
331 let err1 = x - hi * hi;
332 // `hi + hi` is exact, and `hi * lo` counts twice in `a * a`.
333 let err3 = err1 - (hi + hi) * lo;
334 (x, lo * lo - err3)
335 }
336 })
337}
338
339/// Panics in debug builds unless both operands of an exact product lie in
340/// the product domain.
341#[inline]
342#[track_caller]
343fn debug_check_product_domain(a: f64, b: f64) {
344 debug_assert!(
345 in_product_domain(a) && in_product_domain(b),
346 "an operand of the exact product {a:e} * {b:e} is outside the product domain \
347 (see `eft::in_product_domain`)"
348 );
349}
350
351/// Veltkamp's splitting: returns `(hi, lo)` with `hi + lo == a` exactly and
352/// each half fitting in 26 significant bits, so that products of halves are
353/// exact. Requires `|a| * SPLITTER` not to overflow.
354#[inline]
355fn split(a: f64) -> (f64, f64) {
356 let c = SPLITTER * a;
357 let a_big = c - a;
358 let hi = c - a_big;
359 (hi, a - hi)
360}