Skip to main content

ferray_random/bitgen/
xoshiro256.rs

1// ferray-random: Xoshiro256** BitGenerator implementation
2//
3// Reference: David Blackman and Sebastiano Vigna, "Scrambled Linear
4// Pseudorandom Number Generators", ACM TOMS, 2021.
5// Period: 2^256 - 1. Jump: 2^128.
6
7use super::BitGenerator;
8
9/// Xoshiro256** pseudo-random number generator.
10///
11/// This is the default BitGenerator for ferray-random. It has a period of
12/// 2^256 - 1 and supports `jump()` for parallel generation (each jump
13/// advances the state by 2^128 steps).
14///
15/// # Example
16/// ```
17/// use ferray_random::bitgen::Xoshiro256StarStar;
18/// use ferray_random::bitgen::BitGenerator;
19///
20/// let mut rng = Xoshiro256StarStar::seed_from_u64(42);
21/// let val = rng.next_u64();
22/// ```
23pub struct Xoshiro256StarStar {
24    s: [u64; 4],
25}
26
27impl Xoshiro256StarStar {
28    /// Create from an explicit 4-element state. The state must not be all zeros.
29    fn from_state(s: [u64; 4]) -> Self {
30        debug_assert!(
31            s != [0, 0, 0, 0],
32            "Xoshiro256** state must not be all zeros"
33        );
34        Self { s }
35    }
36}
37
38/// SplitMix64 used for seed expansion — converts a single u64 seed into
39/// multiple high-quality u64 values for initializing the Xoshiro state.
40fn splitmix64(state: &mut u64) -> u64 {
41    *state = state.wrapping_add(0x9e3779b97f4a7c15);
42    let mut z = *state;
43    z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
44    z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
45    z ^ (z >> 31)
46}
47
48impl BitGenerator for Xoshiro256StarStar {
49    fn next_u64(&mut self) -> u64 {
50        let result = (self.s[1].wrapping_mul(5)).rotate_left(7).wrapping_mul(9);
51        let t = self.s[1] << 17;
52
53        self.s[2] ^= self.s[0];
54        self.s[3] ^= self.s[1];
55        self.s[1] ^= self.s[2];
56        self.s[0] ^= self.s[3];
57
58        self.s[2] ^= t;
59        self.s[3] = self.s[3].rotate_left(45);
60
61        result
62    }
63
64    fn seed_from_u64(seed: u64) -> Self {
65        let mut sm = seed;
66        let s0 = splitmix64(&mut sm);
67        let s1 = splitmix64(&mut sm);
68        let s2 = splitmix64(&mut sm);
69        let s3 = splitmix64(&mut sm);
70        // Ensure state is not all zeros
71        if s0 | s1 | s2 | s3 == 0 {
72            Self::from_state([1, 0, 0, 0])
73        } else {
74            Self::from_state([s0, s1, s2, s3])
75        }
76    }
77
78    fn jump(&mut self) -> Option<()> {
79        // Jump polynomial for 2^128 steps
80        const JUMP: [u64; 4] = [
81            0x180ec6d33cfd0aba,
82            0xd5a61266f0c9392c,
83            0xa9582618e03fc9aa,
84            0x39abdc4529b1661c,
85        ];
86
87        let mut s0: u64 = 0;
88        let mut s1: u64 = 0;
89        let mut s2: u64 = 0;
90        let mut s3: u64 = 0;
91
92        for &jmp in &JUMP {
93            for b in 0..64 {
94                if (jmp >> b) & 1 != 0 {
95                    s0 ^= self.s[0];
96                    s1 ^= self.s[1];
97                    s2 ^= self.s[2];
98                    s3 ^= self.s[3];
99                }
100                self.next_u64();
101            }
102        }
103
104        self.s[0] = s0;
105        self.s[1] = s1;
106        self.s[2] = s2;
107        self.s[3] = s3;
108
109        Some(())
110    }
111
112    fn stream(_seed: u64, _stream_id: u64) -> Option<Self> {
113        // Xoshiro256** does not support stream IDs; use jump() instead
114        None
115    }
116}
117
118impl Clone for Xoshiro256StarStar {
119    fn clone(&self) -> Self {
120        Self { s: self.s }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn deterministic_output() {
130        let mut rng1 = Xoshiro256StarStar::seed_from_u64(42);
131        let mut rng2 = Xoshiro256StarStar::seed_from_u64(42);
132        for _ in 0..1000 {
133            assert_eq!(rng1.next_u64(), rng2.next_u64());
134        }
135    }
136
137    #[test]
138    fn different_seeds_differ() {
139        let mut rng1 = Xoshiro256StarStar::seed_from_u64(42);
140        let mut rng2 = Xoshiro256StarStar::seed_from_u64(43);
141        let mut same = true;
142        for _ in 0..100 {
143            if rng1.next_u64() != rng2.next_u64() {
144                same = false;
145                break;
146            }
147        }
148        assert!(!same);
149    }
150
151    #[test]
152    fn jump_advances_state() {
153        let mut rng = Xoshiro256StarStar::seed_from_u64(1234);
154        let before = rng.next_u64();
155        let mut rng2 = Xoshiro256StarStar::seed_from_u64(1234);
156        let _ = rng2.next_u64();
157        rng2.jump();
158        let after = rng2.next_u64();
159        // After jump, output should differ
160        assert_ne!(before, after);
161    }
162
163    #[test]
164    fn stream_not_supported() {
165        assert!(Xoshiro256StarStar::stream(42, 0).is_none());
166    }
167
168    #[test]
169    fn next_f64_range() {
170        let mut rng = Xoshiro256StarStar::seed_from_u64(999);
171        for _ in 0..10_000 {
172            let v = rng.next_f64();
173            assert!((0.0..1.0).contains(&v));
174        }
175    }
176}