Skip to main content

rl_lang/interpreter/stdlib/random/
xoshiro.rs

1//! Xoshiro256** pseudo-random number generator.
2//!
3//! A fast, high-quality 256-bit state PRNG. Seeded from [`SystemTime`] at construction.
4//! The seed is spread across 4 `u64` state words using the finalizer from
5//! `splitmix64` (golden ratio + two mixing steps).
6
7pub struct Xoshiro256 {
8    /// The 256-bit PRNG state as four `u64` words.
9    state: [u64; 4],
10}
11
12impl Default for Xoshiro256 {
13    fn default() -> Self {
14        Self::new()
15    }
16}
17
18impl Xoshiro256 {
19    fn new() -> Self {
20        let seed = std::time::SystemTime::now()
21            .duration_since(std::time::UNIX_EPOCH)
22            .map(|d| d.as_nanos() as u64)
23            .unwrap_or(12345);
24
25        // spread the seed into 4 u64
26        let mut state = [0u64; 4];
27        let mut x = seed;
28        for i in &mut state {
29            // golden ratio * 2^64
30            x = x.wrapping_add(0x9e3779b97f4a7c15);
31            // magic constants
32            x = (x ^ (x >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
33            x = (x ^ (x >> 27)).wrapping_mul(0x94d049bb133111eb);
34            *i = x ^ (x >> 31);
35        }
36
37        Self { state }
38    }
39
40    /// Advances the state and returns the next raw `u64` output.
41    fn next(&mut self) -> u64 {
42        // compute result before scrambling the state
43        let result = (self.state[0].wrapping_add(self.state[3]))
44            .rotate_left(23)
45            .wrapping_add(self.state[0]);
46
47        // scramble the state
48        let temp = self.state[1] << 17;
49        self.state[2] ^= self.state[0];
50        self.state[3] ^= self.state[1];
51        self.state[1] ^= self.state[2];
52        self.state[0] ^= self.state[3];
53        self.state[2] ^= temp;
54        self.state[3] = self.state[3].rotate_left(45);
55        result
56    }
57
58    /// Returns a random `i64` in `[min, max]` inclusive.
59    pub fn generate_random_int_range(&mut self, min: i64, max: i64) -> i64 {
60        let range = (max as i128 - min as i128 + 1) as u128;
61        let offset = (self.next() as u128) % range;
62        (min as i128 + offset as i128) as i64
63    }
64
65    /// Returns a random `f64` in `[0.0, 1.0)`.
66    pub fn generate_random_float(&mut self) -> f64 {
67        self.next() as f64 / u64::MAX as f64
68    }
69
70    /// Returns a random `f64` in `[min, max)`.
71    pub fn generate_random_float_range(&mut self, min: f64, max: f64) -> f64 {
72        min + self.generate_random_float() * (max - min)
73    }
74
75    /// Returns `true` with probability `weight` (clamped to `[0.0, 1.0]`).
76    pub fn generate_random_bool(&mut self, weight: f64) -> bool {
77        self.generate_random_float() < weight.clamp(0.0, 1.0)
78    }
79}