use std::cell::Cell;
use std::collections::hash_map::RandomState;
use std::hash::BuildHasher;
fn next_u64(state: u64) -> (u64, u64) {
let state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
(state, z ^ (z >> 31))
}
fn random_seed() -> u64 {
RandomState::new().hash_one(std::thread::current().id())
}
thread_local! {
static STATE: Cell<u64> = Cell::new(random_seed());
}
pub fn random_bit() -> bool {
STATE.with(|state| {
let (next, value) = next_u64(state.get());
state.set(next);
value & 1 == 1
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn assert_split_mix64_matches_the_reference_output() {
let expected = [
0xe220_a839_7b1d_cdaf,
0x6e78_9e6a_a1b9_65f4,
0x06c4_5d18_8009_454f,
0xf88b_b8a8_724c_81ec,
];
let mut state = 0;
for want in expected {
let (next, got) = next_u64(state);
state = next;
assert_eq!(got, want);
}
}
#[test]
fn assert_random_bit_yields_both_values() {
let mut seen = [false; 2];
for _ in 0..64 {
seen[usize::from(random_bit())] = true;
}
assert_eq!(seen, [true, true], "the stream stopped advancing");
}
#[test]
fn assert_random_bit_is_unbiased() {
const DRAWS: u32 = 1_000_000;
const TOLERANCE: u32 = 3_000;
let mut heads = 0;
for _ in 0..DRAWS {
heads += u32::from(random_bit());
}
assert!(heads.abs_diff(DRAWS / 2) <= TOLERANCE, "heads = {heads}");
}
#[test]
fn assert_each_thread_draws_its_own_stream() {
fn draw_word() -> u64 {
(0..64).fold(0, |word, _| (word << 1) | u64::from(random_bit()))
}
let other = std::thread::spawn(draw_word)
.join()
.expect("the drawing thread should not panic");
assert_ne!(draw_word(), other, "two threads shared one stream");
}
}