Skip to main content

steel_utils/random/
xoroshiro.rs

1use crate::random::{
2    PositionalRandom, Random, RandomSource, RandomSplitter, gaussian::MarsagliaPolarGaussian,
3    get_seed, name_hash::NameHash,
4};
5
6// Ratios used in the mix functions
7const GOLDEN_RATIO_64: u64 = 0x9E37_79B9_7F4A_7C15;
8const SILVER_RATIO_64: u64 = 0x6A09_E667_F3BC_C909;
9
10/// A Xoroshiro128++ random number generator.
11pub struct Xoroshiro {
12    seed_lo: u64,
13    seed_hi: u64,
14    next_gaussian: Option<f64>,
15}
16
17/// A splitter for the Xoroshiro128++ random number generator.
18#[derive(Clone)]
19pub struct XoroshiroSplitter {
20    seed_lo: u64,
21    seed_hi: u64,
22}
23
24impl Xoroshiro {
25    /// Creates a new `Xoroshiro` from a seed.
26    #[must_use]
27    pub const fn from_seed(seed: u64) -> Self {
28        // From RandomSupport
29        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    /// Creates a new `Xoroshiro` from a seed without mixing.
36    #[must_use]
37    pub const fn from_seed_unmixed(seed: u64) -> Self {
38        // From RandomSupport and
39        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    /// Resets this random source to vanilla's `XoroshiroRandomSource.setSeed(long)` state.
77    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    // Values checked against results from the equivalent Java source
185
186    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}