pub struct Rng {
s0: u64,
s1: u64,
}
const DEFAULT_SEED: u64 = 0xDEAD_BEEF;
impl Rng {
pub fn new(seed: u64) -> Self {
let seed = if seed == 0 { DEFAULT_SEED } else { seed };
let s0 = splitmix64(seed);
let s1 = splitmix64(s0);
Self { s0, s1 }
}
#[inline]
fn next_u64(&mut self) -> u64 {
let s0 = self.s0;
let mut s1 = self.s1;
let result = s0.wrapping_add(s1);
s1 ^= s0;
self.s0 = s0.rotate_left(24) ^ s1 ^ (s1 << 16);
self.s1 = s1.rotate_left(37);
result
}
#[inline]
pub fn next_f64(&mut self) -> f64 {
let bits = self.next_u64() >> 11; bits as f64 * (1.0_f64 / (1u64 << 53) as f64)
}
}
#[inline]
fn splitmix64(seed: u64) -> u64 {
let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seed_reproducibility() {
let mut a = Rng::new(42);
let mut b = Rng::new(42);
for _ in 0..1000 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn seed_zero_uses_default() {
let mut a = Rng::new(0);
let mut b = Rng::new(DEFAULT_SEED);
for _ in 0..100 {
assert_eq!(a.next_f64(), b.next_f64());
}
}
#[test]
fn different_seeds_differ() {
let mut a = Rng::new(1);
let mut b = Rng::new(2);
let va: Vec<u64> = (0..10).map(|_| a.next_u64()).collect();
let vb: Vec<u64> = (0..10).map(|_| b.next_u64()).collect();
assert_ne!(va, vb);
}
#[test]
fn next_f64_range() {
let mut rng = Rng::new(12345);
for _ in 0..100_000 {
let v = rng.next_f64();
assert!((0.0..1.0).contains(&v), "out of range: {v}");
}
}
#[test]
fn distribution_not_biased() {
let mut rng = Rng::new(7777);
let n = 100_000;
let mut bins = [0u32; 10];
for _ in 0..n {
let v = rng.next_f64();
let idx = (v * 10.0) as usize;
bins[idx.min(9)] += 1;
}
let expected = n as f64 / 10.0;
for (i, &count) in bins.iter().enumerate() {
let ratio = count as f64 / expected;
assert!(
(0.9..1.1).contains(&ratio),
"bin {i}: count={count}, ratio={ratio:.3} — biased",
);
}
}
}