const GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
#[derive(Clone, Debug)]
pub(crate) struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
pub(crate) fn new(seed: u64) -> SplitMix64 {
SplitMix64 { state: seed }
}
pub(crate) fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(GAMMA);
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: u32) -> u32 {
if n == 0 {
return 0;
}
let product = (self.next_u64() >> 32) * u64::from(n);
(product >> 32) as u32
}
pub(crate) fn between(&mut self, lo: u32, hi: u32) -> u32 {
debug_assert!(lo <= hi, "empty range {lo}..={hi}");
lo + self.below(hi - lo + 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seed_zero_matches_the_reference_stream() {
let mut rng = SplitMix64::new(0);
assert_eq!(rng.next_u64(), 0xE220_A839_7B1D_CDAF);
assert_eq!(rng.next_u64(), 0x6E78_9E6A_A1B9_65F4);
assert_eq!(rng.next_u64(), 0x06C4_5D18_8009_454F);
}
#[test]
fn the_same_seed_replays_and_a_different_one_diverges() {
let draw = |seed| {
let mut rng = SplitMix64::new(seed);
(0..64).map(|_| rng.next_u64()).collect::<Vec<_>>()
};
assert_eq!(draw(7), draw(7));
assert_ne!(draw(7), draw(8));
}
#[test]
fn below_stays_in_range_and_covers_it() {
let mut rng = SplitMix64::new(42);
let mut seen = [false; 5];
for _ in 0..10_000 {
let v = rng.below(5);
assert!(v < 5, "below(5) returned {v}");
seen[v as usize] = true;
}
assert!(seen.iter().all(|&s| s), "every value in 0..5 is reachable");
assert_eq!(rng.below(1), 0, "a single-valued range needs no bits");
assert_eq!(rng.below(0), 0, "an empty range does not divide by zero");
}
#[test]
fn between_is_inclusive_at_both_ends() {
let mut rng = SplitMix64::new(99);
let mut lo_seen = false;
let mut hi_seen = false;
for _ in 0..10_000 {
let v = rng.between(1, 5);
assert!((1..=5).contains(&v), "between(1, 5) returned {v}");
lo_seen |= v == 1;
hi_seen |= v == 5;
}
assert!(lo_seen && hi_seen);
assert_eq!(rng.between(3, 3), 3);
}
}