1use crate::random::{
2 PositionalRandom, Random, RandomSource, RandomSplitter, gaussian::MarsagliaPolarGaussian,
3 get_seed, name_hash::NameHash,
4};
5
6const GOLDEN_RATIO_64: u64 = 0x9E37_79B9_7F4A_7C15;
8const SILVER_RATIO_64: u64 = 0x6A09_E667_F3BC_C909;
9
10pub struct Xoroshiro {
12 seed_lo: u64,
13 seed_hi: u64,
14 next_gaussian: f64,
15}
16
17#[derive(Clone)]
19pub struct XoroshiroSplitter {
20 seed_lo: u64,
21 seed_hi: u64,
22}
23
24impl Xoroshiro {
25 #[must_use]
27 pub const fn from_seed(seed: u64) -> Self {
28 let (lo, hi) = Self::upgrade_seed_to_128_bit(seed);
30 let lo = mix_stafford_13(lo);
31 let hi = mix_stafford_13(hi);
32 Self::new(lo, hi)
33 }
34
35 #[must_use]
37 pub const fn from_seed_unmixed(seed: u64) -> Self {
38 let (lo, hi) = Self::upgrade_seed_to_128_bit(seed);
40 Self::new(lo, hi)
41 }
42
43 const fn new(lo: u64, hi: u64) -> Self {
44 let (lo, hi) = if (lo | hi) == 0 {
45 (GOLDEN_RATIO_64, SILVER_RATIO_64)
46 } else {
47 (lo, hi)
48 };
49 Self {
50 seed_lo: lo,
51 seed_hi: hi,
52 next_gaussian: f64::NAN,
53 }
54 }
55
56 const fn upgrade_seed_to_128_bit(seed: u64) -> (u64, u64) {
57 let lo = seed ^ SILVER_RATIO_64;
58 let hi = lo.wrapping_add(GOLDEN_RATIO_64);
59 (lo, hi)
60 }
61
62 const fn next(&mut self, bits: u64) -> u64 {
63 self.next_random() >> (64 - bits)
64 }
65
66 const fn next_random(&mut self) -> u64 {
67 let l = self.seed_lo;
68 let m = self.seed_hi;
69 let n = l.wrapping_add(m).rotate_left(17).wrapping_add(l);
70 let m = m ^ l;
71 self.seed_lo = l.rotate_left(49) ^ m ^ (m << 21);
72 self.seed_hi = m.rotate_left(28);
73 n
74 }
75
76 pub const fn set_seed(&mut self, seed: i64) {
78 *self = Self::from_seed(seed as u64);
79 }
80}
81
82impl MarsagliaPolarGaussian for Xoroshiro {
83 fn stored_next_gaussian(&self) -> Option<f64> {
84 if self.next_gaussian.is_nan() {
85 None
86 } else {
87 Some(self.next_gaussian)
88 }
89 }
90
91 fn set_stored_next_gaussian(&mut self, value: Option<f64>) {
92 self.next_gaussian = value.unwrap_or(f64::NAN);
93 }
94}
95
96const fn mix_stafford_13(z: u64) -> u64 {
97 let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
98 let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
99 z ^ (z >> 31)
100}
101
102impl Random for Xoroshiro {
103 fn fork(&mut self) -> Self {
104 Self::new(self.next_random(), self.next_random())
105 }
106
107 fn next_i32(&mut self) -> i32 {
108 self.next_random() as i32
109 }
110
111 fn next_i32_bounded(&mut self, bound: i32) -> i32 {
112 let mut l = (self.next_i32() as u64) & 0xFFFF_FFFF;
113 let mut m = l.wrapping_mul(bound as u64);
114 let mut n = m & 0xFFFF_FFFF;
115 if n < bound as u64 {
116 let i = u64::from(((!bound as u32).wrapping_add(1)) % bound as u32);
117 while n < i {
118 l = (self.next_i32() as u64) & 0xFFFF_FFFF;
119 m = l.wrapping_mul(bound as u64);
120 n = m & 0xFFFF_FFFF;
121 }
122 }
123 let o = m >> 32;
124 o as i32
125 }
126
127 fn next_i64(&mut self) -> i64 {
128 self.next_random() as i64
129 }
130
131 fn next_f32(&mut self) -> f32 {
132 self.next(24) as f32 * 5.960_464_5e-8
133 }
134
135 fn next_f64(&mut self) -> f64 {
136 self.next(53) as f64 * f64::from(1.110_223e-16_f32)
137 }
138
139 fn next_bool(&mut self) -> bool {
140 (self.next_random() & 1) != 0
141 }
142
143 fn next_gaussian(&mut self) -> f64 {
144 self.calculate_gaussian()
145 }
146
147 fn next_positional(&mut self) -> RandomSplitter {
148 RandomSplitter::Xoroshiro(XoroshiroSplitter {
149 seed_lo: self.next_random(),
150 seed_hi: self.next_random(),
151 })
152 }
153}
154
155impl PositionalRandom for XoroshiroSplitter {
156 #[expect(
157 clippy::many_single_char_names,
158 reason = "matches vanilla's positional seeding math notation"
159 )]
160 fn at(&self, x: i32, y: i32, z: i32) -> RandomSource {
161 let l = get_seed(x, y, z) as u64;
162 let m = l ^ self.seed_lo;
163
164 RandomSource::Xoroshiro(Xoroshiro::new(m, self.seed_hi))
165 }
166
167 fn with_hash_of(&self, hash: &NameHash) -> RandomSource {
168 let [l, m] = hash.md5;
169 RandomSource::Xoroshiro(Xoroshiro::new(l ^ self.seed_lo, m ^ self.seed_hi))
170 }
171
172 fn with_seed(&self, seed: u64) -> RandomSource {
173 RandomSource::Xoroshiro(Xoroshiro::new(seed ^ self.seed_lo, seed ^ self.seed_hi))
174 }
175}
176
177#[cfg(test)]
178#[expect(
179 clippy::unreadable_literal,
180 clippy::cast_sign_loss,
181 clippy::float_cmp,
182 reason = "test vectors from vanilla Java; raw literals and casts are intentional"
183)]
184mod tests {
185 use super::*;
186 use crate::random::{PositionalRandom, Random};
187
188 const MIX_STAFFORD_13_TEST_CASES: &[(u64, i64)] = &[
191 (0, 0),
192 (1, 6238072747940578789),
193 (64, -8456553050427055661),
194 (4096, -1125827887270283392),
195 (262144, -120227641678947436),
196 (16777216, 6406066033425044679),
197 (1073741824, 3143522559155490559),
198 (16, -2773008118984693571),
199 (1024, 8101005175654470197),
200 (65536, -3551754741763842827),
201 (4194304, -2737109459693184599),
202 (2, -2606959012126976886),
203 (128, -5825874238589581082),
204 (8192, 1111983794319025228),
205 (524288, -7964047577924347155),
206 (33554432, -5634612006859462257),
207 (2147483648, -1436547171018572641),
208 (137438953472, -4514638798598940860),
209 (8796093022208, -610572083552328405),
210 (562949953421312, -263574021372026223),
211 (36028797018963968, 7868130499179604987),
212 (253, -4045451768301188906),
213 (127, -6873224393826578139),
214 (8447, 6670985465942597767),
215 (524543, -6228499289678716485),
216 (33554687, 2630391896919662492),
217 (2147483903, -6879633228472053040),
218 (137438953727, -5817997684975131823),
219 (8796093022463, 2384436581894988729),
220 (562949953421567, -5076179956679497213),
221 (36028797018964223, -5993365784811617721),
222 ];
223
224 #[test]
225 fn test_mix_stafford_13() {
226 for &(input, expected) in MIX_STAFFORD_13_TEST_CASES {
227 assert_eq!(
228 mix_stafford_13(input),
229 expected as u64,
230 "mix_stafford_13({input}) failed"
231 );
232 }
233 }
234
235 #[test]
236 fn next_i32_matches_java() {
237 const EXPECTED: [i32; 10] = [
238 -160476802,
239 781697906,
240 653572596,
241 1337520923,
242 -505875771,
243 -47281585,
244 342195906,
245 1417498593,
246 -1478887443,
247 1560080270,
248 ];
249
250 let mut rng = Xoroshiro::from_seed(0);
251 for &expected in &EXPECTED {
252 assert_eq!(rng.next_i32(), expected);
253 }
254 }
255
256 #[test]
257 fn next_i32_bounded() {
258 const SMALL_EXPECTED: [i32; 10] = [9, 1, 1, 3, 8, 9, 0, 3, 6, 3];
259 const LARGE_EXPECTED: [i32; 10] = [
260 9784805, 470346, 13560642, 7320226, 14949645, 13460529, 2824352, 10938308, 14146127,
261 4549185,
262 ];
263 let mut rng = Xoroshiro::from_seed(0);
264
265 for &expected in &SMALL_EXPECTED {
266 assert_eq!(rng.next_i32_bounded(10), expected);
267 }
268
269 for &expected in &LARGE_EXPECTED {
270 assert_eq!(rng.next_i32_bounded(0xFF_FFFF), expected);
271 }
272 }
273
274 #[test]
275 fn next_i32_between_inclusive() {
276 const EXPECTED: [i32; 10] = [99, 59, 57, 65, 94, 100, 54, 66, 83, 68];
277
278 let mut rng = Xoroshiro::from_seed(0);
279 for &expected in &EXPECTED {
280 assert_eq!(rng.next_i32_between(50, 100), expected);
281 }
282 }
283
284 #[test]
285 fn next_i32_between_exclusive() {
286 const EXPECTED: [i32; 10] = [98, 59, 57, 65, 94, 99, 53, 66, 82, 68];
287
288 let mut rng = Xoroshiro::from_seed(0);
289 for &expected in &EXPECTED {
290 assert_eq!(rng.next_i32_between_exclusive(50, 100), expected);
291 }
292 }
293
294 #[test]
295 fn next_f64_matches_java() {
296 const EXPECTED: [f64; 10] = [
297 0.16474369376959186,
298 0.7997457290026366,
299 0.2511961888876212,
300 0.11712489470639631,
301 0.0997124786680137,
302 0.7566797430601416,
303 0.7723285712021574,
304 0.9420469457586381,
305 0.48056202536813664,
306 0.6099690583914598,
307 ];
308
309 let mut rng = Xoroshiro::from_seed(0);
310 for &expected in &EXPECTED {
311 assert_eq!(rng.next_f64(), expected);
312 }
313 }
314
315 #[test]
316 fn next_f32_matches_java() {
317 const EXPECTED: [f32; 10] = [
318 0.16474366,
319 0.7997457,
320 0.25119615,
321 0.117124856,
322 0.09971243,
323 0.7566797,
324 0.77232856,
325 0.94204694,
326 0.48056197,
327 0.609969,
328 ];
329
330 let mut rng = Xoroshiro::from_seed(0);
331 for &expected in &EXPECTED {
332 assert_eq!(rng.next_f32(), expected);
333 }
334 }
335
336 #[test]
337 fn next_i64_matches_java() {
338 const EXPECTED: [i64; 10] = [
339 3038984756725240190,
340 -3694039286755638414,
341 4633751808701151732,
342 2160572957309072155,
343 1839370574944072389,
344 -4488466507718817201,
345 -4199796579929588030,
346 -1069045159880208415,
347 8864804693509535725,
348 -7194800960680693874,
349 ];
350
351 let mut rng = Xoroshiro::from_seed(0);
352 for &expected in &EXPECTED {
353 assert_eq!(rng.next_i64(), expected);
354 }
355 }
356
357 #[test]
358 fn next_bool_matches_java() {
359 const EXPECTED: [bool; 10] = [
360 false, false, false, true, true, true, false, true, true, false,
361 ];
362
363 let mut rng = Xoroshiro::from_seed(0);
364 for &expected in &EXPECTED {
365 assert_eq!(rng.next_bool(), expected);
366 }
367 }
368
369 #[test]
370 fn next_gaussian_matches_java() {
371 const EXPECTED: [f64; 10] = [
372 -0.48540690699780015,
373 0.43399227545320296,
374 -0.3283265251019599,
375 -0.5052497078202575,
376 -0.3772512828630807,
377 0.2419080215945433,
378 -0.42622066207565135,
379 2.411315261138953,
380 -1.1419147030553274,
381 -0.05849758093810378,
382 ];
383
384 let mut rng = Xoroshiro::from_seed(0);
385 for &expected in &EXPECTED {
386 assert_eq!(rng.next_gaussian(), expected);
387 }
388 }
389
390 #[test]
391 fn triangle_matches_java() {
392 const EXPECTED: [f64; 10] = [
393 6.824989823834776,
394 10.670356470906125,
395 6.71516367803936,
396 9.151408127217596,
397 9.352964834883384,
398 8.291618967842293,
399 8.954549938640508,
400 11.833001837470519,
401 10.65851306020791,
402 11.684676364031647,
403 ];
404
405 let mut rng = Xoroshiro::from_seed(0);
406 for &expected in &EXPECTED {
407 assert_eq!(rng.triangle(10.0, 5.0), expected);
408 }
409 }
410
411 #[test]
412 fn fork_creates_independent_rng() {
413 let mut rng = Xoroshiro::from_seed(0);
414 let mut forked = rng.fork();
415
416 assert_eq!(forked.next_i32(), 542195535);
417 assert_eq!(rng.next_i32(), 653572596);
418 }
419
420 #[test]
421 fn positional_random_splitter() {
422 let mut rng = Xoroshiro::from_seed(0);
423 let mut forked = rng.fork();
424
425 assert_eq!(forked.next_i32(), 542195535);
426
427 let splitter = forked.next_positional();
428
429 let RandomSource::Xoroshiro(mut rand1) =
430 splitter.with_hash_of(&NameHash::new("TEST STRING"))
431 else {
432 panic!("Expected Xoroshiro variant");
433 };
434 assert_eq!(rand1.next_i32(), -641435713);
435
436 let RandomSource::Xoroshiro(mut rand2) = splitter.with_seed(42069) else {
437 panic!("Expected Xoroshiro variant");
438 };
439 assert_eq!(rand2.next_i32(), -340700677);
440
441 let RandomSource::Xoroshiro(mut rand3) = splitter.at(1337, 80085, -69420) else {
442 panic!("Expected Xoroshiro variant");
443 };
444 assert_eq!(rand3.next_i32(), 790449132);
445
446 assert_eq!(rng.next_i32(), 653572596);
447 assert_eq!(forked.next_i32(), 435917842);
448 }
449
450 #[test]
451 fn zero_seed_produces_fallback_values() {
452 let mut rng = Xoroshiro::new(0, 0);
453 assert_eq!(rng.next_i64(), 6807859099481836695);
454 }
455}