mod span;
pub use span::Span;
pub trait FloatExt {
fn floor(self) -> f64;
fn trunc(self) -> f64;
fn fract(self) -> f64;
fn round(self) -> f64;
fn powi(self, n: i32) -> f64;
}
impl FloatExt for f64 {
#[inline]
fn trunc(self) -> f64 {
if !self.is_finite() || self.abs() >= 9_223_372_036_854_775_808.0 {
self
} else {
self as i64 as f64
}
}
#[inline]
fn floor(self) -> f64 {
let t = FloatExt::trunc(self);
if t > self { t - 1.0 } else { t }
}
#[inline]
fn fract(self) -> f64 {
self - FloatExt::trunc(self)
}
#[inline]
fn round(self) -> f64 {
if !self.is_finite() {
return self;
}
let a = FloatExt::floor(self.abs() + 0.5);
if self < 0.0 { -a } else { a }
}
#[inline]
fn powi(self, n: i32) -> f64 {
let mut base = if n < 0 { 1.0 / self } else { self };
let mut exp = n.unsigned_abs();
let mut acc = 1.0;
while exp > 0 {
if exp & 1 == 1 {
acc *= base;
}
base *= base;
exp >>= 1;
}
acc
}
}
#[must_use]
pub fn sin_deg(degrees: f64) -> f64 {
if !degrees.is_finite() {
return f64::NAN;
}
let d = degrees - 360.0 * FloatExt::floor(degrees / 360.0);
let (sign, d) = if d > 180.0 {
(-1.0, d - 180.0)
} else {
(1.0, d)
};
let d = if d > 90.0 { 180.0 - d } else { d };
sign * sin_series(d * (core::f64::consts::PI / 180.0))
}
#[must_use]
pub fn cos_deg(degrees: f64) -> f64 {
sin_deg(degrees + 90.0)
}
fn sin_series(x: f64) -> f64 {
let u = x * x;
let mut acc = 1.0 / 355_687_428_096_000.0; for c in [
-1.0 / 1_307_674_368_000.0, 1.0 / 6_227_020_800.0, -1.0 / 39_916_800.0, 1.0 / 362_880.0, -1.0 / 5_040.0, 1.0 / 120.0, -1.0 / 6.0, 1.0, ] {
acc = acc * u + c;
}
x * acc
}
#[cfg(feature = "std")]
#[must_use]
pub fn js_round(x: f64) -> f64 {
if !x.is_finite() || x == 0.0 {
return x;
}
if x > 0.0 {
if x < 0.5 { 0.0 } else { (x + 0.5).floor() }
} else if x >= -0.5 {
-0.0 } else {
(x + 0.5).floor()
}
}
#[cfg(all(test, feature = "std"))]
mod float_ext_tests {
use super::FloatExt;
#[test]
fn float_ext_matches_std() {
for &x in &[
0.0,
-0.0,
0.5,
-0.5,
2.5,
-2.5,
3.7,
-3.7,
1e18,
-1e18,
0.4999999999999999,
123.456,
-123.456,
9.75,
-9.75,
] {
assert_eq!(FloatExt::trunc(x), x.trunc(), "trunc {x}");
assert_eq!(FloatExt::floor(x), x.floor(), "floor {x}");
assert_eq!(FloatExt::fract(x), x.fract(), "fract {x}");
assert_eq!(FloatExt::round(x), x.round(), "round {x}");
}
for &(b, n) in &[(2.0, 10), (3.0, 0), (5.0, 3), (2.0, -2), (1.5, 4)] {
assert_eq!(FloatExt::powi(b, n), b.powi(n), "powi {b}^{n}");
}
}
}
#[cfg(test)]
mod trig_tests {
use super::{cos_deg, sin_deg};
#[test]
fn sin_cos_deg_match_std() {
let mut worst = 0.0f64;
let mut d = -1440.0;
while d <= 1440.0 {
let r = d * core::f64::consts::PI / 180.0;
worst = worst.max((sin_deg(d) - r.sin()).abs());
worst = worst.max((cos_deg(d) - r.cos()).abs());
d += 0.25;
}
assert!(worst < 1e-12, "worst |Δ| over ±1440° was {worst}");
for (deg, want) in [(0.0, 0.0), (90.0, 1.0), (180.0, 0.0), (270.0, -1.0)] {
assert!((sin_deg(deg) - want).abs() < 1e-12, "sin({deg})");
}
for (deg, want) in [(0.0, 1.0), (90.0, 0.0), (180.0, -1.0), (270.0, 0.0)] {
assert!((cos_deg(deg) - want).abs() < 1e-12, "cos({deg})");
}
for d in [36_000.769_537_44, 1.0e6, 9.7e7, -4.2e7] {
let expected = (d % 360.0) * core::f64::consts::PI / 180.0;
assert!(
(sin_deg(d) - expected.sin()).abs() < 1e-9,
"sin({d}) reduction"
);
}
assert!(sin_deg(f64::NAN).is_nan());
assert!(sin_deg(f64::INFINITY).is_nan());
}
}