use std::f64::consts::PI;
const GOLDEN_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
const MIX_A: u64 = 0xBF58_476D_1CE4_E5B9;
const MIX_B: u64 = 0x94D0_49BB_1331_11EB;
const TWO_POW_53: f64 = 9_007_199_254_740_992.0;
const TWO_POW_32: f64 = 4_294_967_296.0;
fn u64_to_f64(x: u64) -> f64 {
let hi = u32::try_from(x >> 32).unwrap_or(0);
let lo = u32::try_from(x & 0xFFFF_FFFF).unwrap_or(0);
f64::from(hi).mul_add(TWO_POW_32, f64::from(lo))
}
#[derive(Debug, Clone)]
pub struct SplitMix64 {
state: u64,
cached_normal: Option<f64>,
}
impl SplitMix64 {
#[must_use]
pub const fn new(seed: u64) -> Self {
Self {
state: seed,
cached_normal: None,
}
}
pub const fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(GOLDEN_GAMMA);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(MIX_A);
z = (z ^ (z >> 27)).wrapping_mul(MIX_B);
z ^ (z >> 31)
}
pub fn next_f64(&mut self) -> f64 {
u64_to_f64(self.next_u64() >> 11) / TWO_POW_53
}
pub fn standard_normal(&mut self) -> f64 {
if let Some(z) = self.cached_normal.take() {
return z;
}
let u1 = self.next_f64().max(f64::MIN_POSITIVE);
let u2 = self.next_f64();
let r = (-2.0 * u1.ln()).sqrt();
let theta = 2.0 * PI * u2;
self.cached_normal = Some(r * theta.sin());
r * theta.cos()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_seed_same_stream() {
let mut a = SplitMix64::new(42);
let mut b = SplitMix64::new(42);
for _ in 0..1000 {
assert_eq!(
a.next_u64(),
b.next_u64(),
"identical seeds must yield identical streams"
);
}
}
#[test]
fn uniform_in_unit_interval() {
let mut r = SplitMix64::new(7);
for _ in 0..10_000 {
let u = r.next_f64();
assert!((0.0..1.0).contains(&u), "next_f64 escaped [0, 1): {u}");
}
}
#[test]
fn standard_normal_is_deterministic_and_finite() {
let mut a = SplitMix64::new(99);
let mut b = SplitMix64::new(99);
for _ in 0..1000 {
let za = a.standard_normal();
assert!(za.is_finite(), "standard_normal produced non-finite {za}");
assert!(
(za - b.standard_normal()).abs() < 1e-15,
"standard_normal stream diverged for identical seeds"
);
}
}
}