#[derive(Debug, Clone)]
pub struct Rng {
state: u64,
}
impl Rng {
#[must_use]
pub const fn new(seed: u64) -> Rng {
Rng { state: seed }
}
#[must_use]
pub const fn state(&self) -> u64 {
self.state
}
pub const 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 const fn below(&mut self, n: usize) -> usize {
if n == 0 {
return 0;
}
(self.next_u64() % n as u64) as usize
}
pub const fn between(&mut self, lo: usize, hi: usize) -> usize {
if hi <= lo {
return lo;
}
lo + self.below(hi - lo + 1)
}
pub const fn chance(&mut self, num: u32, den: u32) -> bool {
self.below(den as usize) < num as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_same_seed_is_the_same_stream() {
let mut a = Rng::new(12345);
let mut b = Rng::new(12345);
for _ in 0..1000 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn different_seeds_are_different_streams() {
let mut a = Rng::new(1);
let mut b = Rng::new(2);
let same = (0..1000).filter(|_| a.next_u64() == b.next_u64()).count();
assert_eq!(same, 0);
}
#[test]
fn the_stream_does_not_settle_on_one_value() {
const N: usize = if cfg!(miri) { 500 } else { 10_000 };
let mut r = Rng::new(0);
let mut seen = std::collections::HashSet::new();
for _ in 0..N {
seen.insert(r.next_u64());
}
assert_eq!(seen.len(), N, "the stream repeats itself");
}
#[test]
fn below_stays_below() {
let mut r = Rng::new(99);
for n in 1..50usize {
for _ in 0..200 {
assert!(r.below(n) < n);
}
}
assert_eq!(r.below(0), 0, "no division by zero");
}
#[test]
fn below_reaches_both_ends() {
let mut r = Rng::new(7);
let mut lo = false;
let mut hi = false;
for _ in 0..1000 {
match r.below(8) {
0 => lo = true,
7 => hi = true,
_ => {}
}
}
assert!(
lo && hi,
"a generator that never picks an end is not uniform"
);
}
#[test]
fn between_covers_its_range_inclusive() {
let mut r = Rng::new(4);
let mut seen = [false; 5];
for _ in 0..1000 {
let v = r.between(2, 6);
assert!((2..=6).contains(&v));
seen[v - 2] = true;
}
assert!(seen.iter().all(|&s| s));
assert_eq!(r.between(5, 5), 5);
assert_eq!(r.between(9, 3), 9, "a backwards range is its own low end");
}
#[test]
fn chance_is_roughly_the_odds_it_says() {
let mut r = Rng::new(31);
let hits = (0..10_000).filter(|_| r.chance(1, 4)).count();
assert!((2200..2800).contains(&hits), "got {hits} in 10000");
}
}