ferray_random/bitgen/
xoshiro256.rs1#![allow(clippy::unreadable_literal)]
10
11use super::BitGenerator;
12
13pub struct Xoshiro256StarStar {
28 s: [u64; 4],
29}
30
31impl Xoshiro256StarStar {
32 fn from_state(s: [u64; 4]) -> Self {
34 debug_assert!(
35 s != [0, 0, 0, 0],
36 "Xoshiro256** state must not be all zeros"
37 );
38 Self { s }
39 }
40}
41
42impl BitGenerator for Xoshiro256StarStar {
43 fn state_bytes(&self) -> Result<Vec<u8>, ferray_core::FerrayError> {
44 let mut out = Vec::with_capacity(32);
45 for &w in &self.s {
46 out.extend_from_slice(&w.to_le_bytes());
47 }
48 Ok(out)
49 }
50
51 fn set_state_bytes(&mut self, bytes: &[u8]) -> Result<(), ferray_core::FerrayError> {
52 if bytes.len() != 32 {
53 return Err(ferray_core::FerrayError::invalid_value(format!(
54 "Xoshiro256** state must be 32 bytes, got {}",
55 bytes.len()
56 )));
57 }
58 let mut s = [0u64; 4];
59 for (i, chunk) in bytes.chunks_exact(8).enumerate() {
60 s[i] = u64::from_le_bytes(chunk.try_into().unwrap());
61 }
62 if s == [0, 0, 0, 0] {
63 return Err(ferray_core::FerrayError::invalid_value(
64 "Xoshiro256** state must not be all zeros",
65 ));
66 }
67 self.s = s;
68 Ok(())
69 }
70
71 fn next_u64(&mut self) -> u64 {
72 let result = (self.s[1].wrapping_mul(5)).rotate_left(7).wrapping_mul(9);
73 let t = self.s[1] << 17;
74
75 self.s[2] ^= self.s[0];
76 self.s[3] ^= self.s[1];
77 self.s[1] ^= self.s[2];
78 self.s[0] ^= self.s[3];
79
80 self.s[2] ^= t;
81 self.s[3] = self.s[3].rotate_left(45);
82
83 result
84 }
85
86 fn seed_from_u64(seed: u64) -> Self {
87 let mut sm = seed;
89 let s0 = super::splitmix64(&mut sm);
90 let s1 = super::splitmix64(&mut sm);
91 let s2 = super::splitmix64(&mut sm);
92 let s3 = super::splitmix64(&mut sm);
93 if s0 | s1 | s2 | s3 == 0 {
95 Self::from_state([1, 0, 0, 0])
96 } else {
97 Self::from_state([s0, s1, s2, s3])
98 }
99 }
100
101 fn jump(&mut self) -> Option<()> {
102 const JUMP: [u64; 4] = [
104 0x180ec6d33cfd0aba,
105 0xd5a61266f0c9392c,
106 0xa9582618e03fc9aa,
107 0x39abdc4529b1661c,
108 ];
109
110 let mut s0: u64 = 0;
111 let mut s1: u64 = 0;
112 let mut s2: u64 = 0;
113 let mut s3: u64 = 0;
114
115 for &jmp in &JUMP {
116 for b in 0..64 {
117 if (jmp >> b) & 1 != 0 {
118 s0 ^= self.s[0];
119 s1 ^= self.s[1];
120 s2 ^= self.s[2];
121 s3 ^= self.s[3];
122 }
123 self.next_u64();
124 }
125 }
126
127 self.s[0] = s0;
128 self.s[1] = s1;
129 self.s[2] = s2;
130 self.s[3] = s3;
131
132 Some(())
133 }
134
135 fn stream(_seed: u64, _stream_id: u64) -> Option<Self> {
136 None
138 }
139}
140
141impl Clone for Xoshiro256StarStar {
142 fn clone(&self) -> Self {
143 Self { s: self.s }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn deterministic_output() {
153 let mut rng1 = Xoshiro256StarStar::seed_from_u64(42);
154 let mut rng2 = Xoshiro256StarStar::seed_from_u64(42);
155 for _ in 0..1000 {
156 assert_eq!(rng1.next_u64(), rng2.next_u64());
157 }
158 }
159
160 #[test]
161 fn different_seeds_differ() {
162 let mut rng1 = Xoshiro256StarStar::seed_from_u64(42);
163 let mut rng2 = Xoshiro256StarStar::seed_from_u64(43);
164 let mut same = true;
165 for _ in 0..100 {
166 if rng1.next_u64() != rng2.next_u64() {
167 same = false;
168 break;
169 }
170 }
171 assert!(!same);
172 }
173
174 #[test]
175 fn jump_advances_state() {
176 let mut rng = Xoshiro256StarStar::seed_from_u64(1234);
177 let before = rng.next_u64();
178 let mut rng2 = Xoshiro256StarStar::seed_from_u64(1234);
179 let _ = rng2.next_u64();
180 rng2.jump();
181 let after = rng2.next_u64();
182 assert_ne!(before, after);
184 }
185
186 #[test]
187 fn stream_not_supported() {
188 assert!(Xoshiro256StarStar::stream(42, 0).is_none());
189 }
190
191 #[test]
192 fn next_f64_range() {
193 let mut rng = Xoshiro256StarStar::seed_from_u64(999);
194 for _ in 0..10_000 {
195 let v = rng.next_f64();
196 assert!((0.0..1.0).contains(&v));
197 }
198 }
199}