Skip to main content

implicit_predicates_core/expansion/
mod.rs

1//! Floating-point expansions: exact values represented as sums of `f64`
2//! components.
3//!
4//! An *expansion* is a slice of `f64` whose exact, unrounded sum is the value
5//! it represents. Every function here requires its expansion inputs to
6//! satisfy, and guarantees for its output, these invariants:
7//!
8//! - no component is zero, so the value zero is the empty slice;
9//! - components are sorted by strictly increasing magnitude;
10//! - components are *nonoverlapping*: the highest set bit of each lies
11//!   strictly below the lowest set bit of the next.
12//!
13//! The components below the last one then add up to less than it in
14//! magnitude, so the last component has the sign of the value. Expansions
15//! built only with this module always satisfy the invariants.
16//!
17//! Results are written to the start of a caller-provided buffer `out`, and
18//! the length written is returned. Each function documents its *capacity*,
19//! the longest result it can produce. Debug builds panic if `out` is shorter
20//! than the capacity; release builds panic only if the actual result does not
21//! fit. Nothing is ever written outside `out`, and nothing allocates.
22//!
23//! The functions also require that:
24//!
25//! - every input component and scalar operand has magnitude below `2^1020`,
26//!   which rules out overflow;
27//! - operands of products ([`from_two_product`] and [`scale`]) lie in the
28//!   product domain of [`eft::in_product_domain`](crate::eft::in_product_domain),
29//!   which debug builds check;
30//! - the floating-point rounding mode is the default round-to-nearest, which
31//!   the correctness proofs depend on.
32//!
33//! These magnitude bounds are not closed under chaining: the output of an
34//! operation whose inputs satisfy them can have components outside the
35//! product domain or close to overflow, so a computation that chains
36//! operations must bound the magnitudes it produces.
37//!
38//! Gradual underflow is harmless: sums of floating-point values are exact
39//! whenever they are subnormal.
40//!
41//! The algorithms are from Shewchuk, "Adaptive Precision Floating-Point
42//! Arithmetic and Fast Robust Geometric Predicates" (1997), all with zero
43//! elimination: [`grow`] is GROW-EXPANSION (Theorem 10), [`sum`] and
44//! [`diff`] are LINEAR-EXPANSION-SUM (Theorem 24) and [`scale`] is
45//! SCALE-EXPANSION (Theorem 19). For each one the paper proves that
46//! nonoverlapping inputs give a nonoverlapping output.
47
48mod linear_sum;
49mod output;
50mod validity;
51
52pub(crate) use validity::is_valid;
53
54use crate::Sign;
55use crate::eft::{fast_two_sum, two_diff, two_product, two_sum};
56use linear_sum::linear_expansion_sum;
57use output::Output;
58
59/// Writes the expansion of `a`: empty if `a` is zero of either sign, `[a]`
60/// otherwise.
61///
62/// Capacity: 1. Requirement: `a` is finite; debug builds panic if it is not.
63///
64/// # Examples
65///
66/// ```
67/// use implicit_predicates_core::expansion;
68///
69/// let mut out = [0.0; 1];
70/// assert_eq!(expansion::from_f64(2.5, &mut out), 1);
71/// assert_eq!(out, [2.5]);
72/// assert_eq!(expansion::from_f64(-0.0, &mut out), 0);
73/// ```
74#[must_use = "the returned length tells which part of `out` holds the result"]
75pub fn from_f64(a: f64, out: &mut [f64]) -> usize {
76    debug_assert!(
77        a.is_finite(),
78        "expansion::from_f64 of {a}, which is not finite"
79    );
80    let mut out = Output::new(out, 1);
81    out.push(a);
82    out.finish()
83}
84
85/// Writes the expansion of the exact sum `a + b`.
86///
87/// Capacity: 2. Requirement: `a` and `b` have magnitude below `2^1020`.
88///
89/// # Examples
90///
91/// ```
92/// use implicit_predicates_core::expansion;
93///
94/// let mut out = [0.0; 2];
95/// let len = expansion::from_two_sum(1.0, 1e-20, &mut out);
96/// assert_eq!(&out[..len], &[1e-20, 1.0]);
97/// ```
98#[must_use = "the returned length tells which part of `out` holds the result"]
99pub fn from_two_sum(a: f64, b: f64, out: &mut [f64]) -> usize {
100    from_rounded_pair(two_sum(a, b), out)
101}
102
103/// Writes the expansion of the exact difference `a - b`.
104///
105/// Capacity: 2. Requirement: `a` and `b` have magnitude below `2^1020`.
106///
107/// # Examples
108///
109/// ```
110/// use implicit_predicates_core::expansion;
111///
112/// let mut out = [0.0; 2];
113/// let len = expansion::from_two_diff(1.0, 1e-20, &mut out);
114/// assert_eq!(&out[..len], &[-1e-20, 1.0]);
115/// ```
116#[must_use = "the returned length tells which part of `out` holds the result"]
117pub fn from_two_diff(a: f64, b: f64, out: &mut [f64]) -> usize {
118    from_rounded_pair(two_diff(a, b), out)
119}
120
121/// Writes the expansion of the exact product `a * b`.
122///
123/// Capacity: 2. Requirement: `a` and `b` lie in the product domain (see
124/// [`eft::in_product_domain`](crate::eft::in_product_domain)); debug builds
125/// panic if they do not.
126///
127/// # Examples
128///
129/// ```
130/// use implicit_predicates_core::expansion;
131///
132/// let a = 1.0 + f64::EPSILON;
133/// let mut out = [0.0; 2];
134/// let len = expansion::from_two_product(a, a, &mut out);
135/// assert_eq!(&out[..len], &[f64::EPSILON * f64::EPSILON, 1.0 + 2.0 * f64::EPSILON]);
136/// ```
137#[must_use = "the returned length tells which part of `out` holds the result"]
138pub fn from_two_product(a: f64, b: f64, out: &mut [f64]) -> usize {
139    from_rounded_pair(two_product(a, b), out)
140}
141
142/// Writes the expansion of `e + b`.
143///
144/// Capacity: `e.len() + 1`. Requirements: `e` is a valid expansion (see the
145/// [module documentation](self)) and `b` has magnitude below `2^1020`
146/// (debug builds panic if `b` is not finite). Guarantee: the result is a
147/// valid expansion.
148///
149/// # Examples
150///
151/// ```
152/// use implicit_predicates_core::expansion;
153///
154/// let mut out = [0.0; 3];
155/// let len = expansion::grow(&[1e-20, 1.0], 1e20, &mut out);
156/// assert_eq!(&out[..len], &[1e-20, 1.0, 1e20]);
157/// ```
158#[must_use = "the returned length tells which part of `out` holds the result"]
159pub fn grow(e: &[f64], b: f64, out: &mut [f64]) -> usize {
160    debug_assert!(b.is_finite(), "expansion::grow by {b}, which is not finite");
161    let mut out = Output::new(out, e.len() + 1);
162    let mut accumulator = b;
163    for &component in e {
164        let (next, low) = two_sum(accumulator, component);
165        out.push(low);
166        accumulator = next;
167    }
168    out.push(accumulator);
169    out.finish()
170}
171
172/// Writes the expansion of `e + f`.
173///
174/// Capacity: `e.len() + f.len()`. Requirement: `e` and `f` are valid
175/// expansions (see the [module documentation](self)). Guarantee: the result
176/// is a valid expansion.
177///
178/// This is the linear-time LINEAR-EXPANSION-SUM rather than the faster
179/// FAST-EXPANSION-SUM because its nonoverlapping guarantee holds for any
180/// nonoverlapping inputs.
181///
182/// # Examples
183///
184/// ```
185/// use implicit_predicates_core::expansion;
186///
187/// let mut out = [0.0; 4];
188/// let len = expansion::sum(&[1e-20, 1.0], &[-1e-20, 2.0], &mut out);
189/// assert_eq!(&out[..len], &[3.0]);
190/// ```
191#[must_use = "the returned length tells which part of `out` holds the result"]
192pub fn sum(e: &[f64], f: &[f64], out: &mut [f64]) -> usize {
193    linear_expansion_sum(e, f, false, Output::new(out, e.len() + f.len()))
194}
195
196/// Writes the expansion of `e - f`, reading `f` without copying or modifying
197/// it.
198///
199/// Capacity: `e.len() + f.len()`. Requirement: `e` and `f` are valid
200/// expansions (see the [module documentation](self)). Guarantee: the result
201/// is a valid expansion. The algorithm is that of [`sum`].
202///
203/// # Examples
204///
205/// ```
206/// use implicit_predicates_core::expansion;
207///
208/// let mut out = [0.0; 4];
209/// let len = expansion::diff(&[1e-20, 1.0], &[1e-20, 2.0], &mut out);
210/// assert_eq!(&out[..len], &[-1.0]);
211/// ```
212#[must_use = "the returned length tells which part of `out` holds the result"]
213pub fn diff(e: &[f64], f: &[f64], out: &mut [f64]) -> usize {
214    linear_expansion_sum(e, f, true, Output::new(out, e.len() + f.len()))
215}
216
217/// Writes the expansion of `e * b`.
218///
219/// Capacity: `2 * e.len()`. Requirements: `e` is a valid expansion (see the
220/// [module documentation](self)), and each of its components and `b` lie in
221/// the product domain (see
222/// [`eft::in_product_domain`](crate::eft::in_product_domain)); debug builds
223/// panic if they do not. Guarantee: the result is a valid expansion.
224///
225/// # Examples
226///
227/// ```
228/// use implicit_predicates_core::expansion;
229///
230/// let mut out = [0.0; 4];
231/// let len = expansion::scale(&[0.25, 4.0], 3.0, &mut out);
232/// assert_eq!(&out[..len], &[12.75]);
233/// ```
234#[must_use = "the returned length tells which part of `out` holds the result"]
235pub fn scale(e: &[f64], b: f64, out: &mut [f64]) -> usize {
236    let mut out = Output::new(out, 2 * e.len());
237    let Some((&first, rest)) = e.split_first() else {
238        return out.finish();
239    };
240    let (mut accumulator, low) = two_product(first, b);
241    out.push(low);
242    for &component in rest {
243        let (product, product_low) = two_product(component, b);
244        let (partial, partial_low) = two_sum(accumulator, product_low);
245        out.push(partial_low);
246        // Theorem 19 of the paper shows `|product| >= |partial|`.
247        let (next, next_low) = fast_two_sum(product, partial);
248        out.push(next_low);
249        accumulator = next;
250    }
251    out.push(accumulator);
252    out.finish()
253}
254
255/// Negates the valid expansion `e` in place. Negation is exact and keeps
256/// the invariants.
257///
258/// # Examples
259///
260/// ```
261/// use implicit_predicates_core::expansion;
262///
263/// let mut e = [1e-20, -1.0];
264/// expansion::negate(&mut e);
265/// assert_eq!(e, [-1e-20, 1.0]);
266/// ```
267pub fn negate(e: &mut [f64]) {
268    for component in e {
269        *component = -*component;
270    }
271}
272
273/// Returns the exact sign of the value of the valid expansion `e` (see the
274/// [module documentation](self)): the sign of its last component, which
275/// outweighs all the others together.
276///
277/// # Examples
278///
279/// ```
280/// use implicit_predicates_core::{Sign, expansion};
281///
282/// assert_eq!(expansion::sign(&[1e-20, -1.0]), Sign::Negative);
283/// assert_eq!(expansion::sign(&[]), Sign::Zero);
284/// ```
285#[must_use]
286pub fn sign(e: &[f64]) -> Sign {
287    e.last().map_or(Sign::Zero, |&largest| Sign::of(largest))
288}
289
290/// Returns a floating-point approximation of the value of the valid
291/// expansion `e`: its components added from smallest to largest. Use
292/// [`sign`] for the exact sign.
293///
294/// # Examples
295///
296/// ```
297/// use implicit_predicates_core::expansion;
298///
299/// assert_eq!(expansion::estimate(&[1e-20, 1.0]), 1.0);
300/// assert_eq!(expansion::estimate(&[]), 0.0);
301/// ```
302#[must_use]
303pub fn estimate(e: &[f64]) -> f64 {
304    e.iter().fold(0.0, |total, &component| total + component)
305}
306
307/// Writes `(x, y)`, a rounded result `x` and its exact error `y`, as an
308/// expansion. The paper shows that the error of TWO-SUM and of TWO-PRODUCT
309/// is too small to overlap the rounded result.
310fn from_rounded_pair((x, y): (f64, f64), out: &mut [f64]) -> usize {
311    let mut out = Output::new(out, 2);
312    out.push(y);
313    out.push(x);
314    out.finish()
315}