use super::{DoubleDouble, fast_two_sum, two_prod, two_sum_safe};
const MAX_DIVISION_STEPS: usize = 64;
const MAX_CORRECTIONS: usize = 4;
struct Expansion(Vec<f64>);
impl Expansion {
fn of(x: DoubleDouble) -> Self {
let mut v = Vec::with_capacity(16);
for c in [x.lo, x.hi] {
if c != 0.0 {
v.push(c);
}
}
Self(v)
}
fn add(&mut self, b: f64) {
let mut q = b;
let mut k = 0;
for i in 0..self.0.len() {
let (s, h) = two_sum_safe(q, self.0[i]);
q = s;
if h != 0.0 {
self.0[k] = h;
k += 1;
}
}
self.0.truncate(k);
if q != 0.0 {
self.0.push(q);
}
}
fn add_dd(&mut self, y: DoubleDouble) {
self.add(y.hi);
self.add(y.lo);
}
fn sub_dd(&mut self, y: DoubleDouble) {
self.add(-y.hi);
self.add(-y.lo);
}
fn sub_multiple(&mut self, n: f64, y: DoubleDouble) {
let (p1, e1) = two_prod(n, y.hi);
let (p2, e2) = two_prod(n, y.lo);
for t in [p1, e1, p2, e2] {
self.add(-t);
}
self.compress();
}
fn compress(&mut self) {
let e = &mut self.0;
let m = e.len();
if m < 2 {
return;
}
let mut bottom = m - 1;
let mut q = e[m - 1];
for i in (0..m - 1).rev() {
let (s, t) = fast_two_sum(q, e[i]);
if t != 0.0 {
e[bottom] = s;
bottom -= 1;
q = t;
} else {
q = s;
}
}
e[bottom] = q;
let mut top = 0;
for i in bottom + 1..m {
let (s, t) = fast_two_sum(e[i], q);
if t != 0.0 {
e[top] = t;
top += 1;
}
q = s;
}
e[top] = q;
e.truncate(top + 1);
e.retain(|&c| c != 0.0); }
fn is_negative(&self) -> bool {
self.0.last().is_some_and(|&c| c < 0.0)
}
fn approx(&self) -> f64 {
self.0.iter().fold(0.0, |s, &c| s + c)
}
fn into_dd(mut self) -> DoubleDouble {
self.compress();
self.0
.iter()
.fold(DoubleDouble::ZERO, |acc, &c| acc.add_f64(c))
}
}
fn exponent(v: f64) -> i32 {
let bits = v.to_bits() & !(1 << 63);
match (bits >> 52) as i32 {
0 => -1011 - bits.leading_zeros() as i32,
biased => biased - 1023,
}
}
fn just_below(y: DoubleDouble) -> DoubleDouble {
let lo = if y.lo == 0.0 {
-f64::from_bits(1) } else if y.lo > 0.0 {
f64::from_bits(y.lo.to_bits() - 1)
} else {
f64::from_bits(y.lo.to_bits() + 1)
};
DoubleDouble::new(y.hi, lo)
}
pub(super) fn rem(x: DoubleDouble, y: DoubleDouble) -> DoubleDouble {
if !x.hi.is_finite() || y.hi.is_nan() || y.hi == 0.0 {
return DoubleDouble::NAN;
}
if y.hi.is_infinite() || x.hi == 0.0 {
return x;
}
let (xa, ya) = (x.abs(), y.abs());
if xa < ya {
return x;
}
let mut r = Expansion::of(xa);
let mut a = r.approx();
let mut steps = 0;
while a != 0.0 {
if !a.is_finite() || steps == MAX_DIVISION_STEPS {
return DoubleDouble::NAN;
}
let k = exponent(a) - exponent(ya.hi) - 51;
if k <= 0 {
break;
}
let ys = ya.ldexp(k); let n = (a / ys.hi * (1.0 - 2f64.powi(-50))).trunc();
r.sub_multiple(n, ys);
a = r.approx();
steps += 1;
}
let mut n = (a / ya.hi).trunc();
if !(n * ya.hi).is_finite() {
n -= n.signum(); }
r.sub_multiple(n, ya);
let mut corrections = 0;
while r.is_negative() {
r.add_dd(ya);
corrections += 1;
if corrections > MAX_CORRECTIONS {
return DoubleDouble::NAN;
}
}
loop {
r.sub_dd(ya);
if r.is_negative() {
r.add_dd(ya);
break;
}
corrections += 1;
if corrections > MAX_CORRECTIONS {
return DoubleDouble::NAN;
}
}
if !r.approx().is_finite() {
return DoubleDouble::NAN;
}
let mut z = r.into_dd();
if z >= ya {
z = just_below(ya);
}
if x.hi.is_sign_negative() { -z } else { z }
}