pub(crate) trait Ieee: Copy {
const FRAC: u32;
const BIAS: i32;
const EXP_MAX: u64;
fn bits(self) -> u64;
fn from_raw(bits: u64) -> Self;
}
impl Ieee for f64 {
const FRAC: u32 = 52;
const BIAS: i32 = 1023;
const EXP_MAX: u64 = 0x7ff;
fn bits(self) -> u64 {
self.to_bits()
}
fn from_raw(bits: u64) -> Self {
f64::from_bits(bits)
}
}
impl Ieee for f32 {
const FRAC: u32 = 23;
const BIAS: i32 = 127;
const EXP_MAX: u64 = 0xff;
fn bits(self) -> u64 {
u64::from(self.to_bits())
}
fn from_raw(bits: u64) -> Self {
f32::from_bits(bits as u32)
}
}
const fn sign_bit<F: Ieee>() -> u64 {
1u64 << (F::FRAC + exp_width::<F>())
}
const fn exp_width<F: Ieee>() -> u32 {
if F::FRAC == 52 { 11 } else { 8 }
}
fn exponent<F: Ieee>(bits: u64) -> i32 {
((bits >> F::FRAC) & F::EXP_MAX) as i32 - F::BIAS
}
pub(crate) fn trunc<F: Ieee>(x: F) -> F {
let bits = x.bits();
let e = exponent::<F>(bits);
if e >= F::FRAC as i32 {
return x;
}
if e < 0 {
return F::from_raw(bits & sign_bit::<F>());
}
let mask = (1u64 << (F::FRAC as i32 - e)) - 1;
F::from_raw(bits & !mask)
}
macro_rules! rounding {
($t:ty, $floor:ident, $ceil:ident, $round:ident, $even:ident) => {
pub(crate) fn $floor(x: $t) -> $t {
let t = trunc(x);
if x < t { t - 1.0 } else { t }
}
pub(crate) fn $ceil(x: $t) -> $t {
let t = trunc(x);
if x > t { t + 1.0 } else { t }
}
pub(crate) fn $round(x: $t) -> $t {
let t = trunc(x);
let d = x - t;
if d >= 0.5 {
t + 1.0
} else if d <= -0.5 {
t - 1.0
} else {
t
}
}
pub(crate) fn $even(x: $t) -> $t {
let t = trunc(x);
let d = x - t;
let away = if !(-0.5..=0.5).contains(&d) {
true
} else if d == 0.5 || d == -0.5 {
(t as i64) & 1 == 1
} else {
false
};
if !away {
t
} else if d > 0.0 {
t + 1.0
} else {
t - 1.0
}
}
};
}
rounding!(f64, floor64, ceil64, round64, round_even64);
rounding!(f32, floor32, ceil32, round32, round_even32);
fn isqrt(n: u128) -> u128 {
let mut rem = n;
let mut root = 0u128;
let mut bit = 1u128 << 126;
while bit > n {
bit >>= 2;
}
while bit != 0 {
if rem >= root + bit {
rem -= root + bit;
root = (root >> 1) + bit;
} else {
root >>= 1;
}
bit >>= 2;
}
root
}
pub(crate) fn sqrt<F: Ieee>(x: F) -> F {
let bits = x.bits();
let sign = bits & sign_bit::<F>();
let mag = bits & !sign_bit::<F>();
let exp_field = (mag >> F::FRAC) & F::EXP_MAX;
if mag == 0 {
return x;
}
if exp_field == F::EXP_MAX {
return if sign != 0 && mag == F::EXP_MAX << F::FRAC {
F::from_raw((F::EXP_MAX << F::FRAC) | (1 << (F::FRAC - 1)))
} else {
x
};
}
if sign != 0 {
return F::from_raw((F::EXP_MAX << F::FRAC) | (1 << (F::FRAC - 1)));
}
let frac_mask = (1u64 << F::FRAC) - 1;
let (mut m, mut e) = if exp_field == 0 {
let f = mag & frac_mask;
let shift = f.leading_zeros() - (63 - F::FRAC);
(f << shift, 1 - F::BIAS - F::FRAC as i32 - shift as i32)
} else {
(
(mag & frac_mask) | (1u64 << F::FRAC),
exp_field as i32 - F::BIAS - F::FRAC as i32,
)
};
let low = F::FRAC + (F::FRAC & 1); let base = low - F::FRAC; let s = if (e - base as i32) & 1 == 0 {
base
} else {
base + 1
};
m <<= s;
e -= s as i32;
const GUARD: u32 = 5;
let big_s = 2 * (F::FRAC + 1 + GUARD) - low - 2;
let n = u128::from(m) << big_s;
let r = isqrt(n);
let sticky = r * r != n;
let mut mant = (r >> GUARD) as u64;
let rest = (r & ((1u128 << GUARD) - 1)) as u64;
let half = 1u64 << (GUARD - 1);
if rest > half || (rest == half && (sticky || mant & 1 == 1)) {
mant += 1;
}
let mut q = GUARD as i32 + (e - big_s as i32) / 2;
if mant >> (F::FRAC + 1) != 0 {
mant >>= 1;
q += 1;
}
let biased = (q + F::FRAC as i32 + F::BIAS) as u64;
F::from_raw((biased << F::FRAC) | (mant & frac_mask))
}
pub(crate) fn ieee_remainder(x: f64, y: f64) -> f64 {
if y == 0.0 || x.is_infinite() || x.is_nan() || y.is_nan() {
return f64::NAN;
}
if y.is_infinite() {
return x;
}
let abs = |v: f64| f64::from_bits(v.to_bits() & !(1u64 << 63));
let negative = x.is_sign_negative();
let p = abs(y);
let mut r = if p <= f64::MAX / 2.0 { x % (p + p) } else { x };
r = abs(r);
if p < 2.0 * f64::MIN_POSITIVE {
if r + r > p {
r -= p;
if r + r >= p {
r -= p;
}
}
} else {
let half = 0.5 * p;
if r > half {
r -= p;
if r >= half {
r -= p;
}
}
}
if negative { -r } else { r }
}
pub(crate) fn fmin(a: f64, b: f64) -> f64 {
if a.is_nan() || b.is_nan() {
f64::NAN
} else if a < b {
a
} else if b < a {
b
} else if a.is_sign_negative() {
a
} else {
b
}
}
pub(crate) fn fmax(a: f64, b: f64) -> f64 {
if a.is_nan() || b.is_nan() {
f64::NAN
} else if a > b || (a == b && b.is_sign_negative()) {
a
} else {
b
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn same64(a: f64, b: f64) -> bool {
a.to_bits() == b.to_bits() || (a.is_nan() && b.is_nan())
}
fn same32(a: f32, b: f32) -> bool {
a.to_bits() == b.to_bits() || (a.is_nan() && b.is_nan())
}
const EDGES64: [f64; 22] = [
0.0,
-0.0,
0.5,
-0.5,
1.5,
-1.5,
2.5,
-2.5,
0.49999999999999994,
-0.49999999999999994,
4503599627370495.5,
-4503599627370495.5,
4503599627370496.0,
9007199254740993.0,
f64::MAX,
f64::MIN,
f64::MIN_POSITIVE,
5e-324,
f64::INFINITY,
f64::NEG_INFINITY,
1e300,
-3.75,
];
#[test]
fn test_rounding_matches_std_on_edges() {
for &x in &EDGES64 {
assert!(same64(trunc(x), x.trunc()), "trunc {x}");
assert!(same64(floor64(x), x.floor()), "floor {x}");
assert!(same64(ceil64(x), x.ceil()), "ceil {x}");
assert!(same64(round64(x), x.round()), "round {x}");
assert!(same64(round_even64(x), x.round_ties_even()), "even {x}");
let y = x as f32;
assert!(same32(trunc(y), y.trunc()), "trunc32 {y}");
assert!(same32(floor32(y), y.floor()), "floor32 {y}");
assert!(same32(ceil32(y), y.ceil()), "ceil32 {y}");
assert!(same32(round32(y), y.round()), "round32 {y}");
assert!(same32(round_even32(y), y.round_ties_even()), "even32 {y}");
}
}
#[test]
fn test_sqrt_matches_std_on_edges() {
for &x in &EDGES64 {
assert!(same64(sqrt(x), x.sqrt()), "sqrt {x}");
let y = x as f32;
assert!(same32(sqrt(y), y.sqrt()), "sqrt32 {y}");
}
for k in 0..2000u64 {
let x = k as f64;
assert!(same64(sqrt(x), x.sqrt()), "sqrt {x}");
let y = k as f32;
assert!(same32(sqrt(y), y.sqrt()), "sqrt32 {y}");
}
}
#[test]
fn test_nan_inputs_are_total() {
assert!(trunc(f64::NAN).is_nan());
assert!(sqrt(f64::NAN).is_nan());
assert!(sqrt(-1.0f64).is_nan());
assert!(sqrt(f32::NEG_INFINITY).is_nan());
assert!(round_even64(f64::NAN).is_nan());
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(4096))]
#[test]
fn prop_f64_helpers_match_std(bits in any::<u64>()) {
let x = f64::from_bits(bits);
prop_assert!(same64(trunc(x), x.trunc()));
prop_assert!(same64(floor64(x), x.floor()));
prop_assert!(same64(ceil64(x), x.ceil()));
prop_assert!(same64(round64(x), x.round()));
prop_assert!(same64(round_even64(x), x.round_ties_even()));
prop_assert!(same64(sqrt(x), x.sqrt()), "{x:e}");
}
#[test]
fn prop_f32_helpers_match_std(bits in any::<u32>()) {
let x = f32::from_bits(bits);
prop_assert!(same32(trunc(x), x.trunc()));
prop_assert!(same32(floor32(x), x.floor()));
prop_assert!(same32(ceil32(x), x.ceil()));
prop_assert!(same32(round32(x), x.round()));
prop_assert!(same32(round_even32(x), x.round_ties_even()));
prop_assert!(same32(sqrt(x), x.sqrt()), "{x:e}");
}
#[test]
fn prop_small_magnitudes_round_like_std(x in -1.0e6f64..1.0e6) {
prop_assert!(same64(round64(x), x.round()));
prop_assert!(same64(round_even64(x), x.round_ties_even()));
prop_assert!(same64(floor64(x), x.floor()));
prop_assert!(same64(ceil64(x), x.ceil()));
}
}
}