const TWO_POW_32: f64 = 4_294_967_296.0;
const HASH_SPACE_32: f64 = TWO_POW_32;
pub(super) fn alpha_m(m: usize) -> f64 {
match m {
16 => 0.673,
32 => 0.697,
64 => 0.709,
_ => 0.7213 / (1.0 + 1.079 / count_to_f64(m)),
}
}
pub(super) fn estimate_from_registers(registers: &[u8]) -> f64 {
let m = count_to_f64(registers.len());
if m <= 0.0 {
return 0.0;
}
let mut sum = 0.0_f64;
let mut zeros = 0_usize;
for &r in registers {
sum += 2.0_f64.powi(-i32::from(r));
if r == 0 {
zeros += 1;
}
}
let raw = alpha_m(registers.len()) * m * m / sum;
if raw <= 2.5 * m && zeros > 0 {
let z = count_to_f64(zeros);
return m * (m / z).ln();
}
if raw > HASH_SPACE_32 / 30.0 {
return -HASH_SPACE_32 * (1.0 - raw / HASH_SPACE_32).ln();
}
raw
}
pub(super) fn count_to_f64(n: usize) -> f64 {
let wide = u64::try_from(n).unwrap_or(u64::MAX);
let hi = u32::try_from(wide >> 32).unwrap_or(0);
let lo = u32::try_from(wide & 0xFFFF_FFFF).unwrap_or(0);
f64::from(hi).mul_add(TWO_POW_32, f64::from(lo))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alpha_m_matches_paper_constants() {
assert!((alpha_m(16) - 0.673).abs() < 1e-12, "alpha_16");
assert!((alpha_m(32) - 0.697).abs() < 1e-12, "alpha_32");
assert!((alpha_m(64) - 0.709).abs() < 1e-12, "alpha_64");
}
#[test]
fn alpha_m_uses_asymptotic_form_for_large_m() {
let got = alpha_m(1024);
let want = 0.7213 / (1.0 + 1.079 / 1024.0);
assert!((got - want).abs() < 1e-12, "alpha_1024 was {got}");
}
#[test]
fn all_zero_registers_estimate_zero() {
let regs = vec![0_u8; 1024];
let est = estimate_from_registers(®s);
assert!(est.abs() < 1e-9, "all-zero estimate should be 0, was {est}");
}
#[test]
fn count_widens_exactly() {
assert!((count_to_f64(16_384) - 16_384.0).abs() < 1e-12, "widening");
}
}