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(kani)]
mod verification {
use super::{SplitMix64, TWO_POW_32, TWO_POW_53, u64_to_f64};
#[kani::proof]
fn rng_u64_to_f64_faithful() {
let x: u64 = kani::any();
kani::assume(x < (1u64 << 53));
let y = u64_to_f64(x);
assert!(y.is_finite(), "u64_to_f64 produced non-finite output");
assert!(y >= 0.0, "u64_to_f64 produced a negative value");
assert!(y < TWO_POW_53, "u64_to_f64 escaped the 2^53 window");
}
#[kani::proof]
fn rng_u64_to_f64_no_overflow() {
let x: u64 = kani::any();
let y = u64_to_f64(x);
assert!(
y.is_finite(),
"full-range u64_to_f64 overflowed to non-finite"
);
assert!(y >= 0.0, "full-range u64_to_f64 produced a negative value");
assert!(y <= TWO_POW_32 * TWO_POW_32, "u64_to_f64 exceeded 2^64");
}
#[kani::proof]
fn rng_next_u64_no_panic() {
let state: u64 = kani::any();
let mut rng = SplitMix64::new(state);
let _ = rng.next_u64();
}
#[kani::proof]
fn rng_next_f64_in_unit_interval() {
let state: u64 = kani::any();
let mut rng = SplitMix64::new(state);
let u = rng.next_f64();
assert!(u >= 0.0, "next_f64 produced a negative value: {u}");
assert!(u < 1.0, "next_f64 reached or exceeded 1.0: {u}");
}
}
#[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"
);
}
}
}