use crate::error::{CoreError, ErrorContext};
use crate::precision::compensated::two_sum;
pub fn faithful_sqrt(x: f64) -> Result<f64, CoreError> {
if x.is_nan() {
return Err(CoreError::DomainError(ErrorContext::new(
"faithful_sqrt: argument is NaN".to_string(),
)));
}
if x < 0.0 {
return Err(CoreError::DomainError(ErrorContext::new(format!(
"faithful_sqrt: argument {x} is negative"
))));
}
Ok(x.sqrt())
}
pub fn faithful_exp(x: f64) -> Result<f64, CoreError> {
if x.is_nan() {
return Err(CoreError::DomainError(ErrorContext::new(
"faithful_exp: argument is NaN".to_string(),
)));
}
const EXP_MAX: f64 = 709.782_931_862_957_4;
const EXP_MIN: f64 = -745.133_219_101_941_6;
if x > EXP_MAX {
return Ok(f64::INFINITY);
}
if x < EXP_MIN {
return Ok(0.0);
}
const LN2: f64 = core::f64::consts::LN_2;
const INV_LN2: f64 = 1.442_695_040_888_963_4_f64;
let k = (x * INV_LN2).round();
let r = x - k * LN2;
const C2: f64 = 0.500_000_000_000_000_00;
const C3: f64 = 0.166_666_666_666_666_67;
const C4: f64 = 0.041_666_666_666_666_664;
const C5: f64 = 0.008_333_333_333_333_334;
const C6: f64 = 0.001_388_888_888_888_889;
const C7: f64 = 0.000_198_412_698_412_698_4;
let r2 = r * r;
let p = r
+ r2 * (C2
+ r * (C3 + r * (C4 + r * (C5 + r * (C6 + r * C7)))));
let exp_r = 1.0 + p;
let k_int = k as i64;
Ok(ldexp_f64(exp_r, k_int))
}
pub fn faithful_log(x: f64) -> Result<f64, CoreError> {
if x.is_nan() {
return Err(CoreError::DomainError(ErrorContext::new(
"faithful_log: argument is NaN".to_string(),
)));
}
if x < 0.0 {
return Err(CoreError::DomainError(ErrorContext::new(format!(
"faithful_log: argument {x} is negative"
))));
}
if x == 0.0 {
return Ok(f64::NEG_INFINITY);
}
if x.is_infinite() {
return Ok(f64::INFINITY);
}
let (m, e) = frexp_f64(x);
let (m, e) = if m < 0.5 {
(m * 2.0, e - 1)
} else {
(m * 2.0, e - 1)
};
const SQRT2: f64 = core::f64::consts::SQRT_2;
let (m, e) = if m > SQRT2 {
(m / 2.0, e + 1)
} else {
(m, e)
};
let f = (m - 1.0) / (m + 1.0);
let f2 = f * f;
const A1: f64 = 2.0;
const A3: f64 = 0.666_666_666_666_666_63; const A5: f64 = 0.400_000_000_000_000_02; const A7: f64 = 0.285_714_285_714_285_71; const A9: f64 = 0.222_222_222_222_222_22;
let log_m = f * (A1 + f2 * (A3 + f2 * (A5 + f2 * (A7 + f2 * A9))));
const LN2: f64 = core::f64::consts::LN_2;
Ok(e as f64 * LN2 + log_m)
}
pub fn error_free_sum(values: &[f64]) -> Result<Vec<f64>, CoreError> {
if values.is_empty() {
return Err(CoreError::InvalidInput(ErrorContext::new(
"error_free_sum: empty slice".to_string(),
)));
}
let mut p: Vec<f64> = values.to_vec();
let n = p.len();
let mut s = 0.0_f64;
for i in 0..n {
let (sigma, q) = two_sum(s, p[i]);
p[i] = q; s = sigma;
}
let mut result = Vec::with_capacity(n + 1);
result.push(s);
result.extend_from_slice(&p);
Ok(result)
}
pub fn error_free_product(a: f64, b: f64) -> (f64, f64) {
use crate::precision::compensated::two_product;
two_product(a, b)
}
fn ldexp_f64(x: f64, exp: i64) -> f64 {
if x == 0.0 || x.is_nan() || x.is_infinite() {
return x;
}
const F64_EXPONENT_BIAS: i64 = 1023;
const F64_MANTISSA_BITS: i64 = 52;
const F64_MAX_EXP: i64 = 1023;
const F64_MIN_EXP: i64 = -1022;
let bits = x.to_bits();
let stored_exp = ((bits >> 52) & 0x7FF) as i64;
if stored_exp == 0 {
return x * f64::from_bits(((exp + F64_EXPONENT_BIAS).clamp(0, 2046) as u64) << 52);
}
let new_exp = stored_exp + exp;
if new_exp >= F64_MAX_EXP + F64_EXPONENT_BIAS + 1 {
return if x > 0.0 {
f64::INFINITY
} else {
f64::NEG_INFINITY
};
}
if new_exp <= F64_MIN_EXP + F64_EXPONENT_BIAS - F64_MANTISSA_BITS - 1 {
return 0.0;
}
if new_exp <= 0 {
let shift = (1 - new_exp) as u32;
if shift >= 53 {
return 0.0;
}
let mantissa = (bits & 0x000F_FFFF_FFFF_FFFF) | 0x0010_0000_0000_0000; let sign = bits & 0x8000_0000_0000_0000;
return f64::from_bits(sign | (mantissa >> shift));
}
let new_bits = (bits & 0x800F_FFFF_FFFF_FFFF) | ((new_exp as u64) << 52);
f64::from_bits(new_bits)
}
fn frexp_f64(x: f64) -> (f64, i64) {
if x == 0.0 {
return (0.0, 0);
}
let bits = x.to_bits();
let sign = bits & 0x8000_0000_0000_0000;
let raw_exp = ((bits >> 52) & 0x7FF) as i64;
let mantissa_bits = bits & 0x000F_FFFF_FFFF_FFFF;
if raw_exp == 0 {
let normal = x * (1u64 << 53) as f64;
let (m, e) = frexp_f64(normal);
return (m, e - 53);
}
let e = raw_exp - 1022;
let m_bits = sign | (1022u64 << 52) | mantissa_bits;
(f64::from_bits(m_bits), e)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn faithful_sqrt_two() {
let s = faithful_sqrt(2.0).expect("valid");
assert!((s - std::f64::consts::SQRT_2).abs() < 1e-15);
}
#[test]
fn faithful_sqrt_zero() {
assert_eq!(faithful_sqrt(0.0).expect("valid"), 0.0);
}
#[test]
fn faithful_sqrt_negative_error() {
assert!(faithful_sqrt(-1.0).is_err());
}
#[test]
fn faithful_sqrt_nan_error() {
assert!(faithful_sqrt(f64::NAN).is_err());
}
#[test]
fn faithful_exp_zero() {
let e = faithful_exp(0.0).expect("valid");
assert!((e - 1.0).abs() < 1e-14);
}
#[test]
fn faithful_exp_one() {
let e = faithful_exp(1.0).expect("valid");
assert!((e - std::f64::consts::E).abs() < 1e-12);
}
#[test]
fn faithful_exp_overflow() {
let e = faithful_exp(800.0).expect("valid");
assert_eq!(e, f64::INFINITY);
}
#[test]
fn faithful_exp_underflow() {
let e = faithful_exp(-800.0).expect("valid");
assert_eq!(e, 0.0);
}
#[test]
fn faithful_exp_nan_error() {
assert!(faithful_exp(f64::NAN).is_err());
}
#[test]
fn faithful_log_one() {
let l = faithful_log(1.0).expect("valid");
assert!(l.abs() < 1e-14);
}
#[test]
fn faithful_log_e() {
let l = faithful_log(std::f64::consts::E).expect("valid");
assert!((l - 1.0).abs() < 1e-12);
}
#[test]
fn faithful_log_zero() {
let l = faithful_log(0.0).expect("valid");
assert_eq!(l, f64::NEG_INFINITY);
}
#[test]
fn faithful_log_negative_error() {
assert!(faithful_log(-1.0).is_err());
}
#[test]
fn faithful_log_nan_error() {
assert!(faithful_log(f64::NAN).is_err());
}
#[test]
fn error_free_sum_basic() {
let data = [1.0_f64, 2.0, 3.0];
let parts = error_free_sum(&data).expect("valid");
let reconstructed: f64 = parts.iter().sum();
assert!((reconstructed - 6.0).abs() < 1e-14);
}
#[test]
fn error_free_sum_empty_error() {
assert!(error_free_sum(&[]).is_err());
}
#[test]
fn error_free_product_exact() {
let (p, e) = error_free_product(3.0, 7.0);
assert_eq!(p, 21.0);
assert_eq!(e, 0.0);
}
#[test]
fn error_free_product_reconstruct() {
let a = 1.0_f64 / 3.0;
let b = 3.0_f64;
let (p, e) = error_free_product(a, b);
assert!((p + e - 1.0).abs() < 1e-15);
}
#[test]
fn frexp_basic() {
let (m, e) = frexp_f64(8.0);
assert!((m - 0.5).abs() < 1e-15);
assert_eq!(e, 4);
}
#[test]
fn ldexp_basic() {
let x = ldexp_f64(1.0, 3);
assert_eq!(x, 8.0);
let y = ldexp_f64(1.0, -1);
assert_eq!(y, 0.5);
}
#[test]
fn exp_log_roundtrip() {
for v in [0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 100.0] {
let l = faithful_log(v).expect("log valid");
let e = faithful_exp(l).expect("exp valid");
assert!((e - v).abs() < v * 1e-10, "v={v}, e={e}");
}
}
}