pub struct Xorshift64 {
state: u64,
}
impl Xorshift64 {
pub fn new(seed: u64) -> Self {
Self {
state: if seed == 0 { 1 } else { seed },
}
}
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
}
pub fn bernoulli(&mut self, p: f64) -> bool {
if p <= 0.0 {
return false;
}
if p >= 1.0 {
return true;
}
let bits = self.next_u64() >> 11;
(bits as f64) / ((1u64 << 53) as f64) < p
}
pub fn range_u64(&mut self, n: u64) -> u64 {
if n == 0 {
return 0;
}
let r = self.next_u64();
((r as u128 * n as u128) >> 64) as u64
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
#[test]
fn deterministic_with_same_seed() {
let mut a = Xorshift64::new(42);
let mut b = Xorshift64::new(42);
for _ in 0..1000 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn bernoulli_extremes() {
let mut g = Xorshift64::new(1);
assert!(!g.bernoulli(0.0));
assert!(g.bernoulli(1.0));
assert!(!g.bernoulli(-0.5));
assert!(g.bernoulli(2.0));
}
#[test]
fn bernoulli_distribution_close_to_p() {
let mut g = Xorshift64::new(7);
let n = 100_000;
let hits: usize = (0..n).filter(|_| g.bernoulli(0.3)).count();
let p = hits as f64 / n as f64;
assert!((0.29..0.31).contains(&p), "p={p}");
}
#[test]
fn range_zero_is_zero() {
let mut g = Xorshift64::new(1);
assert_eq!(g.range_u64(0), 0);
}
#[test]
fn range_within_bounds() {
let mut g = Xorshift64::new(1);
for _ in 0..1000 {
let v = g.range_u64(100);
assert!(v < 100);
}
}
}