#[derive(Debug, Clone)]
pub struct DetRng {
state: u64,
}
impl DetRng {
#[must_use]
#[inline]
pub const fn new(seed: u64) -> Self {
Self {
state: if seed == 0 { 1 } else { seed },
}
}
#[inline]
#[allow(clippy::missing_const_for_fn)] pub fn next_u64(&mut self) -> u64 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.state = x;
x
}
#[inline]
pub fn next_u32(&mut self) -> u32 {
(self.next_u64() >> 32) as u32
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub fn next_usize(&mut self, bound: usize) -> usize {
assert!(bound > 0, "bound must be non-zero");
let bound_u64 = bound as u64;
let threshold = u64::MAX - (u64::MAX % bound_u64);
loop {
let value = self.next_u64();
if value < threshold {
return (value % bound_u64) as usize;
}
}
}
#[inline]
pub fn next_bool(&mut self) -> bool {
self.next_u64() & 1 == 1
}
#[inline]
pub fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut i = 0;
while i < dest.len() {
let rand = self.next_u64();
let bytes = rand.to_le_bytes();
let n = std::cmp::min(dest.len() - i, 8);
dest[i..i + n].copy_from_slice(&bytes[..n]);
i += n;
}
}
#[inline]
pub fn shuffle<T>(&mut self, slice: &mut [T]) {
for i in (1..slice.len()).rev() {
let j = self.next_usize(i + 1);
slice.swap(i, j);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_sequence() {
let mut rng1 = DetRng::new(42);
let mut rng2 = DetRng::new(42);
for _ in 0..100 {
assert_eq!(rng1.next_u64(), rng2.next_u64());
}
}
#[test]
fn different_seeds_different_sequences() {
let mut rng1 = DetRng::new(42);
let mut rng2 = DetRng::new(43);
assert_ne!(rng1.next_u64(), rng2.next_u64());
}
#[test]
fn zero_seed_handled() {
let mut rng = DetRng::new(0);
assert_ne!(rng.next_u64(), 0);
}
#[test]
fn det_rng_debug_clone() {
let mut rng = DetRng::new(42);
let dbg = format!("{rng:?}");
assert!(dbg.contains("DetRng"), "{dbg}");
let _ = rng.next_u64(); let mut forked = rng.clone();
assert_eq!(rng.next_u64(), forked.next_u64());
}
}