#[inline]
pub fn f64_64(mut bits: impl FnMut() -> u64) -> f64 {
let u = bits();
if u == 0 {
return 0.0;
}
let z = u.leading_zeros() as u64;
f64::from_bits((1022 - z) << 52 | ((u << z) << 1) >> 12)
}
#[inline]
const fn two_to_minus(n: u64) -> f64 {
debug_assert!(n <= 1022);
f64::from_bits((1023 - n) << 52)
}
#[inline]
pub fn f64_117(mut bits: impl FnMut() -> u64) -> f64 {
let u = bits();
let z = u.leading_zeros() as u64;
if z <= 11 {
return f64::from_bits((1022 - z) << 52 | ((u << z) << 1) >> 12);
}
crate::cold::cold_barrier();
let u = u.checked_shl(z as u32).unwrap_or(0) | bits() >> (64 - z);
(u >> 11) as f64 * two_to_minus(53 + z)
}
#[inline]
pub fn f64_full(mut bits: impl FnMut() -> u64) -> f64 {
let mut u = bits();
let mut z = u.leading_zeros() as u64;
if z <= 11 {
return f64::from_bits((1022 - z) << 52 | ((u << z) << 1) >> 12);
}
crate::cold::cold_barrier();
let mut exp = 0u64;
while u == 0 {
u = bits();
z = u.leading_zeros() as u64;
exp += 64;
if exp + z >= 1074 {
return 0.0;
}
}
let u = u << z | bits().checked_shr(64 - z as u32).unwrap_or(0);
exp += z;
if exp < 1022 {
return f64::from_bits((1022 - exp) << 52 | (u << 1) >> 12);
}
f64::from_bits(u >> (exp - 1022) >> 12)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sources::Weyl;
#[test]
fn test_matches_reference_division_form() {
let division_form = |u: u64| {
let z = u.leading_zeros();
let m = ((u << z) >> 11) as f64;
m / (1u64 << 53) as f64 / (1u64 << z) as f64
};
let mut rng = Weyl(7);
for _ in 0..100_000 {
let u = rng.0.wrapping_add(0x9E3779B97F4A7C15);
assert_eq!(f64_64(|| rng.next_u64()), division_form(u));
}
}
#[test]
fn test_range() {
let mut rng = Weyl(42);
for _ in 0..100_000 {
let x = f64_64(|| rng.next_u64());
assert!((0.0..1.0).contains(&x), "f64_64: {x}");
}
}
#[test]
fn test_edge_cases() {
assert_eq!(f64_64(|| 1 << 63), 0.5);
assert_eq!(f64_64(|| u64::MAX), f64::from_bits(1.0f64.to_bits() - 1));
assert_eq!(f64_64(|| 1), f64::from_bits((1023 - 64) << 52)); assert_eq!(f64_64(|| 0), 0.0);
}
fn replay(words: &[u64]) -> impl FnMut() -> u64 + '_ {
let mut iter = words.iter();
move || *iter.next().expect("source exhausted")
}
#[test]
fn test_known_values_117() {
for (words, expected) in [
(&[0x8000000000000000, 0][..], 0x3fe0000000000000u64),
(&[0xDEADBEEFDEADBEEF, 0][..], 0x3febd5b7ddfbd5b7),
(&[1 << 52, 0][..], 0x3f30000000000000),
(&[1 << 51, 0xC0FFEE0DDF00D5ED][..], 0x3f20000000000001),
(&[1, 0xC0FFEE0DDF00D5ED][..], 0x3bfc0ffee0ddf00d),
(&[3, 0xFFFFFFFFFFFFFFFF][..], 0x3c0fffffffffffff),
(&[0, 0xC0FFEE0DDF00D5ED][..], 0x3be81ffdc1bbe01a),
(&[0, 0][..], 0),
] {
assert_eq!(
f64_117(replay(words)).to_bits(),
expected,
"words {words:x?}"
);
}
}
#[test]
fn test_known_values_full() {
for (words, expected) in [
(vec![0x8000000000000000], 0x3fe0000000000000u64),
(vec![0xDEADBEEFDEADBEEF], 0x3febd5b7ddfbd5b7),
(vec![1 << 52], 0x3f30000000000000),
(vec![1 << 51, 0xC0FFEE0DDF00D5ED], 0x3f20000000000001),
(vec![1, 0xC0FFEE0DDF00D5ED], 0x3bfc0ffee0ddf00d),
(vec![3, 0xFFFFFFFFFFFFFFFF], 0x3c0fffffffffffff),
(vec![0, 0xC0FFEE0DDF00D5ED, 0xABCD], 0x3be81ffdc1bbe01a),
(vec![0, 0, 0xC0FFEE0DDF00D5ED, 0xAB], 0x37e81ffdc1bbe01a),
(
[vec![0; 15], vec![2, u64::MAX]].concat(),
0x000bffffffffffff,
),
(
[vec![0; 15], vec![1, u64::MAX]].concat(),
0x0007ffffffffffff,
),
(
[vec![0; 16], vec![1 << 50, 0xFFFFFFFFFFFFF]].concat(),
0x0000001000000000,
),
([vec![0; 16], vec![3, 0]].concat(), 0),
(vec![0; 18], 0),
] {
assert_eq!(
f64_full(replay(&words)).to_bits(),
expected,
"words {words:x?}"
);
}
}
#[test]
fn test_matches_go_reference_hash() {
let mut rng = Weyl(42);
let mut h = 0u64;
for _ in 0..1_000_000 {
h = h.rotate_left(1) ^ f64_117(|| rng.next_u64()).to_bits();
}
assert_eq!(h, 0xf9b6db6017240be7);
let mut rng = Weyl(42);
let mut h = 0u64;
for _ in 0..1_000_000 {
h = h.rotate_left(1) ^ f64_full(|| rng.next_u64()).to_bits();
}
assert_eq!(h, 0xf9b6db6017240be7);
}
#[test]
fn test_range_117_full() {
let mut rng = Weyl(42);
for _ in 0..100_000 {
let x = f64_117(|| rng.next_u64());
assert!((0.0..1.0).contains(&x), "f64_117: {x}");
let x = f64_full(|| rng.next_u64());
assert!((0.0..1.0).contains(&x), "f64_full: {x}");
}
}
}