#[derive(Clone, Debug)]
pub(crate) struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
pub(crate) fn new(seed: u64) -> Self {
SplitMix64 { 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 next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
}
pub(crate) fn range_f64(&mut self, lo: f64, hi: f64) -> f64 {
if hi.partial_cmp(&lo) != Some(std::cmp::Ordering::Greater) {
return lo;
}
lo + (hi - lo) * self.next_f64()
}
pub(crate) fn below(&mut self, n: usize) -> usize {
debug_assert!(n > 0);
((self.next_u64() as u128 * n as u128) >> 64) as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_for_equal_seeds() {
let mut a = SplitMix64::new(7);
let mut b = SplitMix64::new(7);
for _ in 0..100 {
assert_eq!(a.next_u64(), b.next_u64());
}
let mut c = SplitMix64::new(8);
assert_ne!(SplitMix64::new(7).next_u64(), c.next_u64());
}
#[test]
fn known_reference_values() {
let mut r = SplitMix64::new(1_234_567);
assert_eq!(r.next_u64(), 6_457_827_717_110_365_317);
assert_eq!(r.next_u64(), 3_203_168_211_198_807_973);
}
#[test]
fn ranges_are_respected_and_roughly_uniform() {
let mut r = SplitMix64::new(42);
let n = 20_000;
let mut sum = 0.0;
let mut hist = [0usize; 10];
for _ in 0..n {
let x = r.range_f64(-2.0, 3.0);
assert!((-2.0..3.0).contains(&x));
sum += x;
let k = r.below(10);
assert!(k < 10);
hist[k] += 1;
}
let mean = sum / n as f64;
assert!((mean - 0.5).abs() < 0.05, "mean {mean}");
for h in hist {
assert!((1_700..2_300).contains(&h), "bucket {h}");
}
assert_eq!(r.range_f64(1.0, 1.0), 1.0);
assert_eq!(r.range_f64(1.0, f64::NAN), 1.0);
}
}