#[expect(clippy::excessive_precision)]
const IVLN2HI: f64 = 1.44269504072144627571e+00;
#[expect(clippy::excessive_precision)]
const IVLN2LO: f64 = 1.67517131648865118353e-10;
#[expect(clippy::excessive_precision)]
const LG1: f64 = 6.666666666666735130e-01;
#[expect(clippy::excessive_precision)]
const LG2: f64 = 3.999999999940941908e-01;
#[expect(clippy::excessive_precision)]
const LG3: f64 = 2.857142874366239149e-01;
#[expect(clippy::excessive_precision)]
const LG4: f64 = 2.222219843214978396e-01;
#[expect(clippy::excessive_precision)]
const LG5: f64 = 1.818357216161805012e-01;
#[expect(clippy::excessive_precision)]
const LG6: f64 = 1.531383769920937332e-01;
#[expect(clippy::excessive_precision)]
const LG7: f64 = 1.479819860511658591e-01;
pub(crate) fn log2(mut x: f64) -> f64 {
let x1p54 = f64::from_bits(0x4350000000000000);
let mut ui = x.to_bits();
let mut k = 0i32;
#[expect(clippy::as_conversions)] let mut hx = (ui >> 32) as u32;
if hx < 0x00100000 || (hx >> 31) > 0 {
if ui << 1 == 0 {
return -1. / (x * x);
}
if (hx >> 31) > 0 {
#[expect(clippy::eq_op)]
return (x - x) / 0.0;
}
k -= 54;
x *= x1p54;
ui = x.to_bits();
#[expect(clippy::as_conversions)] let t = (ui >> 32) as u32;
hx = t;
} else if hx >= 0x7ff00000 {
return x;
} else if hx == 0x3ff00000 && ui << 32 == 0 {
return 0.;
}
hx += 0x3ff00000 - 0x3fe6a09e;
#[expect(clippy::as_conversions, clippy::cast_possible_wrap)] let k_mod = (hx >> 20) as i32 - 0x3ff;
k += k_mod;
hx = (hx & 0x000fffff) + 0x3fe6a09e;
ui = (u64::from(hx) << 32) | (ui & 0xffffffff);
x = f64::from_bits(ui);
let f = x - 1.0;
let hfsq = 0.5 * f * f;
let s = f / (2.0 + f);
let z = s * s;
let w = z * z;
let t1 = w * (LG2 + w * (LG4 + w * LG6));
let t2 = z * (LG1 + w * (LG3 + w * (LG5 + w * LG7)));
let r = t2 + t1;
let mut hi = f - hfsq;
ui = hi.to_bits();
ui &= u64::MAX << 32;
hi = f64::from_bits(ui);
let lo = f - hi - hfsq + s * (hfsq + r);
let mut val_hi = hi * IVLN2HI;
let mut val_lo = (lo + hi) * IVLN2LO + lo * IVLN2HI;
let y: f64 = k.into();
let w = y + val_hi;
val_lo += (y - w) + val_hi;
val_hi = w;
val_lo + val_hi
}
type F64Int = u64;
const F64_BITS: u32 = 64;
const F64_SIG_BITS: u32 = 52;
const F64_SIGN_MASK: F64Int = 1 << (F64_BITS - 1);
const F64_SIG_MASK: F64Int = (1 << F64_SIG_BITS) - 1;
const F64_EXP_BITS: u32 = F64_BITS - F64_SIG_BITS - 1;
const F64_EXP_SAT: u32 = (1 << F64_EXP_BITS) - 1;
const F64_EXP_BIAS: u32 = F64_EXP_SAT >> 1;
#[expect(clippy::as_conversions)] const fn f64_from_parts(negative: bool, exponent: u32, significand: F64Int) -> f64 {
let sign: F64Int = if negative { 1 } else { 0 };
f64::from_bits(
(sign << (F64_BITS - 1))
| (((exponent & F64_EXP_SAT) as F64Int) << F64_SIG_BITS)
| (significand & F64_SIG_MASK),
)
}
#[expect(clippy::as_conversions)] const fn f64_ex(x: f64) -> u32 {
(x.to_bits() >> F64_SIG_BITS) as u32 & F64_EXP_SAT
}
#[expect(clippy::as_conversions, clippy::cast_possible_wrap)] const fn f64_exp_unbiased(x: f64) -> i32 {
f64_ex(x) as i32 - (F64_EXP_BIAS as i32)
}
const fn trunc(x: f64) -> f64 {
let xi: F64Int = x.to_bits();
let e = f64_exp_unbiased(x);
#[expect(clippy::as_conversions, clippy::cast_possible_wrap)] if e >= F64_SIG_BITS as i32 {
return x;
}
#[expect(clippy::as_conversions, clippy::cast_sign_loss)] let clear_mask = if e < 0 {
!F64_SIGN_MASK
} else {
F64_SIG_MASK >> (e as u32)
};
let cleared = xi & clear_mask;
f64::from_bits(xi ^ cleared)
}
pub(crate) const fn round(x: f64) -> f64 {
let f0p5 = f64_from_parts(false, F64_EXP_BIAS - 1, 0); let f0p25 = f64_from_parts(false, F64_EXP_BIAS - 2, 0);
trunc(x + (f0p5 - f0p25 * f64::EPSILON).copysign(x))
}
pub(crate) const fn floor(x: f64) -> f64 {
let zero = 0;
let mut ix = x.to_bits();
let e = f64_exp_unbiased(x);
#[expect(clippy::as_conversions, clippy::cast_possible_wrap)] if e >= F64_SIG_BITS as i32 {
return x;
}
if e >= 0 {
#[expect(clippy::as_conversions, clippy::cast_sign_loss)] let m = F64_SIG_MASK >> (e as u32);
if ix & m == zero {
return x;
}
if x.is_sign_negative() {
ix += m;
}
ix &= !m;
f64::from_bits(ix)
} else {
if (ix & !F64_SIGN_MASK) == 0 {
return x;
}
if x.is_sign_positive() {
0.
} else {
-1.
}
}
}
#[cfg(test)]
mod tests {
use libm::{floor as libm_floor, log2 as libm_log2, round as libm_round};
use proptest::prelude::*;
use super::{floor, log2, round};
proptest! {
#[test]
fn fuzzy_round(x in proptest::num::f64::ANY) {
let test = round(x);
let reference = libm_round(x);
assert_eq!(test.to_bits(), reference.to_bits());
}
#[test]
fn fuzzy_floor(x in proptest::num::f64::ANY) {
let test = floor(x);
let reference = libm_floor(x);
assert_eq!(test.to_bits(), reference.to_bits());
}
#[test]
fn fuzzy_log2(x in proptest::num::f64::ANY) {
let test = log2(x);
let reference = libm_log2(x);
assert_eq!(test.to_bits(), reference.to_bits());
}
}
}