forked from 1b-agent/fieldcraft-upstream
Crates fieldcraft-*, binaries fieldcraft and fieldcraft-cli, the .fieldcraft file (format tag fieldcraft/1, manifest fieldcraft.json), FIELDCRAFT_* environment variables, the MCP server name, egui widget ids (fc_*), the app icon document FieldCraft.icon and its generated family, docs and licences. No compatibility shim: no files of the earlier name exist. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
97 lines
3.2 KiB
Rust
97 lines
3.2 KiB
Rust
//! Error-free transformations (Shewchuk 1997, §2.3 and §2.5).
|
|
//!
|
|
//! Each function returns `(x, y)` with `x = fl(a ∘ b)` and `y` the exact roundoff, so that
|
|
//! `a ∘ b = x + y` exactly. They assume IEEE 754 binary64 with round-to-nearest-even, no
|
|
//! intermediate extended precision and no fused multiply-add contraction; that is what Rust's
|
|
//! `f64` guarantees on every target FieldCraft builds for (x86_64 with SSE2, aarch64, wasm32):
|
|
//! rustc never contracts `a * b + c` into an FMA and does not use x87. No overflow or underflow
|
|
//! may occur, which the coordinate domain of [`super::Point2`] / [`super::Point3`] guarantees.
|
|
|
|
/// `2^ceil(53 / 2) + 1`, the splitter for Dekker's product (§2.5, Theorem 17).
|
|
const SPLITTER: f64 = 134_217_729.0;
|
|
|
|
/// Fast-Two-Sum (Theorem 6). Requires `|a| >= |b|` (or `a == 0`).
|
|
#[inline(always)]
|
|
pub(crate) fn fast_two_sum(a: f64, b: f64) -> (f64, f64) {
|
|
let x = a + b;
|
|
let b_virtual = x - a;
|
|
(x, b - b_virtual)
|
|
}
|
|
|
|
/// Two-Sum (Theorem 7), no precondition on the magnitudes.
|
|
#[inline(always)]
|
|
pub(crate) fn two_sum(a: f64, b: f64) -> (f64, f64) {
|
|
let x = a + b;
|
|
let b_virtual = x - a;
|
|
let a_virtual = x - b_virtual;
|
|
let b_round = b - b_virtual;
|
|
let a_round = a - a_virtual;
|
|
(x, a_round + b_round)
|
|
}
|
|
|
|
/// The roundoff of `x = fl(a - b)`, given `x`.
|
|
#[inline(always)]
|
|
pub(crate) fn two_diff_tail(a: f64, b: f64, x: f64) -> f64 {
|
|
let b_virtual = a - x;
|
|
let a_virtual = x + b_virtual;
|
|
let b_round = b_virtual - b;
|
|
let a_round = a - a_virtual;
|
|
a_round + b_round
|
|
}
|
|
|
|
/// Two-Diff: `a - b = x + y` exactly.
|
|
#[inline(always)]
|
|
pub(crate) fn two_diff(a: f64, b: f64) -> (f64, f64) {
|
|
let x = a - b;
|
|
(x, two_diff_tail(a, b, x))
|
|
}
|
|
|
|
/// Dekker's split (Theorem 17): `a = hi + lo` with each half fitting in 26 bits.
|
|
#[inline(always)]
|
|
pub(crate) fn split(a: f64) -> (f64, f64) {
|
|
let c = SPLITTER * a;
|
|
let a_big = c - a;
|
|
let hi = c - a_big;
|
|
(hi, a - hi)
|
|
}
|
|
|
|
/// Two-Product with `b` already split (Theorem 18).
|
|
#[inline(always)]
|
|
pub(crate) fn two_product_presplit(a: f64, b: f64, b_hi: f64, b_lo: f64) -> (f64, f64) {
|
|
let x = a * b;
|
|
let (a_hi, a_lo) = split(a);
|
|
let err1 = x - a_hi * b_hi;
|
|
let err2 = err1 - a_lo * b_hi;
|
|
let err3 = err2 - a_hi * b_lo;
|
|
(x, a_lo * b_lo - err3)
|
|
}
|
|
|
|
/// Two-Product (Theorem 18): `a * b = x + y` exactly.
|
|
#[inline(always)]
|
|
pub(crate) fn two_product(a: f64, b: f64) -> (f64, f64) {
|
|
let (b_hi, b_lo) = split(b);
|
|
two_product_presplit(a, b, b_hi, b_lo)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn transforms_are_exact_on_classic_cases() {
|
|
let (x, y) = two_sum(1.0, 1e-17);
|
|
assert_eq!((x, y), (1.0, 1e-17));
|
|
// 2^53 + 1 is a tie that rounds to the even 2^53.
|
|
let (x, y) = fast_two_sum(2f64.powi(53), 1.0);
|
|
assert_eq!((x, y), (2f64.powi(53), 1.0));
|
|
let (x, y) = two_diff(1.0, 1e-17);
|
|
assert_eq!((x, y), (1.0, -1e-17));
|
|
// (1 + 2^-30)^2 = 1 + 2^-29 + 2^-60: the last term is the roundoff.
|
|
let a = 1.0 + 2f64.powi(-30);
|
|
let (x, y) = two_product(a, a);
|
|
assert_eq!((x, y), (1.0 + 2f64.powi(-29), 2f64.powi(-60)));
|
|
let (hi, lo) = split(a);
|
|
assert_eq!(hi + lo, a);
|
|
}
|
|
}
|