const F64_MBITS: u32 = 52;
const F64_EBIAS: u32 = 1023;
const F32_MBITS: u32 = 23;
const F32_EBIAS: u32 = 127;
struct EntropyPool<F> {
src: F,
pool: u64,
nbits: u32,
}
impl<F: FnMut() -> u64> EntropyPool<F> {
#[inline]
fn new(mut src: F) -> Self {
let pool = src();
Self {
src,
pool,
nbits: 64,
}
}
#[inline]
fn get_bits(&mut self, n: u32) -> u64 {
debug_assert!(n < 64);
let mut result = self.pool;
if self.nbits < n {
crate::cold::cold_barrier();
let needed = n - self.nbits;
self.pool = (self.src)();
result |= self.pool << self.nbits;
self.pool >>= needed;
self.nbits = 64 - needed;
} else {
self.nbits -= n;
self.pool >>= n;
}
result & ((1u64 << n) - 1)
}
}
#[inline]
fn seek64<F: FnMut() -> u64>(pool: &mut EntropyPool<F>) -> (u64, u32) {
let mut a = pool.get_bits(F64_MBITS);
let mut base = 1.0f64.to_bits();
let mut nb = 0;
while a == 0 {
if base < ((F64_MBITS as u64) << F64_MBITS) {
const B: u32 = F64_EBIAS % F64_MBITS;
nb = F64_MBITS - B;
a = pool.get_bits(B) << nb;
break;
}
a = pool.get_bits(F64_MBITS);
base -= (F64_MBITS as u64) << F64_MBITS;
}
a += base;
let b = f64::from_bits(a) - f64::from_bits(base);
nb += (base >> F64_MBITS) as u32 - (b.to_bits() >> F64_MBITS) as u32;
(b.to_bits(), nb)
}
#[inline]
fn seek32<F: FnMut() -> u64>(pool: &mut EntropyPool<F>) -> (u32, u32) {
let mut a = pool.get_bits(F32_MBITS) as u32;
let mut base = 1.0f32.to_bits();
let mut nb = 0;
while a == 0 {
if base < (F32_MBITS << F32_MBITS) {
const B: u32 = F32_EBIAS % F32_MBITS;
nb = F32_MBITS - B;
a = (pool.get_bits(B) as u32) << nb;
break;
}
a = pool.get_bits(F32_MBITS) as u32;
base -= F32_MBITS << F32_MBITS;
}
a += base;
let b = f32::from_bits(a) - f32::from_bits(base);
nb += (base >> F32_MBITS) - (b.to_bits() >> F32_MBITS);
(b.to_bits(), nb)
}
#[inline]
pub fn f64_down(bits: impl FnMut() -> u64) -> f64 {
let mut pool = EntropyPool::new(bits);
let (partial, nb) = seek64(&mut pool);
f64::from_bits(pool.get_bits(nb) + partial)
}
#[inline]
pub fn f32_down(bits: impl FnMut() -> u64) -> f32 {
let mut pool = EntropyPool::new(bits);
let (partial, nb) = seek32(&mut pool);
f32::from_bits(pool.get_bits(nb) as u32 + partial)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sources::Weyl;
fn replay(words: &[u64]) -> impl FnMut() -> u64 + '_ {
let mut iter = words.iter();
move || *iter.next().expect("source exhausted")
}
#[test]
fn test_known_values_f64() {
assert_eq!(f64_down(replay(&[1 << 51])), 0.5);
assert_eq!(
f64_down(replay(&[(1 << 51) | (1 << 52)])),
f64::from_bits(0.5f64.to_bits() + 1)
);
assert_eq!(
f64_down(replay(&[(1 << 52) - 1])),
f64::from_bits(1.0f64.to_bits() - 2)
);
assert_eq!(
f64_down(replay(&[(1 << 53) - 1])),
f64::from_bits(1.0f64.to_bits() - 1)
);
assert_eq!(
f64_down(replay(&[1, 0])),
f64::from_bits(0x3CB0000000000000)
);
}
#[test]
fn test_all_zeros_terminates_and_yields_zero() {
let mut n_calls = 0u32;
let zero_src = || {
n_calls += 1;
assert!(n_calls < 64, "zoom loop does not terminate");
0u64
};
assert_eq!(f64_down(zero_src), 0.0);
}
#[test]
fn test_all_zeros_terminates_and_yields_zero_f32() {
assert_eq!(f32_down(replay(&[0, 0, 0])), 0.0);
}
#[test]
fn test_all_ones_source() {
let x = f64_down(replay(&[!0u64]));
assert_eq!(x, f64::from_bits(1.0f64.to_bits() - 1));
}
#[test]
fn test_range_and_moments_f64() {
let mut src = Weyl(0xDEADBEEF);
let n = 1_000_000;
let mut sum = 0.0;
let mut top_binade = 0u32;
for _ in 0..n {
let x = f64_down(|| src.next_u64());
assert!((0.0..1.0).contains(&x), "out of range: {x}");
sum += x;
if x >= 0.5 {
top_binade += 1;
}
}
let mean = sum / n as f64;
assert!((mean - 0.5).abs() < 1.5e-3, "mean {mean}");
let frac = top_binade as f64 / n as f64;
assert!((frac - 0.5).abs() < 2.5e-3, "top-binade fraction {frac}");
}
#[test]
fn test_range_and_moments_f32() {
let mut src = Weyl(0xC0FFEE);
let n = 1_000_000;
let mut sum = 0.0f64;
for _ in 0..n {
let x = f32_down(|| src.next_u64());
assert!((0.0..1.0).contains(&x), "out of range: {x}");
sum += x as f64;
}
let mean = sum / n as f64;
assert!((mean - 0.5).abs() < 1.5e-3, "mean {mean}");
}
#[test]
fn test_low_binades_are_reachable_and_correctly_distributed() {
let mut src = Weyl(42);
let n = 1_000_000;
let threshold = 1.0 / 1024.0;
let mut hits = 0u32;
for _ in 0..n {
if f64_down(|| src.next_u64()) < threshold {
hits += 1;
}
}
assert!((820..1140).contains(&hits), "hits {hits}");
}
#[test]
fn test_entropy_pool_bit_accounting() {
let word = 0x0123_4567_89AB_CDEFu64;
let words = [word];
let mut pool = EntropyPool::new(replay(&words));
for i in 0..8 {
assert_eq!(pool.get_bits(8), (word >> (8 * i)) & 0xFF);
}
assert_eq!(pool.get_bits(0), 0);
}
}