const SPLITMIX_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
const JUMP: [u64; 4] = [
0x180E_C6D3_3CFD_0ABA,
0xD5A6_1266_F0C9_392C,
0xA958_2618_E03F_C9AA,
0x39AB_DC45_29B1_661C,
];
const LONG_JUMP: [u64; 4] = [
0x76E1_5D3E_FEFD_CBBF,
0xC500_4E44_1C52_2FB3,
0x7771_0069_854E_E241,
0x3910_9BB0_2ACB_E635,
];
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Xoshiro256 {
state: [u64; 4],
}
impl Xoshiro256 {
#[inline]
pub fn seed_from_u64(seed: u64) -> Self {
let mut x = seed;
Self {
state: [
splitmix64(&mut x),
splitmix64(&mut x),
splitmix64(&mut x),
splitmix64(&mut x),
],
}
}
#[inline]
pub fn from_state(state: [u64; 4]) -> Option<Self> {
if state == [0; 4] {
None
} else {
Some(Self { state })
}
}
#[inline]
pub fn state(&self) -> [u64; 4] {
self.state
}
#[inline]
pub fn next_u64(&mut self) -> u64 {
let result = self.state[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
let t = self.state[1] << 17;
self.state[2] ^= self.state[0];
self.state[3] ^= self.state[1];
self.state[1] ^= self.state[2];
self.state[0] ^= self.state[3];
self.state[2] ^= t;
self.state[3] = self.state[3].rotate_left(45);
result
}
#[inline]
pub fn next_u32(&mut self) -> u32 {
(self.next_u64() >> 32) as u32
}
#[inline]
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0 / ((1u64 << 53) as f64))
}
pub fn fill_bytes(&mut self, buf: &mut [u8]) {
let mut chunks = buf.chunks_exact_mut(8);
for chunk in &mut chunks {
let bytes = self.next_u64().to_le_bytes();
chunk.copy_from_slice(&bytes);
}
let tail = chunks.into_remainder();
if !tail.is_empty() {
let bytes = self.next_u64().to_le_bytes();
tail.copy_from_slice(&bytes[..tail.len()]);
}
}
pub fn jump(&mut self) {
self.apply_jump(&JUMP);
}
pub fn long_jump(&mut self) {
self.apply_jump(&LONG_JUMP);
}
fn apply_jump(&mut self, constants: &[u64; 4]) {
let mut s0 = 0u64;
let mut s1 = 0u64;
let mut s2 = 0u64;
let mut s3 = 0u64;
for &word in constants {
for b in 0..64 {
if (word & (1u64 << b)) != 0 {
s0 ^= self.state[0];
s1 ^= self.state[1];
s2 ^= self.state[2];
s3 ^= self.state[3];
}
let _ = self.next_u64();
}
}
self.state[0] = s0;
self.state[1] = s1;
self.state[2] = s2;
self.state[3] = s3;
}
}
#[inline]
fn splitmix64(x: &mut u64) -> u64 {
*x = x.wrapping_add(SPLITMIX_GAMMA);
let mut z = *x;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seed_produces_a_value() {
let mut rng = Xoshiro256::seed_from_u64(1);
let _ = rng.next_u64();
}
#[test]
fn different_seeds_produce_different_streams() {
let mut a = Xoshiro256::seed_from_u64(1);
let mut b = Xoshiro256::seed_from_u64(2);
assert_ne!(a.next_u64(), b.next_u64());
}
#[test]
fn same_seed_produces_same_stream() {
let mut a = Xoshiro256::seed_from_u64(42);
let mut b = Xoshiro256::seed_from_u64(42);
for _ in 0..256 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn zero_seed_does_not_yield_zero_state() {
let mut rng = Xoshiro256::seed_from_u64(0);
assert_ne!(rng.state(), [0; 4]);
assert_ne!(rng.next_u64(), 0);
}
#[test]
fn from_state_rejects_all_zero() {
assert!(Xoshiro256::from_state([0, 0, 0, 0]).is_none());
assert!(Xoshiro256::from_state([0, 0, 0, 1]).is_some());
}
#[test]
fn state_roundtrip_resumes_stream() {
let mut rng = Xoshiro256::seed_from_u64(99);
for _ in 0..17 {
let _ = rng.next_u64();
}
let snap = rng.state();
let mut resumed = Xoshiro256::from_state(snap).unwrap();
for _ in 0..32 {
assert_eq!(rng.next_u64(), resumed.next_u64());
}
}
#[test]
fn splitmix64_known_value_zero_counter() {
let mut x = 0u64;
assert_eq!(splitmix64(&mut x), 0xE220_A839_7B1D_CDAF);
}
#[test]
fn xoshiro_known_first_outputs_for_seed_zero() {
let mut rng = Xoshiro256::seed_from_u64(0);
let v0 = rng.next_u64();
let v1 = rng.next_u64();
let v2 = rng.next_u64();
let v3 = rng.next_u64();
assert_ne!(v0, 0);
assert_ne!(v0, v1);
assert_ne!(v1, v2);
assert_ne!(v2, v3);
let mut rng2 = Xoshiro256::seed_from_u64(0);
assert_eq!(rng2.next_u64(), v0);
assert_eq!(rng2.next_u64(), v1);
assert_eq!(rng2.next_u64(), v2);
assert_eq!(rng2.next_u64(), v3);
}
#[test]
fn next_u32_takes_high_bits() {
let mut a = Xoshiro256::seed_from_u64(7);
let mut b = Xoshiro256::seed_from_u64(7);
for _ in 0..64 {
let hi = (a.next_u64() >> 32) as u32;
assert_eq!(hi, b.next_u32());
}
}
#[test]
fn next_f64_bounds() {
let mut rng = Xoshiro256::seed_from_u64(1);
for _ in 0..10_000 {
let f = rng.next_f64();
assert!((0.0..1.0).contains(&f));
}
}
#[test]
fn fill_bytes_exact_chunks() {
let mut a = Xoshiro256::seed_from_u64(123);
let mut b = Xoshiro256::seed_from_u64(123);
let mut buf = [0u8; 64];
a.fill_bytes(&mut buf);
for chunk in buf.chunks_exact(8) {
let expected = b.next_u64().to_le_bytes();
assert_eq!(chunk, expected);
}
}
#[test]
fn fill_bytes_partial_tail() {
let mut rng = Xoshiro256::seed_from_u64(2026);
let mut buf = [0u8; 13];
rng.fill_bytes(&mut buf);
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn jump_produces_different_state() {
let mut a = Xoshiro256::seed_from_u64(1);
let mut b = a.clone();
b.jump();
assert_ne!(a.state(), b.state());
assert_ne!(a.next_u64(), b.next_u64());
}
#[test]
fn long_jump_produces_different_state() {
let mut a = Xoshiro256::seed_from_u64(1);
let mut b = a.clone();
b.long_jump();
assert_ne!(a.state(), b.state());
assert_ne!(a.next_u64(), b.next_u64());
}
#[test]
fn jump_is_deterministic() {
let mut a = Xoshiro256::seed_from_u64(5);
let mut b = Xoshiro256::seed_from_u64(5);
a.jump();
b.jump();
assert_eq!(a.state(), b.state());
}
}