const NO_FRACTION: f64 = 4_503_599_627_370_496.0;
const _: () = assert!(NO_FRACTION < i64::MAX as f64);
pub(crate) fn floor(v: f64) -> f64 {
if !v.is_finite() || v.abs() >= NO_FRACTION {
return v;
}
let t = trunc(v);
if t > v { t - 1.0 } else { t }
}
pub(crate) fn round(v: f64) -> f64 {
if !v.is_finite() || v.abs() >= NO_FRACTION {
return v;
}
let t = trunc(v);
let frac = v - t;
if frac >= 0.5 {
t + 1.0
} else if frac <= -0.5 {
t - 1.0
} else {
t
}
}
fn trunc(v: f64) -> f64 {
debug_assert!(v.is_finite() && v.abs() < NO_FRACTION);
#[allow(clippy::cast_possible_truncation)]
let t = v as i64 as f64;
if t == 0.0 { t.copysign(v) } else { t }
}
const H: f64 = 0.866_025_403_784_438_6;
pub(crate) const FLAT_CORNERS: [(f64, f64); 6] = [
(1.0, 0.0),
(0.5, H),
(-0.5, H),
(-1.0, 0.0),
(-0.5, -H),
(0.5, -H),
];
pub(crate) const POINTY_CORNERS: [(f64, f64); 6] = [
(H, 0.5),
(0.0, 1.0),
(-H, 0.5),
(-H, -0.5),
(0.0, -1.0),
(H, -0.5),
];
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
fn corners() -> Vec<f64> {
let mut v = vec![
0.0,
-0.0,
0.5,
-0.5,
1.5,
-1.5,
2.5,
-2.5,
0.499_999_999_999_999_94,
-0.499_999_999_999_999_94,
1.0 - f64::EPSILON,
NO_FRACTION,
-NO_FRACTION,
NO_FRACTION - 1.0,
-(NO_FRACTION - 1.0),
NO_FRACTION + 2.0,
f64::MAX,
f64::MIN,
f64::MIN_POSITIVE,
-f64::MIN_POSITIVE,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
i64::MAX as f64,
i64::MIN as f64,
f64::from((1i32 << 30) - 1),
f64::from(-((1i32 << 30) - 1)),
];
for i in -600..600 {
let x = f64::from(i) / 7.0;
v.push(x);
v.push(x * 1e9);
v.push(x * 1e-9);
}
v
}
fn alike(a: f64, b: f64) -> bool {
(a.is_nan() && b.is_nan()) || a.to_bits() == b.to_bits()
}
#[test]
fn floor_agrees_with_std_on_every_value_that_has_ever_caused_trouble() {
for v in corners() {
assert!(
alike(floor(v), v.floor()),
"floor({v:?}): got {:?}, std says {:?}",
floor(v),
v.floor(),
);
}
}
#[test]
fn round_agrees_with_std_on_every_value_that_has_ever_caused_trouble() {
for v in corners() {
assert!(
alike(round(v), v.round()),
"round({v:?}): got {:?}, std says {:?}",
round(v),
v.round(),
);
}
}
#[test]
fn both_agree_with_std_across_a_wide_sweep() {
let mut s = 0x2545_F491_4F6C_DD1Du64;
for _ in 0..2_000_000 {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
#[allow(clippy::cast_precision_loss)]
let mag = f64::from((s >> 40) as u32) / 1024.0;
let v = if s & 1 == 0 { mag } else { -mag };
assert!(alike(floor(v), v.floor()), "floor({v:?})");
assert!(alike(round(v), v.round()), "round({v:?})");
}
}
#[test]
fn the_corner_tables_are_the_angles_they_replace() {
use core::f64::consts::PI;
for (i, &(c, s)) in FLAT_CORNERS.iter().enumerate() {
let a = (i as f64).mul_add(PI / 3.0, 0.0);
assert!(
(c - a.cos()).abs() < 1e-15,
"flat {i} cos: {c} vs {}",
a.cos()
);
assert!(
(s - a.sin()).abs() < 1e-15,
"flat {i} sin: {s} vs {}",
a.sin()
);
}
for (i, &(c, s)) in POINTY_CORNERS.iter().enumerate() {
let a = (i as f64).mul_add(PI / 3.0, PI / 6.0);
assert!(
(c - a.cos()).abs() < 1e-15,
"pointy {i} cos: {c} vs {}",
a.cos()
);
assert!(
(s - a.sin()).abs() < 1e-15,
"pointy {i} sin: {s} vs {}",
a.sin()
);
}
}
#[test]
fn every_corner_is_on_the_unit_circle() {
for &(c, s) in FLAT_CORNERS.iter().chain(POINTY_CORNERS.iter()) {
assert!((c * c + s * s - 1.0).abs() < 1e-15, "({c}, {s})");
}
}
}