use std::f32::consts::{FRAC_PI_2, PI};
const TAU: f32 = 2.0 * PI;
const TAU_HI: f32 = 6.28125;
const TAU_LO: f32 = 0.001_935_307_2;
const LN2: f32 = std::f32::consts::LN_2;
#[allow(clippy::excessive_precision)]
const LN2_HI: f32 = 0.693_115_234_375;
const LN2_LO: f32 = 3.194_618_3e-5;
pub fn sin(x: f32) -> f32 {
if !x.is_finite() {
return 0.0;
}
let s = sin_abs(x.abs());
if x.is_sign_negative() {
-s
} else {
s
}
}
pub fn cos(x: f32) -> f32 {
if !x.is_finite() {
return 0.0;
}
let r = reduce_tau(x.abs()).abs();
let (sign, t) = if r > FRAC_PI_2 {
(-1.0, PI - r)
} else {
(1.0, r)
};
let t2 = t * t;
let p = 1.0
+ t2 * (-0.5
+ t2 * (1.0 / 24.0
+ t2 * (-1.0 / 720.0
+ t2 * (1.0 / 40_320.0
+ t2 * (-1.0 / 3_628_800.0
+ t2 * (1.0 / 479_001_600.0 + t2 * (-1.0 / 87_178_291_200.0)))))));
(sign * p).clamp(-1.0, 1.0)
}
pub fn exp(x: f32) -> f32 {
if x.is_nan() {
return 0.0;
}
if x == f32::INFINITY {
return f32::MAX;
}
if x == f32::NEG_INFINITY {
return 0.0;
}
let x = x.clamp(-87.0, 88.0);
let k = (x / LN2 + 0.5).floor();
let r = x - k * LN2_HI - k * LN2_LO;
let p = 1.0
+ r * (1.0
+ r * (0.5
+ r * (1.0 / 6.0
+ r * (1.0 / 24.0
+ r * (1.0 / 120.0 + r * (1.0 / 720.0 + r * (1.0 / 5040.0)))))));
let biased = k as i32 + 127;
if biased <= 0 {
return 0.0;
}
if biased >= 255 {
return f32::MAX;
}
p * f32::from_bits((biased as u32) << 23)
}
fn reduce_tau(x: f32) -> f32 {
let k = (x / TAU + 0.5).floor();
(x - k * TAU_HI) - k * TAU_LO
}
fn sin_abs(x: f32) -> f32 {
sin_reduced(reduce_tau(x))
}
fn sin_reduced(r: f32) -> f32 {
let folded = if r > FRAC_PI_2 {
PI - r
} else if r < -FRAC_PI_2 {
-PI - r
} else {
r
};
let r2 = folded * folded;
let p = 1.0
+ r2 * (-1.0 / 6.0
+ r2 * (1.0 / 120.0
+ r2 * (-1.0 / 5040.0
+ r2 * (1.0 / 362_880.0
+ r2 * (-1.0 / 39_916_800.0 + r2 * (1.0 / 6_227_020_800.0))))));
(folded * p).clamp(-1.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_gives_exact_sin_cos_exp() {
assert_eq!(sin(0.0), 0.0);
assert_eq!(cos(0.0), 1.0);
assert_eq!(exp(0.0), 1.0);
}
fn sweep_points() -> impl Iterator<Item = f32> {
let wide = (0..)
.map(|i| -1000.0 + 0.0137 * i as f64)
.take_while(|&x| x <= 1000.0);
let narrow = (0..)
.map(|i| -50.0 + 0.00123 * i as f64)
.take_while(|&x| x <= 50.0);
wide.chain(narrow).map(|x| x as f32)
}
#[test]
fn sin_matches_f64_within_1e6_over_the_sweeps() {
for x in sweep_points() {
let got = sin(x);
let want = (x as f64).sin();
assert!(
(got as f64 - want).abs() <= 1e-6,
"sin({x}) = {got}, want {want}"
);
}
}
#[test]
fn cos_matches_f64_within_1e6_over_the_sweeps() {
for x in sweep_points() {
let got = cos(x);
let want = (x as f64).cos();
assert!(
(got as f64 - want).abs() <= 1e-6,
"cos({x}) = {got}, want {want}"
);
}
}
#[test]
fn exp_has_relative_error_below_2e6_on_minus_20_to_20() {
let mut x = -20.0f64;
while x <= 20.0 {
let xf = x as f32;
let got = exp(xf) as f64;
let want = (xf as f64).exp();
let rel = ((got - want) / want).abs();
assert!(rel <= 2e-6, "exp({xf}) = {got}, want {want}, rel {rel}");
x += 0.00123;
}
}
#[test]
fn exp_handles_non_finite_and_extreme_input() {
assert_eq!(exp(f32::NAN), 0.0);
assert_eq!(exp(f32::INFINITY), f32::MAX);
assert_eq!(exp(f32::NEG_INFINITY), 0.0);
assert!(exp(1e7).is_finite() && exp(-1e7) >= 0.0);
}
#[test]
fn golden_bits_do_not_change_across_machines() {
let cases: [(f32, u32, u32, u32); 12] = [
(0.1, 0x3dcc_7577, 0x3f7e_b898, 0x3f8d_763e),
(1.0, 0x3f57_6aa4, 0x3f0a_5140, 0x402d_f854),
(2.5, 0x3f19_3579, 0xbf4d_17bf, 0x4142_eb7f),
(100.0, 0xbf01_a12e, 0x3f5c_c0ee, 0x7ef8_82b7),
(-37.7, 0xba69_065e, 0x3f7f_fff9, 0x2443_6a30),
(12345.678, 0xbf34_4b08, 0x3f35_be20, 0x7ef8_82b7),
(0.5, 0x3ef5_7744, 0x3f60_a940, 0x3fd3_094c),
(-3.0, 0xbe10_81c9, 0xbf7d_7026, 0x3d4b_ed86),
(7.25, 0x3f52_b56e, 0x3f11_637a, 0x44b0_035b),
(20.0, 0x3f69_b6c7, 0x3ed0_f024, 0x4de7_5844),
(-0.75, 0xbf2e_7fe1, 0x3f3b_4ff6, 0x3ef1_da07),
(999.5, 0x3ee9_7d93, 0x3f63_d480, 0x7ef8_82b7),
];
for (x, s, c, e) in cases {
assert_eq!(sin(x).to_bits(), s, "sin({x})");
assert_eq!(cos(x).to_bits(), c, "cos({x})");
assert_eq!(exp(x).to_bits(), e, "exp({x})");
}
}
#[test]
fn sin_is_odd_bit_for_bit() {
for x in sweep_points().step_by(20).take(1000) {
assert_eq!(sin(-x).to_bits(), (-sin(x)).to_bits(), "x = {x}");
}
}
#[test]
fn non_finite_input_gives_finite_output_and_huge_input_stays_in_range() {
for x in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
assert_eq!(sin(x), 0.0);
assert_eq!(cos(x), 0.0);
}
for x in [1e7f32, -1e7, 3.3e6, -9.9e6, 12345.678] {
let s = sin(x);
let c = cos(x);
assert!(s.is_finite() && (-1.0..=1.0).contains(&s), "sin({x}) = {s}");
assert!(c.is_finite() && (-1.0..=1.0).contains(&c), "cos({x}) = {c}");
}
}
}