use crate::dd::DD;
use crate::exact::Dy;
use crate::qd::QD;
use std::cmp::Ordering;
use std::ops::{Add, Div, Mul, Neg, Sub};
pub mod mutate {
pub const RENORM_DROP: u32 = 1;
pub const TWO_SUM_SIGN: u32 = 2;
pub const ADD_T1: u32 = 3;
pub const MUL_E23: u32 = 4;
pub const MUL_F_E3: u32 = 5;
pub const DIV_REM: u32 = 6;
pub const SQRT_ITER: u32 = 7;
pub const ACC_L3: u32 = 8;
pub const PARSE_TRUNC: u32 = 9;
pub const OV_RENORM_MASK: u32 = 10;
pub const OV_FMA_PLAIN: u32 = 11;
pub const N: u32 = 11;
pub const NAMES: [&str; 12] = [
"none",
"renorm: eighth part dropped",
"two-sum: error with a wrong sign",
"add: error of a1 + b1 dropped",
"mul: error of a2 b3 dropped",
"od x f64: error of a3 b dropped",
"divide: sixth remainder update skipped",
"sqrt: two Newton iterations instead of four",
"dot accumulator: level-3 running error dropped",
"parse: last part truncated",
"8-lane renorm: zero test ignored",
"8-lane two-product: FMA as a plain product (native)",
];
#[cfg(feature = "mutate")]
thread_local!(static ID: std::cell::Cell<u32> = const { std::cell::Cell::new(0) });
#[cfg(feature = "mutate")]
pub fn set(id: u32) {
ID.with(|m| m.set(id));
}
#[cfg(feature = "mutate")]
#[inline(always)]
pub fn on(id: u32) -> bool {
ID.with(|m| m.get() == id)
}
#[cfg(not(feature = "mutate"))]
#[inline(always)]
pub fn on(_id: u32) -> bool {
false
}
}
pub(crate) trait Limb: Copy {
type M: Copy;
fn zero() -> Self;
fn splat(x: f64) -> Self;
fn add(self, o: Self) -> Self;
fn sub(self, o: Self) -> Self;
fn mul(self, o: Self) -> Self;
fn div(self, o: Self) -> Self;
fn fma(self, b: Self, c: Self) -> Self;
fn fms(self, b: Self, c: Self) -> Self;
fn neg(self) -> Self;
fn sqrt(self) -> Self;
fn pow2_floor(self) -> Self;
fn is_zero(self) -> Self::M;
fn sel(m: Self::M, t: Self, f: Self) -> Self;
fn renorm<const N: usize>(c: [Self; N]) -> [Self; 8];
fn outline<T>(f: impl FnOnce() -> T) -> T;
}
impl Limb for f64 {
type M = bool;
#[inline(always)]
fn zero() -> f64 {
0.0
}
#[inline(always)]
fn splat(x: f64) -> f64 {
x
}
#[inline(always)]
fn add(self, o: f64) -> f64 {
self + o
}
#[inline(always)]
fn sub(self, o: f64) -> f64 {
self - o
}
#[inline(always)]
fn mul(self, o: f64) -> f64 {
self * o
}
#[inline(always)]
fn div(self, o: f64) -> f64 {
self / o
}
#[inline(always)]
fn fma(self, b: f64, c: f64) -> f64 {
self.mul_add(b, c)
}
#[inline(always)]
fn fms(self, b: f64, c: f64) -> f64 {
self.mul_add(b, -c)
}
#[inline(always)]
fn neg(self) -> f64 {
-self
}
#[inline(always)]
fn sqrt(self) -> f64 {
f64::sqrt(self)
}
#[inline(always)]
fn pow2_floor(self) -> f64 {
f64::from_bits(self.to_bits() & 0x7ff0_0000_0000_0000)
}
#[inline(always)]
fn is_zero(self) -> bool {
self == 0.0
}
#[inline(always)]
fn sel(m: bool, t: f64, f: f64) -> f64 {
if m {
t
} else {
f
}
}
#[inline(always)]
fn renorm<const N: usize>(c: [f64; N]) -> [f64; 8] {
renorm_scalar(c)
}
#[inline(always)]
fn outline<T>(f: impl FnOnce() -> T) -> T {
f()
}
}
#[inline(always)]
pub(crate) fn two_sum<L: Limb>(a: L, b: L) -> (L, L) {
let s = a.add(b);
let bb = s.sub(a);
let x = a.sub(s.sub(bb));
let y = b.sub(bb);
if mutate::on(mutate::TWO_SUM_SIGN) {
(s, x.sub(y))
} else {
(s, x.add(y))
}
}
#[inline(always)]
pub(crate) fn quick_two_sum<L: Limb>(a: L, b: L) -> (L, L) {
let s = a.add(b);
(s, b.sub(s.sub(a)))
}
#[inline(always)]
pub(crate) fn two_prod<L: Limb>(a: L, b: L) -> (L, L) {
let p = a.mul(b);
(p, a.fms(b, p))
}
#[inline(always)]
fn dep<L: Limb, const K: usize>(s: &mut [L; K], l: usize, t: L) {
let mut e = t;
for m in l..K - 1 {
let (x, y) = two_sum(s[m], e);
s[m] = x;
e = y;
}
s[K - 1] = s[K - 1].add(e);
}
#[inline(always)]
pub(crate) fn vecsum<L: Limb, const N: usize>(c: [L; N]) -> [L; N] {
let mut c = c;
let mut s = c[N - 1];
for i in (0..N - 1).rev() {
let (t, e) = two_sum(c[i], s);
s = t;
c[i + 1] = e;
}
c[0] = s;
c
}
#[inline(always)]
pub(crate) fn renorm_scalar<const N: usize>(c: [f64; N]) -> [f64; 8] {
if c[0].is_infinite() {
return std::array::from_fn(|i| if i < N { c[i] } else { 0.0 });
}
let c = vecsum(c);
let mut out = [0.0f64; 8];
let mut k = 0;
let mut acc = c[0];
for &ci in &c[1..] {
let (t, e) = quick_two_sum(acc, ci);
if k < 7 && e != 0.0 {
out[k] = t;
k += 1;
acc = e;
} else {
acc = t;
}
}
out[k] = acc;
if mutate::on(mutate::RENORM_DROP) {
out[7] = 0.0;
}
out
}
pub(crate) mod alg {
use super::{dep, mutate, quick_two_sum, two_prod, two_sum, Limb};
#[inline(always)]
pub fn add<L: Limb>(a: &[L; 8], b: &[L; 8]) -> [L; 8] {
L::outline(
#[inline(always)]
|| {
let mut s = [L::zero(); 9];
let mut t = [L::zero(); 8];
for i in 0..8 {
(s[i], t[i]) = two_sum(a[i], b[i]);
}
if mutate::on(mutate::ADD_T1) {
t[1] = L::zero();
}
s[8] = t[7];
dep(&mut s, 1, t[0]);
dep(&mut s, 2, t[1]);
dep(&mut s, 3, t[2]);
dep(&mut s, 4, t[3]);
dep(&mut s, 5, t[4]);
dep(&mut s, 6, t[5]);
dep(&mut s, 7, t[6]);
L::renorm(s)
},
)
}
#[inline(always)]
pub fn add_f<L: Limb>(a: &[L; 8], b: L) -> [L; 8] {
L::outline(
#[inline(always)]
|| {
let mut s = [L::zero(); 9];
let mut e = b;
for i in 0..8 {
(s[i], e) = two_sum(a[i], e);
}
s[8] = e;
L::renorm(s)
},
)
}
#[inline(always)]
pub fn mul_f<L: Limb>(a: &[L; 8], b: L) -> [L; 8] {
L::outline(
#[inline(always)]
|| {
let mut s = [L::zero(); 9];
let mut e = [L::zero(); 8];
for i in 0..8 {
(s[i], e[i]) = two_prod(a[i], b);
}
if mutate::on(mutate::MUL_F_E3) {
e[3] = L::zero();
}
s[8] = e[7];
dep(&mut s, 1, e[0]);
dep(&mut s, 2, e[1]);
dep(&mut s, 3, e[2]);
dep(&mut s, 4, e[3]);
dep(&mut s, 5, e[4]);
dep(&mut s, 6, e[5]);
dep(&mut s, 7, e[6]);
L::renorm(s)
},
)
}
#[inline(always)]
pub fn expand<L: Limb>(a: &[L; 8], b: &[L; 8]) -> [L; 9] {
L::outline(
#[inline(always)]
|| {
let mut s = [L::zero(); 9];
let mut e = [L::zero(); 8];
for j in 0..8 {
(s[j], e[j]) = two_prod(a[0], b[j]);
}
s[8] = a[7].fma(
b[1],
a[6].fma(
b[2],
a[5].fma(b[3], a[4].fma(b[4], a[3].fma(b[5], a[2].fma(b[6], a[1].mul(b[7]))))),
),
);
dep(&mut s, 1, e[0]);
dep(&mut s, 2, e[1]);
dep(&mut s, 3, e[2]);
dep(&mut s, 4, e[3]);
dep(&mut s, 5, e[4]);
dep(&mut s, 6, e[5]);
dep(&mut s, 7, e[6]);
dep(&mut s, 8, e[7]);
macro_rules! pp {
($(($i:literal, $j:literal)),*) => {
$({
let (p, e) = two_prod(a[$i], b[$j]);
let e = if $i == 2 && $j == 3 && mutate::on(mutate::MUL_E23) { L::zero() } else { e };
dep(&mut s, $i + $j, p);
dep(&mut s, $i + $j + 1, e);
})*
};
}
pp!((1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6));
pp!((2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5));
pp!((3, 0), (3, 1), (3, 2), (3, 3), (3, 4));
pp!((4, 0), (4, 1), (4, 2), (4, 3));
pp!((5, 0), (5, 1), (5, 2));
pp!((6, 0), (6, 1));
pp!((7, 0));
s
},
)
}
#[inline(always)]
pub fn mul<L: Limb>(a: &[L; 8], b: &[L; 8]) -> [L; 8] {
L::renorm(expand(a, b))
}
#[inline(always)]
pub fn sub_mul_f<L: Limb>(r: &[L; 8], b: &[L; 8], q: L) -> [L; 8] {
L::outline(
#[inline(always)]
|| {
let mut s = [L::zero(); 9];
let mut t = [L::zero(); 8];
let mut e = [L::zero(); 8];
for i in 0..8 {
let (p, pe) = two_prod(b[i], q);
(s[i], t[i]) = two_sum(r[i], p.neg());
e[i] = pe.neg();
}
s[8] = t[7].add(e[7]);
macro_rules! carry {
($($l:literal),*) => {
$(
dep(&mut s, $l + 1, t[$l]);
dep(&mut s, $l + 1, e[$l]);
)*
};
}
carry!(0, 1, 2, 3, 4, 5, 6);
L::renorm(s)
},
)
}
#[inline(always)]
pub fn div<L: Limb>(a: &[L; 8], b: &[L; 8], q: impl Fn(L) -> L) -> [L; 8] {
let mut qs = [L::zero(); 9];
let mut r = *a;
qs[0] = q(r[0]);
for k in 1..9 {
if !(k == 5 && mutate::on(mutate::DIV_REM)) {
r = sub_mul_f(&r, b, qs[k - 1]);
}
qs[k] = q(r[0]);
}
L::renorm(qs)
}
#[inline(always)]
pub fn mul_pwr2<L: Limb>(a: &[L; 8], s: L) -> [L; 8] {
a.map(|v| v.mul(s))
}
#[inline(always)]
pub fn neg<L: Limb>(a: &[L; 8]) -> [L; 8] {
let z = L::zero();
std::array::from_fn(|i| if i == 0 { a[0].neg() } else { a[i].neg().add(z) })
}
#[inline(always)]
pub fn sqrt<L: Limb>(
x: &[L; 8],
mul: impl Fn(&[L; 8], &[L; 8]) -> [L; 8],
add: impl Fn(&[L; 8], &[L; 8]) -> [L; 8],
add_f: impl Fn(&[L; 8], L) -> [L; 8],
) -> [L; 8] {
let z = L::zero();
let zero = x[0].is_zero();
let s = x[0].sqrt().pow2_floor();
let inv = L::splat(1.0).div(s);
let a = x.map(|v| v.mul(inv).mul(inv));
let mut r = [z; 8];
r[0] = L::splat(1.0).div(a[0].sqrt());
let h = mul_pwr2(&a, L::splat(0.5));
let n = if mutate::on(mutate::SQRT_ITER) { 2 } else { 4 };
for _ in 0..n {
let hr2 = mul(&h, &mul(&r, &r));
let c = add_f(&neg(&hr2), L::splat(0.5));
r = add(&r, &mul(&c, &r));
}
let r = mul(&r, &a);
r.map(|v| L::sel(zero, z, v.mul(s)))
}
#[inline(always)]
pub fn acc_levels<L: Limb, const K: usize>(acc: &mut [L; 9], t: &[L; K]) {
L::outline(
#[inline(always)]
|| {
for l in 0..K.min(8) {
if l == 3 && mutate::on(mutate::ACC_L3) {
let (x, _) = two_sum(acc[3], t[3]);
acc[3] = x;
continue;
}
dep(acc, l, t[l]);
}
if K > 8 {
acc[8] = acc[8].add(t[8]);
}
},
)
}
#[inline(always)]
pub fn to_dd<L: Limb>(a: &[L; 8]) -> (L, L) {
let t = a[1].add(a[2].add(a[3].add(a[4].add(a[5].add(a[6].add(a[7]))))));
quick_two_sum(a[0], t)
}
#[inline(always)]
pub fn to_qd_parts<L: Limb>(a: &[L; 8]) -> [L; 4] {
[a[0], a[1], a[2], a[3].add(a[4].add(a[5].add(a[6].add(a[7]))))]
}
#[inline(always)]
pub fn to_f64_parts<L: Limb>(a: &[L; 8]) -> (L, L) {
(a[0], a[1].add(a[2].add(a[3].add(a[4].add(a[5].add(a[6].add(a[7])))))))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct OD {
pub x: [f64; 8],
}
impl OD {
pub const ZERO: OD = OD { x: [0.0; 8] };
pub const ONE: OD = OD {
x: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
};
pub const EPS: f64 = f64::from_bits((1023 - 418) << 52);
#[inline]
pub fn new(x: [f64; 8]) -> OD {
OD { x: renorm_scalar(x) }
}
#[inline(always)]
pub fn from_f64(x: f64) -> OD {
let mut o = OD::ZERO;
o.x[0] = x;
o
}
#[inline(always)]
pub fn from_dd(d: DD) -> OD {
let mut o = OD::ZERO;
o.x[0] = d.hi;
o.x[1] = d.lo;
o
}
#[inline(always)]
pub fn from_qd(q: QD) -> OD {
let mut o = OD::ZERO;
o.x[..4].copy_from_slice(&q.x);
o
}
#[inline(always)]
pub fn to_f64(self) -> f64 {
let (h, t) = alg::to_f64_parts(&self.x);
if t == 0.0 {
h
} else {
h + t
}
}
#[inline(always)]
pub fn to_dd(self) -> DD {
let (hi, lo) = alg::to_dd(&self.x);
DD { hi, lo }
}
#[inline(always)]
pub fn to_qd(self) -> QD {
let [a, b, c, d] = alg::to_qd_parts(&self.x);
QD::new(a, b, c, d)
}
#[inline(always)]
pub fn is_zero(self) -> bool {
self.x[0] == 0.0
}
#[inline(always)]
pub fn abs(self) -> OD {
if self.x[0] < 0.0 {
-self
} else {
self
}
}
#[inline(always)]
pub fn mul_pwr2(self, b: f64) -> OD {
debug_assert!(crate::dd::is_pwr2(b), "mul_pwr2: {b} is not a power of two");
OD {
x: alg::mul_pwr2(&self.x, b),
}
}
#[inline]
pub fn add_f(self, b: f64) -> OD {
OD {
x: alg::add_f(&self.x, b),
}
}
#[inline]
pub fn mul_f(self, b: f64) -> OD {
OD {
x: alg::mul_f(&self.x, b),
}
}
#[inline]
pub fn recip(self) -> OD {
OD::ONE / self
}
#[inline]
pub fn sqrt(self) -> OD {
let w = |x: &[f64; 8]| OD { x: *x };
OD {
x: alg::sqrt(
&self.x,
|a, b| (w(a) * w(b)).x,
|a, b| (w(a) + w(b)).x,
|a, b| w(a).add_f(b).x,
),
}
}
pub fn powi(self, n: i32) -> OD {
if n == 0 {
return OD::ONE;
}
let mut r = self;
let mut s = OD::ONE;
let mut nn = n.unsigned_abs();
if nn > 1 {
while nn > 0 {
if nn % 2 == 1 {
s = s * r;
}
nn /= 2;
if nn > 0 {
r = r * r;
}
}
} else {
s = r;
}
if n < 0 {
OD::ONE / s
} else {
s
}
}
pub fn exact(self) -> Dy {
Dy::sum(&self.x)
}
pub fn parse(s: &str) -> Result<OD, String> {
let mut x = crate::qd::parse_parts::<8>(s, mutate::on(mutate::PARSE_TRUNC))?;
for v in x[1..].iter_mut() {
*v += 0.0;
}
Ok(OD { x })
}
pub fn to_sci(self, digits: usize) -> String {
crate::qd::sci_parts(&self.x, digits)
}
}
impl std::fmt::Display for OD {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_sci(f.precision().map(|p| p + 1).unwrap_or(128)))
}
}
impl std::str::FromStr for OD {
type Err = String;
fn from_str(s: &str) -> Result<OD, String> {
OD::parse(s)
}
}
impl PartialOrd for OD {
fn partial_cmp(&self, o: &OD) -> Option<Ordering> {
for i in 0..8 {
match self.x[i].partial_cmp(&o.x[i])? {
Ordering::Equal => continue,
c => return Some(c),
}
}
Some(Ordering::Equal)
}
}
impl Neg for OD {
type Output = OD;
#[inline(always)]
fn neg(self) -> OD {
OD { x: alg::neg(&self.x) }
}
}
impl Add for OD {
type Output = OD;
#[inline]
fn add(self, b: OD) -> OD {
OD {
x: alg::add(&self.x, &b.x),
}
}
}
impl Sub for OD {
type Output = OD;
#[inline]
fn sub(self, b: OD) -> OD {
self + (-b)
}
}
impl Mul for OD {
type Output = OD;
#[inline]
fn mul(self, b: OD) -> OD {
OD {
x: alg::mul(&self.x, &b.x),
}
}
}
impl Div for OD {
type Output = OD;
#[inline]
fn div(self, b: OD) -> OD {
let b0 = b.x[0];
OD {
x: alg::div(&self.x, &b.x, |r: f64| r / b0),
}
}
}
pub fn to_parts(v: &[OD]) -> [Vec<f64>; 8] {
std::array::from_fn(|k| v.iter().map(|q| q.x[k]).collect())
}
pub fn from_parts(c: [&[f64]; 8]) -> Vec<OD> {
let n = c[0].len();
assert!(
c.iter().all(|p| p.len() == n),
"from_parts: the eight arrays must have one length"
);
(0..n)
.map(|i| OD {
x: std::array::from_fn(|k| c[k][i]),
})
.collect()
}
#[derive(Clone, Copy, Debug, Default)]
pub struct OdAcc {
pub s: [f64; 9],
}
impl OdAcc {
pub fn zero() -> OdAcc {
OdAcc::default()
}
#[inline]
pub fn add_prod(self, a: OD, b: OD) -> OdAcc {
let t = alg::expand(&a.x, &b.x);
let mut s = self.s;
alg::acc_levels(&mut s, &t);
OdAcc { s }
}
#[inline]
pub fn add(self, a: OD) -> OdAcc {
let mut s = self.s;
alg::acc_levels(&mut s, &a.x);
OdAcc { s }
}
#[inline]
pub fn finish(self) -> OD {
let mut r = OD::from_f64(self.s[0]);
for l in 1..9 {
r = r.add_f(self.s[l]);
}
r
}
}