pub fn abs(x: f64) -> f64 {
if x < 0.0 {
-x
} else {
x
}
}
pub fn trunc(x: f64) -> f64 {
if !x.is_finite() {
return x;
}
if abs(x) >= 4_503_599_627_370_496.0 {
return x;
}
(x as i64) as f64
}
pub fn floor(x: f64) -> f64 {
let t = trunc(x);
if t > x {
t - 1.0
} else {
t
}
}
pub fn round(x: f64) -> f64 {
if !x.is_finite() {
return x;
}
let bump = if x < 0.0 { -0.5 } else { 0.5 };
trunc(x + bump)
}
pub fn fmod(x: f64, y: f64) -> f64 {
if y == 0.0 || !x.is_finite() || y.is_nan() {
return f64::NAN;
}
if y.is_infinite() {
return x;
}
let sign = x < 0.0;
let mut a = abs(x);
let b = abs(y);
if a < b {
return x; }
while a >= b {
let mut scaled = b;
while scaled + scaled <= a {
scaled += scaled;
}
a -= scaled;
}
if sign {
-a
} else {
a
}
}
pub fn powi(base: f64, exp: i32) -> f64 {
if exp < 0 {
return 1.0 / powi(base, -exp);
}
let mut acc = 1.0;
let mut b = base;
let mut e = exp as u32;
while e > 0 {
if e & 1 == 1 {
acc *= b;
}
b *= b;
e >>= 1;
}
acc
}
pub const PI: f64 = core::f64::consts::PI;
const LN2: f64 = core::f64::consts::LN_2;
const LN10: f64 = core::f64::consts::LN_10;
const INV_LN2: f64 = core::f64::consts::LOG2_E;
const LN2_HI: f64 = 0.693_147_167_563_438_4;
const LN2_LO: f64 = 1.299_650_689_388_988_9e-8;
fn ldexp(x: f64, k: i32) -> f64 {
if k > 1023 {
let mut y = x * powi(2.0, 1023);
let mut rem = k - 1023;
while rem > 1023 {
y *= powi(2.0, 1023);
rem -= 1023;
}
y * powi(2.0, rem)
} else if k < -1022 {
let mut y = x * powi(2.0, -1022);
let mut rem = k + 1022;
while rem < -1022 {
y *= powi(2.0, -1022);
rem += 1022;
}
y * powi(2.0, rem)
} else {
x * powi(2.0, k)
}
}
pub fn ceil(x: f64) -> f64 {
-floor(-x)
}
pub fn sqrt(x: f64) -> f64 {
if x.is_nan() || x < 0.0 {
return f64::NAN;
}
if x == 0.0 || !x.is_finite() {
return x; }
let mut m = x;
let mut e: i32 = 0;
while m >= 4.0 {
m /= 4.0;
e += 1;
}
while m < 1.0 {
m *= 4.0;
e -= 1;
}
let mut y = m; for _ in 0..40 {
let ny = 0.5 * (y + m / y);
if ny == y {
break;
}
y = ny;
}
let mut full = y * powi(2.0, e);
let (p, err) = two_prod(full, full);
let resid = (p - x) + err; full -= resid / (2.0 * full);
round_sqrt(x, full)
}
fn split(a: f64) -> (f64, f64) {
let c = 134_217_729.0 * a; let hi = c - (c - a);
(hi, a - hi)
}
fn two_prod(a: f64, b: f64) -> (f64, f64) {
let p = a * b;
let (ah, al) = split(a);
let (bh, bl) = split(b);
let e = ((ah * bh - p) + ah * bl + al * bh) + al * bl;
(p, e)
}
fn round_sqrt(x: f64, y: f64) -> f64 {
let up = f64::from_bits(y.to_bits() + 1);
let down = f64::from_bits(y.to_bits().wrapping_sub(1));
let mut best = y;
let mut best_err = abs_residual(x, y);
for &c in &[down, up] {
if c > 0.0 && c.is_finite() {
let err = abs_residual(x, c);
if err < best_err {
best_err = err;
best = c;
}
}
}
best
}
fn abs_residual(x: f64, y: f64) -> f64 {
let (p, e) = two_prod(y, y);
abs((p - x) + e)
}
pub fn exp(x: f64) -> f64 {
if x.is_nan() {
return x;
}
if x == f64::INFINITY {
return x;
}
if x == f64::NEG_INFINITY {
return 0.0;
}
if x > 709.8 {
return f64::INFINITY;
}
if x < -745.2 {
return 0.0;
}
let k = round(x * INV_LN2);
let r = (x - k * LN2_HI) - k * LN2_LO; let mut term = r;
let mut sum = r;
for n in 2..18 {
term *= r / n as f64;
sum += term;
}
ldexp(1.0 + sum, k as i32)
}
pub fn ln(x: f64) -> f64 {
if x.is_nan() || x <= 0.0 {
return f64::NAN;
}
if x == f64::INFINITY {
return x;
}
let mut m = x;
let mut e: i32 = 0;
while m >= core::f64::consts::SQRT_2 {
m /= 2.0;
e += 1;
}
while m < core::f64::consts::FRAC_1_SQRT_2 {
m *= 2.0;
e -= 1;
}
let s = (m - 1.0) / (m + 1.0);
let s2 = s * s;
let mut term = s;
let mut sum = s;
for k in 1..30 {
term *= s2;
sum += term / (2 * k + 1) as f64;
}
2.0 * sum + e as f64 * LN2
}
pub fn log10(x: f64) -> f64 {
ln(x) / LN10
}
pub fn log2(x: f64) -> f64 {
ln(x) / LN2
}
pub fn pow(base: f64, y: f64) -> f64 {
if y == 0.0 || base == 1.0 {
return 1.0;
}
if base.is_nan() || y.is_nan() {
return f64::NAN;
}
let integral = y == trunc(y);
if integral && abs(y) <= 1024.0 {
return powi(base, y as i32);
}
if base >= 0.0 {
if y == 0.5 {
return sqrt(base);
}
if y == -0.5 && base != 0.0 {
return sqrt(1.0 / base);
}
}
if base < 0.0 {
if !integral {
return f64::NAN;
}
let mag = exp(y * ln(-base));
return if fmod(y, 2.0) == 0.0 { mag } else { -mag };
}
if base == 0.0 {
return if y < 0.0 { f64::INFINITY } else { 0.0 };
}
exp(y * ln(base))
}
pub fn sin(x: f64) -> f64 {
if !x.is_finite() {
return f64::NAN;
}
let (k, r) = reduce_quarter_pi(x);
match k & 3 {
0 => sin_kernel(r),
1 => cos_kernel(r),
2 => -sin_kernel(r),
_ => -cos_kernel(r),
}
}
pub fn cos(x: f64) -> f64 {
if !x.is_finite() {
return f64::NAN;
}
let (k, r) = reduce_quarter_pi(x);
match k & 3 {
0 => cos_kernel(r),
1 => -sin_kernel(r),
2 => -cos_kernel(r),
_ => sin_kernel(r),
}
}
pub fn tan(x: f64) -> f64 {
sin(x) / cos(x)
}
const PIO2_1: f64 = 1.570796012878418;
const PIO2_2: f64 = 3.139164164167596e-7;
const PIO2_3: f64 = 6.223372171896613e-14;
fn reduce_quarter_pi(x: f64) -> (i64, f64) {
let k = round(x * (2.0 / PI));
let (p1, e1) = two_prod(k, PIO2_1);
let r = ((x - p1) - e1) - k * PIO2_2;
let r = r - k * PIO2_3;
(k as i64, r)
}
fn sin_kernel(r: f64) -> f64 {
let r2 = r * r;
let mut term = r;
let mut sum = r;
let mut c = 0.0;
for n in 1..13 {
term *= -r2 / ((2 * n) as f64 * (2 * n + 1) as f64);
let y = term - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum
}
fn cos_kernel(r: f64) -> f64 {
let r2 = r * r;
let mut term = 1.0;
let mut sum = 1.0;
let mut c = 0.0;
for n in 1..13 {
term *= -r2 / ((2 * n - 1) as f64 * (2 * n) as f64);
let y = term - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum
}
pub fn atan(x: f64) -> f64 {
if x.is_nan() {
return x;
}
if x == f64::INFINITY {
return PI / 2.0;
}
if x == f64::NEG_INFINITY {
return -PI / 2.0;
}
let neg = x < 0.0;
let mut a = abs(x);
let mut halvings = 0u32;
while a > 0.1 {
a = a / (1.0 + sqrt(1.0 + a * a));
halvings += 1;
}
let a2 = a * a;
let mut term = a;
let mut sum = a;
for k in 1..30 {
term *= -a2;
sum += term / (2 * k + 1) as f64;
}
let mut result = sum * powi(2.0, halvings as i32);
if neg {
result = -result;
}
result
}
pub fn atan2(y: f64, x: f64) -> f64 {
let y_neg = y.is_sign_negative();
let x_neg = x.is_sign_negative();
if x > 0.0 {
atan(y / x)
} else if x < 0.0 {
if y_neg {
atan(y / x) - PI
} else {
atan(y / x) + PI
}
} else if y > 0.0 {
PI / 2.0
} else if y < 0.0 {
-PI / 2.0
} else {
match (y_neg, x_neg) {
(false, false) => 0.0,
(true, false) => -0.0,
(false, true) => PI,
(true, true) => -PI,
}
}
}
pub fn asin(x: f64) -> f64 {
if x.is_nan() || !(-1.0..=1.0).contains(&x) {
return f64::NAN;
}
if x == 1.0 {
return PI / 2.0;
}
if x == -1.0 {
return -PI / 2.0;
}
atan(x / sqrt(1.0 - x * x))
}
pub fn acos(x: f64) -> f64 {
if x.is_nan() || !(-1.0..=1.0).contains(&x) {
return f64::NAN;
}
PI / 2.0 - asin(x)
}
pub fn sinh(x: f64) -> f64 {
let e = exp(x);
(e - 1.0 / e) / 2.0
}
pub fn cosh(x: f64) -> f64 {
let e = exp(x);
(e + 1.0 / e) / 2.0
}
pub fn tanh(x: f64) -> f64 {
if x > 20.0 {
return 1.0;
}
if x < -20.0 {
return -1.0;
}
let e2 = exp(2.0 * x);
(e2 - 1.0) / (e2 + 1.0)
}
pub fn asinh(x: f64) -> f64 {
ln(x + sqrt(x * x + 1.0))
}
pub fn acosh(x: f64) -> f64 {
if x < 1.0 {
return f64::NAN;
}
ln(x + sqrt(x * x - 1.0))
}
pub fn atanh(x: f64) -> f64 {
if x <= -1.0 || x >= 1.0 {
return if x == 1.0 {
f64::INFINITY
} else if x == -1.0 {
f64::NEG_INFINITY
} else {
f64::NAN
};
}
0.5 * ln((1.0 + x) / (1.0 - x))
}
pub fn degrees(x: f64) -> f64 {
x * (180.0 / PI)
}
pub fn radians(x: f64) -> f64 {
x * (PI / 180.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64) {
assert!((a - b).abs() <= 1e-10 * (1.0 + b.abs()), "{a} vs {b}");
}
#[test]
#[allow(clippy::approx_constant)] fn transcendental() {
close(sqrt(2.0), core::f64::consts::SQRT_2);
close(sqrt(1e300), 1e150);
close(exp(1.0), core::f64::consts::E);
close(ln(core::f64::consts::E), 1.0);
close(ln(1000.0), 6.907_755_278_982_137);
close(log10(1000.0), 3.0);
close(log2(1024.0), 10.0);
close(pow(2.0, 0.5), core::f64::consts::SQRT_2);
close(pow(9.0, 0.5), 3.0);
close(sin(1.0), 0.841_470_984_807_896_5);
close(cos(1.0), 0.540_302_305_868_139_8);
close(tan(1.0), 1.557_407_724_654_902_3);
close(sin(10.0), -0.544_021_110_889_369_8);
close(atan(1.0), PI / 4.0);
close(atan2(1.0, 1.0), PI / 4.0);
close(asin(0.5), 0.523_598_775_598_298_9);
close(acos(0.5), 1.047_197_551_196_597_7);
close(sinh(1.0), 1.175_201_193_643_801_4);
close(cosh(1.0), 1.543_080_634_815_243_7);
close(tanh(0.5), 0.462_117_157_260_009_8);
close(asinh(1.0), 0.881_373_587_019_543);
close(acosh(2.0), 1.316_957_896_924_816_7);
close(atanh(0.5), 0.549_306_144_334_054_8);
close(degrees(PI), 180.0);
close(radians(180.0), PI);
close(ceil(2.1), 3.0);
close(ceil(-2.1), -2.0);
}
#[test]
fn basics() {
assert_eq!(abs(-3.5), 3.5);
assert_eq!(trunc(3.9), 3.0);
assert_eq!(trunc(-3.9), -3.0);
assert_eq!(floor(-3.1), -4.0);
assert_eq!(round(2.5), 3.0);
assert_eq!(round(-2.5), -3.0);
assert_eq!(round(2.4), 2.0);
assert_eq!(powi(10.0, 3), 1000.0);
assert_eq!(powi(2.0, 10), 1024.0);
assert_eq!(fmod(7.5, 2.0), 1.5);
}
#[test]
fn fmod_is_overflow_free() {
assert_eq!(fmod(7.5, 2.0), 1.5);
assert_eq!(fmod(-7.5, 2.0), -1.5);
assert_eq!(fmod(7.5, -2.0), 1.5);
assert_eq!(fmod(1e10, 3.0), 1.0);
assert_eq!(fmod(3.0, 7.0), 3.0);
assert_eq!(fmod(1e308, 1e-300), 1e308 % 1e-300);
assert!(fmod(1e308, 1e-300).is_finite());
assert!(fmod(5.0, 0.0).is_nan());
assert!(fmod(f64::INFINITY, 2.0).is_nan());
assert_eq!(fmod(2.0, f64::INFINITY), 2.0);
}
#[test]
fn exp_overflow_underflow() {
assert!(exp(710.0).is_infinite() && exp(710.0) > 0.0);
assert!(exp(1000.0).is_infinite());
assert!(exp(1e308).is_infinite());
assert_eq!(exp(-1000.0), 0.0);
assert_eq!(exp(-1e308), 0.0);
assert_eq!(exp(-745.0), f64::from_bits(1));
assert_eq!(exp(-746.0), 0.0);
assert!(exp(709.0).is_finite());
close(exp(709.0), 8.218_407_461_554_972e307);
close(exp(1.0), core::f64::consts::E);
}
#[test]
fn ln_domain_is_nan() {
assert!(ln(0.0).is_nan());
assert!(ln(-1.0).is_nan());
assert!(log2(0.0).is_nan());
assert!(log10(0.0).is_nan());
}
#[test]
fn pow_edges() {
assert!(pow(0.0, -1.0).is_infinite() && pow(0.0, -1.0) > 0.0);
assert!(pow(0.0, -0.5).is_infinite() && pow(0.0, -0.5) > 0.0);
assert_eq!(pow(0.0, 0.5), 0.0);
assert!(pow(2.0, 2000.0).is_infinite());
assert!(pow(-2.0, 2000.0).is_infinite() && pow(-2.0, 2000.0) > 0.0);
assert!(pow(-2.0, 1025.0).is_infinite() && pow(-2.0, 1025.0) < 0.0);
assert!(pow(-8.0, 1.0 / 3.0).is_nan());
assert_eq!(pow(2.0, 0.5), sqrt(2.0));
assert_eq!(pow(0.5, -0.5), sqrt(2.0));
assert_eq!(pow(0.0, 0.0), 1.0);
}
#[test]
fn atan2_signed_zero() {
close(atan2(-0.0, -1.0), -PI);
close(atan2(0.0, -1.0), PI);
close(atan2(0.0, -0.0), PI);
close(atan2(-0.0, -0.0), -PI);
assert_eq!(atan2(-0.0, 1.0), 0.0);
assert_eq!(atan2(0.0, 1.0), 0.0);
}
#[test]
fn sqrt_correctly_rounded() {
assert_eq!(sqrt(5.740_547_787_712_544e29), 757_664_027_634_448.5);
assert_eq!(sqrt(4.0), 2.0);
assert_eq!(sqrt(0.0), 0.0);
assert!(sqrt(-1.0).is_nan());
}
}