#![allow(clippy::excessive_precision)]
const PIO2_1: f32 = 1.570_312_5;
const PIO2_2: f32 = 4.837_512_969_970_703_125e-4;
const PIO2_3: f32 = 7.549_789_948_768_648e-8;
const FRAC_2_PI: f32 = core::f32::consts::FRAC_2_PI;
#[inline(always)]
fn sin_poly(r: f32) -> f32 {
let z = r * r;
let p = ((-1.951_529_589_1e-4 * z + 8.332_160_873_6e-3) * z - 1.666_665_461_1e-1) * z * r;
r + p
}
#[inline(always)]
fn cos_poly(r: f32) -> f32 {
let z = r * r;
let p = ((2.443_315_711_809_948e-5 * z - 1.388_731_625_493_765e-3) * z
+ 4.166_664_568_298_827e-2)
* z
* z;
1.0 - 0.5 * z + p
}
#[inline(always)]
fn reduce_pio2(x: f32) -> (i32, f32) {
let kf = (x * FRAC_2_PI).round();
let r = ((x - kf * PIO2_1) - kf * PIO2_2) - kf * PIO2_3;
let k = (kf as i32) & 3;
(k, r)
}
#[must_use]
pub fn sin(x: f32) -> f32 {
if !x.is_finite() {
return f32::NAN;
}
let (k, r) = reduce_pio2(x);
match k {
0 => sin_poly(r),
1 => cos_poly(r),
2 => -sin_poly(r),
_ => -cos_poly(r),
}
}
#[must_use]
pub fn cos(x: f32) -> f32 {
if !x.is_finite() {
return f32::NAN;
}
let (k, r) = reduce_pio2(x);
match k {
0 => cos_poly(r),
1 => -sin_poly(r),
2 => -cos_poly(r),
_ => sin_poly(r),
}
}
const LOG2E: f32 = core::f32::consts::LOG2_E;
const LN2_HI: f32 = 0.693_145_751_953_125;
const LN2_LO: f32 = 1.428_606_765_330_187e-6;
#[inline(always)]
fn pow2i(k: i32) -> f32 {
debug_assert!((-126..=127).contains(&k));
f32::from_bits(((k + 127) as u32) << 23)
}
#[must_use]
pub fn exp(x: f32) -> f32 {
if x.is_nan() {
return f32::NAN;
}
if x > 88.722_84 {
return f32::INFINITY;
}
if x < -103.972_08 {
return 0.0;
}
let kf = (x * LOG2E).round();
let k = kf as i32;
let r = (x - kf * LN2_HI) - kf * LN2_LO;
let p = (((((1.987_569_150_0e-4 * r + 1.398_199_950_7e-3) * r + 8.333_451_907_3e-3) * r
+ 4.166_579_589_4e-2)
* r
+ 1.666_666_545_9e-1)
* r
+ 0.5)
* r
* r
+ r
+ 1.0;
if k > 127 {
p * pow2i(127) * pow2i(k - 127)
} else if k < -126 {
p * pow2i(-126) * pow2i(k + 126)
} else {
p * pow2i(k)
}
}
const LG1: f32 = 0.666_666_626_93;
const LG2: f32 = 0.400_009_721_52;
const LG3: f32 = 0.284_987_866_88;
const LG4: f32 = 0.242_790_788_41;
#[must_use]
pub fn ln(x: f32) -> f32 {
if x.is_nan() || x < 0.0 {
return f32::NAN;
}
if x == 0.0 {
return f32::NEG_INFINITY;
}
if x.is_infinite() {
return f32::INFINITY;
}
let mut bits = x.to_bits();
let mut k: i32 = 0;
if bits < 0x0080_0000 {
let scaled = x * 33_554_432.0; bits = scaled.to_bits();
k -= 25;
}
k += ((bits >> 23) as i32) - 127;
bits = (bits & 0x007f_ffff) | 0x3f80_0000; if bits >= 0x3fb5_04f3 {
bits -= 0x0080_0000;
k += 1;
}
let m = f32::from_bits(bits);
let f = m - 1.0;
let s = f / (2.0 + f);
let z = s * s;
let w = z * z;
let t1 = w * (LG2 + w * LG4);
let t2 = z * (LG1 + w * LG3);
let r = t2 + t1;
let hfsq = 0.5 * f * f;
let kf = k as f32;
kf * LN2_HI - ((hfsq - (s * (hfsq + r) + kf * LN2_LO)) - f)
}
#[must_use]
pub fn powf(x: f32, y: f32) -> f32 {
if y == 0.0 {
return 1.0;
}
if x.is_nan() || y.is_nan() || x < 0.0 {
return f32::NAN;
}
if x == 0.0 {
return if y > 0.0 { 0.0 } else { f32::INFINITY };
}
exp64(f64::from(y) * ln64(f64::from(x))) as f32
}
#[must_use]
pub fn powi(x: f32, n: i32) -> f32 {
let mut base = if n < 0 { 1.0 / x } else { x };
let mut e = n.unsigned_abs();
let mut acc = 1.0f32;
while e > 0 {
if e & 1 == 1 {
acc *= base;
}
e >>= 1;
if e > 0 {
base *= base;
}
}
acc
}
#[must_use]
pub fn cbrt(x: f32) -> f32 {
if x == 0.0 || !x.is_finite() {
return x;
}
let ax = x.abs();
let (ax, post) = if ax.to_bits() < 0x0080_0000 {
(ax * 16_777_216.0, 1.0 / 256.0)
} else {
(ax, 1.0)
};
let mut y = f32::from_bits(ax.to_bits() / 3 + 0x2a51_37a0);
for _ in 0..3 {
let y2 = y * y;
y -= (y2 * y - ax) / (3.0 * y2);
}
let y = y * post;
if x < 0.0 {
-y
} else {
y
}
}
#[must_use]
pub fn hypot(x: f32, y: f32) -> f32 {
let ax = x.abs();
let ay = y.abs();
if ax.is_infinite() || ay.is_infinite() {
return f32::INFINITY;
}
if ax.is_nan() || ay.is_nan() {
return f32::NAN;
}
let m = if ax > ay { ax } else { ay };
if m == 0.0 {
return 0.0;
}
let (scale, unscale) = if m > 1.0e18 {
(
1.0 / 18_446_744_073_709_551_616.0,
18_446_744_073_709_551_616.0,
) } else if m < 1.0e-18 {
(
18_446_744_073_709_551_616.0,
1.0 / 18_446_744_073_709_551_616.0,
)
} else {
(1.0, 1.0)
};
let sx = ax * scale;
let sy = ay * scale;
(sx * sx + sy * sy).sqrt() * unscale
}
const LN2_HI64: f64 = 6.931_471_803_691_238_164_90e-01;
const LN2_LO64: f64 = 1.908_214_929_270_587_700_02e-10;
const INV_LN2_64: f64 = core::f64::consts::LOG2_E;
const P1: f64 = 1.666_666_666_666_660_190_37e-01;
const P2: f64 = -2.777_777_777_701_559_338_42e-03;
const P3: f64 = 6.613_756_321_437_934_361_17e-05;
const P4: f64 = -1.653_390_220_546_525_153_90e-06;
const P5: f64 = 4.138_136_797_057_238_460_39e-08;
#[inline(always)]
fn pow2i64(k: i32) -> f64 {
debug_assert!((-1022..=1023).contains(&k));
f64::from_bits(((k + 1023) as u64) << 52)
}
#[must_use]
pub fn exp64(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x > 709.782_712_893_384 {
return f64::INFINITY;
}
if x < -745.133_219_101_941_1 {
return 0.0;
}
let kf = (x * INV_LN2_64).round();
let k = kf as i32;
let hi = x - kf * LN2_HI64;
let lo = kf * LN2_LO64;
let r = hi - lo;
let t = r * r;
let c = r - t * (P1 + t * (P2 + t * (P3 + t * (P4 + t * P5))));
let y = 1.0 - ((lo - (r * c) / (2.0 - c)) - hi);
if k > 1023 {
y * pow2i64(1023) * pow2i64(k - 1023)
} else if k < -1022 {
y * pow2i64(-1022) * pow2i64(k + 1022)
} else {
y * pow2i64(k)
}
}
const LG1_64: f64 = 6.666_666_666_666_735_130e-01;
const LG2_64: f64 = 3.999_999_999_940_941_908e-01;
const LG3_64: f64 = 2.857_142_874_366_239_149e-01;
const LG4_64: f64 = 2.222_219_843_214_978_396e-01;
const LG5_64: f64 = 1.818_357_216_161_805_012e-01;
const LG6_64: f64 = 1.531_383_769_920_937_332e-01;
const LG7_64: f64 = 1.479_819_860_511_658_591e-01;
#[must_use]
pub fn ln64(x: f64) -> f64 {
if x.is_nan() || x < 0.0 {
return f64::NAN;
}
if x == 0.0 {
return f64::NEG_INFINITY;
}
if x.is_infinite() {
return f64::INFINITY;
}
let mut bits = x.to_bits();
let mut k: i32 = 0;
if bits < 0x0010_0000_0000_0000 {
let scaled = x * 18_014_398_509_481_984.0; bits = scaled.to_bits();
k -= 54;
}
k += ((bits >> 52) as i32) - 1023;
bits = (bits & 0x000f_ffff_ffff_ffff) | 0x3ff0_0000_0000_0000; if bits >= 0x3ff6_a09e_667f_3bcd {
bits -= 0x0010_0000_0000_0000;
k += 1;
}
let m = f64::from_bits(bits);
let f = m - 1.0;
let s = f / (2.0 + f);
let z = s * s;
let w = z * z;
let t1 = w * (LG2_64 + w * (LG4_64 + w * LG6_64));
let t2 = z * (LG1_64 + w * (LG3_64 + w * (LG5_64 + w * LG7_64)));
let r = t2 + t1;
let hfsq = 0.5 * f * f;
let kf = f64::from(k);
kf * LN2_HI64 - ((hfsq - (s * (hfsq + r) + kf * LN2_LO64)) - f)
}
#[inline(always)]
fn two_prod(a: f64, b: f64) -> (f64, f64) {
const SPLIT: f64 = 134_217_729.0; let p = a * b;
let ta = SPLIT * a;
let a_hi = ta - (ta - a);
let a_lo = a - a_hi;
let tb = SPLIT * b;
let b_hi = tb - (tb - b);
let b_lo = b - b_hi;
let e = ((a_hi * b_hi - p) + a_hi * b_lo + a_lo * b_hi) + a_lo * b_lo;
(p, e)
}
#[must_use]
pub fn powf64(x: f64, y: f64) -> f64 {
if y == 0.0 {
return 1.0;
}
if x.is_nan() || y.is_nan() || x < 0.0 {
return f64::NAN;
}
if x == 0.0 {
return if y > 0.0 { 0.0 } else { f64::INFINITY };
}
if x.is_infinite() {
return if y > 0.0 { f64::INFINITY } else { 0.0 };
}
let l_hi = ln64(x);
let l_lo = x * exp64(-l_hi) - 1.0;
let (t_hi, mut t_lo) = two_prod(y, l_hi);
t_lo += y * l_lo;
if t_hi > 709.782_712_893_384 {
return f64::INFINITY;
}
if t_hi < -745.133_219_101_941_1 {
return 0.0;
}
exp64(t_hi) * (1.0 + t_lo)
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)] mod tests {
use super::*;
fn ulp_diff32(a: f32, b: f32) -> u32 {
if a == b {
return 0;
}
let ia = a.to_bits() as i32;
let ib = b.to_bits() as i32;
(ia - ib).unsigned_abs()
}
fn ulp_diff64(a: f64, b: f64) -> u64 {
if a == b {
return 0;
}
let ia = a.to_bits() as i64;
let ib = b.to_bits() as i64;
(ia - ib).unsigned_abs()
}
fn r32(f: impl Fn(f64) -> f64) -> impl Fn(f32) -> f32 {
move |x| f(f64::from(x)) as f32
}
fn sweep32(lo: f32, hi: f32, n: u32, f: impl Fn(f32) -> f32, g: impl Fn(f32) -> f32) -> u32 {
let mut worst = 0;
for i in 0..=n {
let x = lo + (hi - lo) * (i as f32 / n as f32);
let a = f(x);
let b = g(x);
assert!(
a.is_finite() == b.is_finite(),
"finite mismatch at {x}: {a} vs {b}"
);
if a.is_finite() {
worst = worst.max(ulp_diff32(a, b));
}
}
worst
}
#[test]
fn sin_cos_within_2_ulp_of_correctly_rounded() {
assert!(sweep32(-100.0, 100.0, 400_000, sin, r32(f64::sin)) <= 2);
assert!(sweep32(-100.0, 100.0, 400_000, cos, r32(f64::cos)) <= 2);
assert_eq!(sin(0.0), 0.0);
assert_eq!(cos(0.0), 1.0);
assert!(sin(f32::INFINITY).is_nan());
}
#[test]
fn exp_within_2_ulp_of_correctly_rounded() {
assert!(sweep32(-87.0, 88.0, 400_000, exp, r32(f64::exp)) <= 2);
assert_eq!(exp(0.0), 1.0);
assert_eq!(exp(100.0), f32::INFINITY);
assert_eq!(exp(-200.0), 0.0);
assert!(exp(-100.0) > 0.0 && exp(-100.0) < f32::MIN_POSITIVE);
}
#[test]
fn ln_within_1_ulp_of_correctly_rounded() {
let mut worst = 0;
for i in 0..=200_000u32 {
let x = 10f32.powf(-30.0 + 60.0 * (i as f32 / 200_000.0));
worst = worst.max(ulp_diff32(ln(x), r32(f64::ln)(x)));
}
assert!(worst <= 1, "ln worst ulp {worst}");
assert_eq!(ln(1.0), 0.0);
assert_eq!(ln(0.0), f32::NEG_INFINITY);
assert!(ln(-1.0).is_nan());
assert!(ulp_diff32(ln(1.0e-40), r32(f64::ln)(1.0e-40)) <= 1);
}
#[test]
fn cbrt_within_1_ulp_of_correctly_rounded() {
let mut worst = 0;
for i in 0..=200_000u32 {
let x = 10f32.powf(-30.0 + 60.0 * (i as f32 / 200_000.0));
worst = worst.max(ulp_diff32(cbrt(x), r32(f64::cbrt)(x)));
worst = worst.max(ulp_diff32(cbrt(-x), r32(f64::cbrt)(-x)));
}
assert!(worst <= 1, "cbrt worst ulp {worst}");
assert_eq!(cbrt(0.0), 0.0);
assert_eq!(cbrt(8.0), 2.0);
assert_eq!(cbrt(-27.0), -3.0);
assert!(ulp_diff32(cbrt(1.0e-40), r32(f64::cbrt)(1.0e-40)) <= 1);
}
#[test]
fn hypot_matches_sqrt_formula_and_scales() {
assert_eq!(hypot(3.0, 4.0), 5.0);
assert_eq!(hypot(0.0, 0.0), 0.0);
assert_eq!(hypot(-3.0, 4.0), 5.0);
let mut worst = 0;
for i in 0..=100_000u32 {
let x = 1.0e-3 + 1.0e3 * (i as f32 / 100_000.0);
let y = 1.0e3 - x;
worst = worst.max(ulp_diff32(
hypot(x, y),
(f64::from(x).hypot(f64::from(y))) as f32,
));
}
assert!(worst <= 1, "hypot worst ulp {worst}");
assert!(hypot(1.0e30, 1.0e30).is_finite());
assert!(hypot(1.0e-30, 1.0e-30) > 0.0);
}
#[test]
fn powf_within_1_ulp_of_correctly_rounded() {
let mut worst = 0;
for i in 0..=400u32 {
let x = 10f32.powf(-3.0 + 6.0 * (i as f32 / 400.0));
for j in 0..=160u32 {
let y = -8.0 + 16.0 * (j as f32 / 160.0);
worst = worst.max(ulp_diff32(
powf(x, y),
(f64::from(x).powf(f64::from(y))) as f32,
));
}
}
assert!(worst <= 1, "powf worst ulp {worst}");
assert_eq!(powf(2.0, 0.0), 1.0);
assert_eq!(powf(0.0, 2.0), 0.0);
assert_eq!(powf(0.0, -1.0), f32::INFINITY);
assert!(powf(-2.0, 0.5).is_nan());
}
#[test]
fn powi_matches_repeated_multiplication() {
for n in 0..=12 {
let mut expect = 1.0f32;
for _ in 0..n {
expect *= 1.7;
}
assert!(ulp_diff32(powi(1.7, n), expect) <= 2, "n = {n}");
}
assert_eq!(powi(2.0, 10), 1024.0);
assert_eq!(powi(2.0, -2), 0.25);
assert_eq!(powi(5.0, 0), 1.0);
}
#[test]
fn exp64_ln64_within_2_ulp_of_libm() {
let mut worst_e = 0;
for i in 0..=400_000u32 {
let x = -700.0 + 1400.0 * (f64::from(i) / 400_000.0);
worst_e = worst_e.max(ulp_diff64(exp64(x), x.exp()));
}
assert!(worst_e <= 2, "exp64 worst ulp {worst_e}");
let mut worst_l = 0;
for i in 0..=400_000u32 {
let x = 10f64.powf(-300.0 + 600.0 * (f64::from(i) / 400_000.0));
worst_l = worst_l.max(ulp_diff64(ln64(x), x.ln()));
}
assert!(worst_l <= 2, "ln64 worst ulp {worst_l}");
assert_eq!(exp64(0.0), 1.0);
assert_eq!(ln64(1.0), 0.0);
assert_eq!(exp64(1000.0), f64::INFINITY);
assert_eq!(exp64(-800.0), 0.0);
assert_eq!(ln64(0.0), f64::NEG_INFINITY);
assert!(ulp_diff64(ln64(1.0e-310), (1.0e-310f64).ln()) <= 2);
}
#[test]
fn powf64_within_16_ulp_of_libm() {
let mut worst = 0;
for i in 0..=400u32 {
let x = 10f64.powf(-3.0 + 6.0 * (f64::from(i) / 400.0));
for j in 0..=160u32 {
let y = -8.0 + 16.0 * (f64::from(j) / 160.0);
worst = worst.max(ulp_diff64(powf64(x, y), x.powf(y)));
}
}
assert!(worst <= 16, "powf64 worst ulp {worst}");
}
#[test]
fn bit_exact_pins() {
assert_eq!(sin(1.0f32).to_bits(), 0x3f57_6aa5);
assert_eq!(cos(1.0f32).to_bits(), 0x3f0a_5140);
assert_eq!(exp(1.0f32).to_bits(), 0x402d_f854);
assert_eq!(ln(10.0f32).to_bits(), 0x4013_5d8e);
assert_eq!(cbrt(10.0f32).to_bits(), 0x4009_e242);
assert_eq!(powf(3.0f32, 2.5).to_bits(), 0x4179_6a52);
assert_eq!(hypot(1.5f32, 2.5).to_bits(), 0x403a_9728);
assert_eq!(exp64(1.0f64).to_bits(), 0x4005_bf0a_8b14_576a);
assert_eq!(ln64(10.0f64).to_bits(), 0x4002_6bb1_bbb5_5516);
assert_eq!(powf64(3.0f64, 2.5).to_bits(), 0x402f_2d4a_4563_563d);
}
}