#[cfg(feature = "std")]
use core::cell::Cell;
#[cfg(feature = "std")]
std::thread_local! {
static RNG_STATE: Cell<Rng> = Cell::new(Rng::from_system_time());
}
#[cfg(not(feature = "std"))]
use core::sync::atomic::Ordering;
#[cfg(not(feature = "std"))]
use portable_atomic::AtomicU64;
#[cfg(not(feature = "std"))]
static RNG_STATE: AtomicU64 = AtomicU64::new(0x853c49e6748fea9b);
#[derive(Debug, Clone, Copy)]
pub struct Rng {
s0: u64,
s1: u64,
}
impl Rng {
#[inline]
pub const fn new(s0: u64, s1: u64) -> Self {
let s0 = if s0 == 0 && s1 == 0 { 1 } else { s0 };
Self { s0, s1 }
}
#[inline]
pub fn from_seed(seed: u64) -> Self {
let s0 = splitmix64(seed);
let s1 = splitmix64(seed.wrapping_add(0x9e3779b97f4a7c15));
Self::new(s0, s1)
}
#[cfg(feature = "std")]
pub fn from_system_time() -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let seed = duration.as_nanos() as u64;
Self::from_seed(seed)
}
#[inline]
pub fn next_u64(&mut self) -> u64 {
let s0 = self.s0;
let mut s1 = self.s1;
let result = s0.wrapping_add(s1);
s1 ^= s0;
self.s0 = s0.rotate_left(24) ^ s1 ^ (s1 << 16);
self.s1 = s1.rotate_left(37);
result
}
#[inline]
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
}
#[inline]
pub fn next_f64_bipolar(&mut self) -> f64 {
self.next_f64() * 2.0 - 1.0
}
#[inline]
pub fn next_bool(&mut self) -> bool {
(self.next_u64() >> 63) == 1
}
#[inline]
pub fn next_bool_with_probability(&mut self, probability: f64) -> bool {
self.next_f64() < probability
}
pub fn jump(&mut self) {
const JUMP: [u64; 2] = [0xdf900294d8f554a5, 0x170865df4b3201fc];
let mut s0 = 0u64;
let mut s1 = 0u64;
for jump_val in JUMP.iter() {
for b in 0..64 {
if (jump_val >> b) & 1 != 0 {
s0 ^= self.s0;
s1 ^= self.s1;
}
self.next_u64();
}
}
self.s0 = s0;
self.s1 = s1;
}
}
impl Default for Rng {
fn default() -> Self {
#[cfg(feature = "std")]
{
Self::from_system_time()
}
#[cfg(not(feature = "std"))]
{
Self::new(0x853c49e6748fea9b, 0xda3e39cb94b95bdb)
}
}
}
#[inline]
fn splitmix64(mut x: u64) -> u64 {
x = x.wrapping_add(0x9e3779b97f4a7c15);
x = (x ^ (x >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)).wrapping_mul(0x94d049bb133111eb);
x ^ (x >> 31)
}
pub trait SeedableRng: Sized {
fn from_seed(seed: u64) -> Self;
fn next_f64(&mut self) -> f64;
fn next_f64_bipolar(&mut self) -> f64 {
self.next_f64() * 2.0 - 1.0
}
}
impl SeedableRng for Rng {
fn from_seed(seed: u64) -> Self {
Rng::from_seed(seed)
}
fn next_f64(&mut self) -> f64 {
self.next_f64()
}
fn next_f64_bipolar(&mut self) -> f64 {
self.next_f64_bipolar()
}
}
#[inline]
pub fn random() -> f64 {
#[cfg(feature = "std")]
{
RNG_STATE.with(|cell| {
let mut rng = cell.get();
let value = rng.next_f64();
cell.set(rng);
value
})
}
#[cfg(not(feature = "std"))]
{
let z = splitmix64(RNG_STATE.fetch_add(0x9e3779b97f4a7c15, Ordering::Relaxed));
(z >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
}
}
#[inline]
pub fn random_bipolar() -> f64 {
random() * 2.0 - 1.0
}
#[inline]
pub fn seed(seed: u64) {
#[cfg(feature = "std")]
{
RNG_STATE.with(|cell| {
cell.set(Rng::from_seed(seed));
});
}
#[cfg(not(feature = "std"))]
{
RNG_STATE.store(seed, Ordering::Relaxed);
}
}
#[inline]
pub fn random_bool(probability: f64) -> bool {
random() < probability
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rng_deterministic() {
let mut rng1 = Rng::from_seed(12345);
let mut rng2 = Rng::from_seed(12345);
for _ in 0..100 {
assert_eq!(rng1.next_u64(), rng2.next_u64());
}
}
#[test]
fn test_rng_different_seeds() {
let mut rng1 = Rng::from_seed(12345);
let mut rng2 = Rng::from_seed(54321);
assert_ne!(rng1.next_u64(), rng2.next_u64());
}
#[test]
fn test_rng_f64_range() {
let mut rng = Rng::from_seed(42);
for _ in 0..1000 {
let v = rng.next_f64();
assert!((0.0..1.0).contains(&v), "Value {} out of range", v);
}
}
#[test]
fn test_rng_bipolar_range() {
let mut rng = Rng::from_seed(42);
for _ in 0..1000 {
let v = rng.next_f64_bipolar();
assert!((-1.0..1.0).contains(&v), "Value {} out of range", v);
}
}
#[test]
fn test_rng_distribution() {
let mut rng = Rng::from_seed(42);
let mut sum = 0.0;
let count = 10000;
for _ in 0..count {
sum += rng.next_f64();
}
let mean = sum / count as f64;
assert!((mean - 0.5).abs() < 0.02, "Mean {} too far from 0.5", mean);
}
#[test]
fn test_global_random() {
seed(12345);
let v1 = random();
let v2 = random();
assert_ne!(v1, v2);
assert!((0.0..1.0).contains(&v1));
assert!((0.0..1.0).contains(&v2));
}
#[test]
fn test_random_bipolar() {
seed(42);
for _ in 0..100 {
let v = random_bipolar();
assert!((-1.0..1.0).contains(&v));
}
}
#[test]
fn test_random_bool() {
seed(42);
let mut true_count = 0;
let count = 10000;
for _ in 0..count {
if random_bool(0.3) {
true_count += 1;
}
}
let ratio = true_count as f64 / count as f64;
assert!(
(ratio - 0.3).abs() < 0.03,
"Ratio {} too far from 0.3",
ratio
);
}
#[test]
fn test_rng_jump() {
let mut rng1 = Rng::from_seed(42);
let mut rng2 = Rng::from_seed(42);
rng1.jump();
assert_ne!(rng1.next_u64(), rng2.next_u64());
}
#[test]
fn test_zero_seed_handling() {
let mut rng = Rng::new(0, 0);
let v = rng.next_f64();
assert!((0.0..1.0).contains(&v));
}
#[test]
fn test_rng_known_answer_direct_state() {
let mut rng = Rng::new(1, 2);
let expected: [u64; 8] = [
0x0000000000000003,
0x0000006001030003,
0x20c102c302000c03,
0x810180670d23ad61,
0x26d13a4941333a42,
0x538a501c02f58b2e,
0x2ab2076dee382f7e,
0x30dfcfb722fecd9c,
];
for (i, &want) in expected.iter().enumerate() {
let got = rng.next_u64();
assert_eq!(got, want, "next_u64 mismatch at index {i}: {got:#018x}");
}
}
#[test]
fn test_rng_known_answer_from_seed() {
let mut rng = Rng::from_seed(0x0123456789ABCDEF);
let expected: [u64; 4] = [
0xeaed8aa2d9317b30,
0xb300b6f0786253c8,
0x4753ec6d32d7fadf,
0x371c7ae10fed1d49,
];
for (i, &want) in expected.iter().enumerate() {
let got = rng.next_u64();
assert_eq!(got, want, "from_seed next_u64 mismatch at index {i}");
}
}
#[test]
fn test_next_bool_uses_top_bit() {
let mut rng = Rng::new(1, 2);
let expected = [false, false, false, true, false, false, false, false];
for (i, &want) in expected.iter().enumerate() {
assert_eq!(rng.next_bool(), want, "next_bool mismatch at index {i}");
}
}
#[test]
fn test_next_bool_balanced() {
let mut rng = Rng::from_seed(0xBEEF);
let mut trues = 0usize;
let count = 20_000;
for _ in 0..count {
if rng.next_bool() {
trues += 1;
}
}
let ratio = trues as f64 / count as f64;
assert!(
(ratio - 0.5).abs() < 0.02,
"next_bool ratio {ratio} not ~0.5"
);
}
}