use super::consts::{INV_FACT, LN_2, LN_2_TAIL, LOG2_E, LOG10_E};
use super::{DoubleDouble as Dd, pow2, two_prod};
const LN_1P_SERIES_LIMIT: f64 = 0.25;
const EXP_M1_SMALL: f64 = 0.5;
const TANH_SWITCH: f64 = 1.1;
const EXP_FAST_MIN: f64 = -670.0;
const EXP_FAST_MAX: f64 = 708.0;
const EXP2_FAST_MIN: f64 = -966.0;
const EXP2_FAST_MAX: f64 = 1022.0;
const EXP_WIDE_LIMIT: f64 = 746.0;
const HYPERBOLIC_EXP_ONLY: f64 = 40.0;
const LN_NEWTON_MIN: f64 = 1e-270;
const LN_NEWTON_MAX: f64 = 1e270;
const LN_2X_ABOVE: f64 = 1e150;
const ROOT_MIN: f64 = two_pow(-900);
const ROOT_MAX: f64 = two_pow(1000);
const ROOT_RESCALE: i32 = 600;
const POWI_PLAIN_EXPONENT: i64 = 960;
const POWF_POWI_LIMIT: f64 = 7.0;
const fn two_pow(e: i32) -> f64 {
f64::from_bits(((e + 1023) as u64) << 52)
}
#[inline]
fn exponent(x: f64) -> i32 {
((x.to_bits() >> 52) & 0x7ff) as i32 - 1023
}
fn scale(x: Dd, e: i64) -> Dd {
x.ldexp(e.clamp(-2200, 2200) as i32)
}
#[inline]
fn expm1_kernel(r: Dd) -> Dd {
let mut p = INV_FACT[10];
for k in (2..10).rev() {
p = p * r + INV_FACT[k];
}
(p * r + 1.0) * r
}
fn expm1_small(x: Dd) -> Dd {
let k = (exponent(x.hi) + 11).clamp(0, 9);
let mut s = expm1_kernel(x.mul_pow2(two_pow(-k)));
for _ in 0..k {
s = s.mul_pow2(2.0) + s * s;
}
s
}
fn exp_reduced(x: Dd) -> (Dd, i32) {
let m = (x.hi * LOG2_E.hi()).round();
let (a, b) = two_prod(m, LN_2.hi());
let (c, d) = two_prod(m, LN_2.lo());
let r = x - Dd::from_parts_unchecked(a, b) - Dd::from_parts_unchecked(c, d) - m * LN_2_TAIL;
(expm1_small(r) + 1.0, m as i32)
}
#[cold]
#[inline(never)]
fn exp_wide(x: Dd, k: i32) -> Dd {
let h = x.hi;
if h > EXP_WIDE_LIMIT {
return Dd::INFINITY;
}
if h < -EXP_WIDE_LIMIT {
return Dd::ZERO;
}
if h.is_nan() {
return Dd::NAN;
}
let (p, m) = exp_reduced(x);
scale(p, i64::from(m + k))
}
#[inline]
fn exp_half(a: Dd) -> Dd {
if a.hi <= EXP_FAST_MAX {
let (p, m) = exp_reduced(a);
return p.mul_pow2(pow2(m - 1));
}
exp_wide(a, -1)
}
fn ln_1p_series(x: Dd) -> Dd {
let w = x.mul_pow2(2.0) / (x + 2.0); let z2 = (w * w).mul_pow2(0.25); let mut power = z2;
let mut sum = Dd::ONE;
let mut k = 3.0;
loop {
let t = power / k;
sum += t;
if t.hi <= 1e-34 || k > 121.0 {
break;
}
power *= z2;
k += 2.0;
}
w * sum
}
#[inline]
fn ln_newton(a: Dd, x0: f64) -> Dd {
let t = a * Dd::from_f64(-x0).exp() - 1.0;
t.add_f64(-0.5 * t.hi * t.hi) + x0
}
#[cold]
#[inline(never)]
fn ln_wide(a: Dd) -> Dd {
let x = a.hi;
if x > 0.0 && x < f64::INFINITY {
let k = if x < 1.0 { 1000 } else { -1000 };
return a.ldexp(k).ln() - LN_2 * f64::from(k);
}
if x == 0.0 {
return Dd::NEG_INFINITY;
}
if x == f64::INFINITY {
return Dd::INFINITY;
}
Dd::NAN
}
#[inline]
fn sqrt_kernel(x: Dd) -> Dd {
let y = x.hi.sqrt();
let (p, e) = two_prod(y, y);
let resid = (x - Dd::from_parts_unchecked(p, e)).hi;
Dd::new(y, resid / (2.0 * y))
}
#[cold]
#[inline(never)]
fn sqrt_wide(x: Dd) -> Dd {
let h = x.hi;
if h == 0.0 {
return Dd::from_f64(h); }
if h.is_nan() || h < 0.0 {
return Dd::NAN;
}
if h == f64::INFINITY {
return Dd::INFINITY;
}
let k = if h < 1.0 { ROOT_RESCALE } else { -ROOT_RESCALE };
sqrt_kernel(x.ldexp(k)).ldexp(-k / 2)
}
#[inline]
fn cbrt_kernel(x: Dd, y: f64) -> Dd {
let r = (x - Dd::from_f64(y) * y * y).hi;
let d = r / (3.0 * y * y);
Dd::new(y, d - d * d / y)
}
#[cold]
#[inline(never)]
fn cbrt_wide(x: Dd) -> Dd {
let h = x.hi;
if h == 0.0 || !h.is_finite() {
return Dd::from_f64(h.cbrt()); }
let k = if h.abs() < 1.0 {
ROOT_RESCALE
} else {
-ROOT_RESCALE
};
let x = x.ldexp(k);
cbrt_kernel(x, x.hi.cbrt()).ldexp(-k / 3)
}
#[inline]
fn powi_plain(x: Dd, n: i32) -> Dd {
let mut result = Dd::ONE;
let mut base = x;
let mut e = n.unsigned_abs();
while e > 0 {
if e & 1 == 1 {
result *= base;
}
e >>= 1;
if e > 0 {
base *= base;
}
}
if n < 0 { result.recip() } else { result }
}
fn frexp(x: Dd) -> (Dd, i64) {
let (x, offset) = if x.hi.abs() < f64::MIN_POSITIVE {
(x.mul_pow2(two_pow(54)), -54) } else {
(x, 0)
};
let e = exponent(x.hi);
(x.ldexp(-e), i64::from(e + offset))
}
#[inline]
fn renormalise(m: Dd, e: i64) -> (Dd, i64) {
let k = exponent(m.hi); (m.mul_pow2(two_pow(-k)), e + i64::from(k))
}
#[cold]
#[inline(never)]
fn powi_wide(x: Dd, n: i32) -> Dd {
let h = x.hi;
if h == 0.0 || !h.is_finite() {
return Dd::from_f64(h.powi(n)); }
let (mut base, mut base_exp) = frexp(x);
let (mut result, mut result_exp) = (Dd::ONE, 0_i64);
let mut e = n.unsigned_abs();
loop {
if e & 1 == 1 {
(result, result_exp) = renormalise(result * base, result_exp + base_exp);
}
e >>= 1;
if e == 0 {
break;
}
(base, base_exp) = renormalise(base * base, 2 * base_exp);
}
if n < 0 {
result = result.recip();
result_exp = -result_exp;
}
scale(result, result_exp)
}
fn is_odd(y: Dd) -> bool {
(y.hi % 2.0 != 0.0) != (y.lo % 2.0 != 0.0)
}
#[cold]
#[inline(never)]
fn powf_special(x: Dd, y: Dd) -> Dd {
if y.hi == 0.0 || x == Dd::ONE {
return Dd::ONE; }
if x.hi.is_nan() || y.hi.is_nan() {
return Dd::NAN;
}
let ax = x.abs();
if y.hi.is_infinite() {
if ax == Dd::ONE {
return Dd::ONE;
}
return if (ax > Dd::ONE) == (y.hi > 0.0) {
Dd::INFINITY
} else {
Dd::ZERO
};
}
let integer = y == y.trunc();
let magnitude = if x.hi == 0.0 || x.hi.is_infinite() {
if (x.hi == 0.0) == (y.hi < 0.0) {
Dd::INFINITY
} else {
Dd::ZERO
}
} else if !integer {
return Dd::NAN;
} else {
ax.powf(y) };
if x.hi.is_sign_negative() && integer && is_odd(y) {
-magnitude
} else {
magnitude
}
}
impl Dd {
#[inline]
pub fn sqrt(self) -> Self {
if (ROOT_MIN..=ROOT_MAX).contains(&self.hi) {
return sqrt_kernel(self);
}
sqrt_wide(self)
}
pub fn exp(self) -> Self {
if (EXP_FAST_MIN..=EXP_FAST_MAX).contains(&self.hi) {
let (p, m) = exp_reduced(self);
return p.mul_pow2(pow2(m));
}
exp_wide(self, 0)
}
pub fn exp_m1(self) -> Self {
if self.hi.abs() < EXP_M1_SMALL {
return expm1_small(self);
}
self.exp() - 1.0
}
pub fn ln(self) -> Self {
let x = self.hi;
if (x - 1.0).abs() < LN_1P_SERIES_LIMIT {
return ln_1p_series(self - 1.0);
}
if (LN_NEWTON_MIN..=LN_NEWTON_MAX).contains(&x) {
return ln_newton(self, x.ln());
}
ln_wide(self)
}
pub fn ln_1p(self) -> Self {
if self.hi.abs() < LN_1P_SERIES_LIMIT {
return ln_1p_series(self);
}
(self + 1.0).ln()
}
pub fn exp2(self) -> Self {
let x = self.hi;
if (EXP2_FAST_MIN..=EXP2_FAST_MAX).contains(&x) {
let n = x.round();
let p = expm1_small((self - n) * LN_2) + 1.0;
return p.mul_pow2(pow2(n as i32));
}
exp2_wide(self)
}
pub fn log2(self) -> Self {
self.ln() * LOG2_E
}
pub fn log10(self) -> Self {
self.ln() * LOG10_E
}
pub fn log(self, base: Self) -> Self {
self.ln() / base.ln()
}
pub fn powi(self, n: i32) -> Self {
let k = i64::from(exponent(self.hi).abs()) + 1;
if k * i64::from(n.unsigned_abs()) <= POWI_PLAIN_EXPONENT {
return powi_plain(self, n);
}
powi_wide(self, n)
}
pub fn powf(self, y: Self) -> Self {
let x = self.hi;
if y.lo == 0.0 && y.hi.abs() <= POWF_POWI_LIMIT && y.hi == y.hi.trunc() {
if (x.is_finite() && x != 0.0) || y.hi == 0.0 {
return self.powi(y.hi as i32);
}
} else if x > 0.0 && x < f64::INFINITY && y.hi.is_finite() {
if y.hi == 0.5 && y.lo == 0.0 {
return self.sqrt();
}
return (y * self.ln()).exp();
}
powf_special(self, y)
}
pub fn cbrt(self) -> Self {
if (ROOT_MIN..=ROOT_MAX).contains(&self.hi.abs()) {
return cbrt_kernel(self, self.hi.cbrt());
}
cbrt_wide(self)
}
pub fn hypot(self, other: Self) -> Self {
let (a, b) = (self.abs(), other.abs());
if a.hi.is_infinite() || b.hi.is_infinite() {
return Self::INFINITY;
}
if a.hi.is_nan() || b.hi.is_nan() {
return Self::NAN;
}
let (big, small) = if a >= b { (a, b) } else { (b, a) };
if big.hi == 0.0 {
return Self::ZERO;
}
let r = small / big;
big * (r * r + 1.0).sqrt()
}
pub fn sinh(self) -> Self {
let a = self.hi.abs();
if a < 0.5 {
let e = self.exp_m1();
return (e * (e + 2.0) / (e + 1.0)).mul_pow2(0.5);
}
if a <= HYPERBOLIC_EXP_ONLY {
let ex = self.exp();
return (ex - ex.recip()).mul_pow2(0.5);
}
let r = exp_half(self.abs());
if self.hi < 0.0 { -r } else { r }
}
pub fn cosh(self) -> Self {
let a = self.abs();
if a.hi <= HYPERBOLIC_EXP_ONLY {
let ex = a.exp();
return (ex + ex.recip()).mul_pow2(0.5);
}
exp_half(a)
}
pub fn tanh(self) -> Self {
if self.hi.abs() <= HYPERBOLIC_EXP_ONLY {
let a = self.abs().mul_pow2(2.0);
let r = if a.hi < TANH_SWITCH {
let e = a.exp_m1();
e / (e + 2.0)
} else {
1.0 - 2.0 / (a.exp() + 1.0)
};
return if self.hi.is_sign_negative() { -r } else { r };
}
if self.hi.is_nan() {
return Self::NAN;
}
Dd::from_f64(1.0_f64.copysign(self.hi))
}
pub fn asinh(self) -> Self {
if !self.hi.is_finite() {
return self;
}
let a = self.abs();
let r = if a.hi > LN_2X_ABOVE {
a.ln() + LN_2
} else {
let a2 = a * a;
(a + a2 / ((a2 + 1.0).sqrt() + 1.0)).ln_1p()
};
if self.hi.is_sign_negative() { -r } else { r }
}
pub fn acosh(self) -> Self {
if self.hi.is_nan() || self < Self::ONE {
return Self::NAN;
}
if self.hi > LN_2X_ABOVE {
return self.ln() + LN_2; }
let t = self - 1.0;
(t + (t * (self + 1.0)).sqrt()).ln_1p()
}
pub fn atanh(self) -> Self {
let a = self.abs();
if a.hi.is_nan() || a >= Self::ONE {
if a == Self::ONE {
return Dd::from_f64(f64::INFINITY.copysign(self.hi));
}
return Self::NAN;
}
let r = (a.mul_pow2(2.0) / (Self::ONE - a)).ln_1p().mul_pow2(0.5);
if self.hi.is_sign_negative() { -r } else { r }
}
}
#[cold]
#[inline(never)]
fn exp2_wide(x: Dd) -> Dd {
let h = x.hi;
if h > 1025.0 {
return Dd::INFINITY;
}
if h < -1076.0 {
return Dd::ZERO;
}
if h.is_nan() {
return Dd::NAN;
}
let n = h.round();
let p = expm1_small((x - n) * LN_2) + 1.0;
scale(p, n as i64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn refinements_tolerate_inaccurate_starting_values() {
let xs = [
Dd::new(3.0, 1.1e-16),
Dd::new(0.6, -2.3e-17),
Dd::new(1.3, 4.1e-17),
Dd::new(1e-200, 3.7e-217),
Dd::new(7.5e250, -2.9e234),
Dd::new(1e-269, 1.3e-286),
Dd::new(9.99e269, 2.1e253),
];
let (ln_tol, cbrt_tol) = (two_pow(-102), two_pow(-101));
for x in xs {
let (ln, cbrt) = (x.ln(), x.cbrt());
for ulps in [-16.0, -4.0, -1.0, 1.0, 4.0, 16.0] {
let x0 = x.hi.ln() * (1.0 + ulps * f64::EPSILON);
let got = ln_newton(x, x0);
let err = ((got - ln) / ln).abs().hi;
assert!(err <= ln_tol, "ln {x:?} from {x0}: {got:?}");
if ulps.abs() <= 4.0 {
let y0 = x.hi.cbrt() * (1.0 + ulps * f64::EPSILON);
let got = cbrt_kernel(x, y0);
let err = ((got - cbrt) / cbrt).abs().hi;
assert!(err <= cbrt_tol, "cbrt {x:?} from {y0}: {got:?}");
}
}
}
}
}