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() {
return f64::NAN;
}
x - trunc(x / y) * y
}
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;
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;
}
y = round_sqrt(m, y);
y * powi(2.0, e)
}
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(m: 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(m, y);
for &c in &[down, up] {
if c > 0.0 && c.is_finite() {
let err = abs_residual(m, c);
if err < best_err {
best_err = err;
best = c;
}
}
}
best
}
fn abs_residual(m: f64, y: f64) -> f64 {
let (p, e) = two_prod(y, y);
abs((p - m) + 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;
}
let k = round(x / LN2);
let r = x - k * LN2; let mut term = 1.0;
let mut sum = 1.0;
for n in 1..20 {
term *= r / n as f64;
sum += term;
}
sum * powi(2.0, k as i32)
}
pub fn ln(x: f64) -> f64 {
if x.is_nan() || x < 0.0 {
return f64::NAN;
}
if x == 0.0 {
return f64::NEG_INFINITY;
}
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;
}
if y == trunc(y) && abs(y) <= 1024.0 {
return powi(base, y as i32);
}
if base < 0.0 {
return f64::NAN; }
if base == 0.0 {
return 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)
}
fn reduce_quarter_pi(x: f64) -> (i64, f64) {
let half_pi = PI / 2.0;
let k = round(x / half_pi);
let r = x - k * half_pi;
(k as i64, r)
}
fn sin_kernel(r: f64) -> f64 {
let r2 = r * r;
let mut term = r;
let mut sum = r;
for n in 1..12 {
term *= -r2 / ((2 * n) as f64 * (2 * n + 1) as f64);
sum += term;
}
sum
}
fn cos_kernel(r: f64) -> f64 {
let r2 = r * r;
let mut term = 1.0;
let mut sum = 1.0;
for n in 1..12 {
term *= -r2 / ((2 * n - 1) as f64 * (2 * n) as f64);
sum += term;
}
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 {
if x > 0.0 {
atan(y / x)
} else if x < 0.0 {
if y >= 0.0 {
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 {
0.0
}
}
}
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);
}
}