#[derive(Clone, Debug)]
pub(crate) struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
pub(crate) const fn new(seed: u64) -> Self {
Self { state: seed }
}
pub(crate) fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub(crate) fn below(&mut self, n: u64) -> u64 {
if n <= 1 {
return 0;
}
let zone = u64::MAX - (u64::MAX % n) - 1;
loop {
let value = self.next_u64();
if value <= zone {
return value % n;
}
}
}
pub(crate) fn bool(&mut self) -> bool {
self.next_u64() >> 63 == 1
}
}
#[cfg(test)]
mod tests {
use super::*;
const SEED_0: [u64; 4] = [
0xE220_A839_7B1D_CDAF,
0x6E78_9E6A_A1B9_65F4,
0x06C4_5D18_8009_454F,
0xF88B_B8A8_724C_81EC,
];
const SEED_1: [u64; 4] = [
0x910A_2DEC_8902_5CC1,
0xBEEB_8DA1_658E_EC67,
0xF893_A2EE_FB32_555E,
0x71C1_8690_EE42_C90B,
];
#[test]
fn stream_matches_committed_vectors() {
for (seed, expected) in [(0, SEED_0), (1, SEED_1)] {
let mut rng = SplitMix64::new(seed);
let actual: Vec<u64> = (0..4).map(|_| rng.next_u64()).collect();
assert_eq!(actual, expected, "stream changed for seed {seed}");
}
}
#[test]
fn same_seed_gives_the_same_stream() {
let mut a = SplitMix64::new(12345);
let mut b = SplitMix64::new(12345);
for _ in 0..100 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn below_stays_in_range() {
let mut rng = SplitMix64::new(7);
for n in [1_u64, 2, 3, 10, 255, 1 << 32] {
for _ in 0..200 {
assert!(rng.below(n) < n, "below({n}) escaped its range");
}
}
}
#[test]
fn below_degenerate_bounds_are_zero() {
let mut rng = SplitMix64::new(0);
assert_eq!(rng.below(0), 0);
assert_eq!(rng.below(1), 0);
}
#[test]
fn below_is_roughly_uniform() {
let mut rng = SplitMix64::new(99);
let mut buckets = [0_u32; 6];
for _ in 0..60_000 {
buckets[rng.below(6) as usize] += 1;
}
for (i, count) in buckets.iter().enumerate() {
assert!(
(8_500..11_500).contains(count),
"bucket {i} got {count} of 60000 draws"
);
}
}
#[test]
fn bool_is_balanced() {
let mut rng = SplitMix64::new(4);
let trues = (0..10_000).filter(|_| rng.bool()).count();
assert!(
(4_500..5_500).contains(&trues),
"{trues} of 10000 were true"
);
}
}