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