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
38impl BitGenerator for Xoshiro256StarStar {
39    fn next_u64(&mut self) -> u64 {
40        let result = (self.s[1].wrapping_mul(5)).rotate_left(7).wrapping_mul(9);
41        let t = self.s[1] << 17;
42
43        self.s[2] ^= self.s[0];
44        self.s[3] ^= self.s[1];
45        self.s[1] ^= self.s[2];
46        self.s[0] ^= self.s[3];
47
48        self.s[2] ^= t;
49        self.s[3] = self.s[3].rotate_left(45);
50
51        result
52    }
53
54    fn seed_from_u64(seed: u64) -> Self {
55        // Use the shared splitmix64 helper (#259).
56        let mut sm = seed;
57        let s0 = super::splitmix64(&mut sm);
58        let s1 = super::splitmix64(&mut sm);
59        let s2 = super::splitmix64(&mut sm);
60        let s3 = super::splitmix64(&mut sm);
61        // Ensure state is not all zeros
62        if s0 | s1 | s2 | s3 == 0 {
63            Self::from_state([1, 0, 0, 0])
64        } else {
65            Self::from_state([s0, s1, s2, s3])
66        }
67    }
68
69    fn jump(&mut self) -> Option<()> {
70        // Jump polynomial for 2^128 steps
71        const JUMP: [u64; 4] = [
72            0x180ec6d33cfd0aba,
73            0xd5a61266f0c9392c,
74            0xa9582618e03fc9aa,
75            0x39abdc4529b1661c,
76        ];
77
78        let mut s0: u64 = 0;
79        let mut s1: u64 = 0;
80        let mut s2: u64 = 0;
81        let mut s3: u64 = 0;
82
83        for &jmp in &JUMP {
84            for b in 0..64 {
85                if (jmp >> b) & 1 != 0 {
86                    s0 ^= self.s[0];
87                    s1 ^= self.s[1];
88                    s2 ^= self.s[2];
89                    s3 ^= self.s[3];
90                }
91                self.next_u64();
92            }
93        }
94
95        self.s[0] = s0;
96        self.s[1] = s1;
97        self.s[2] = s2;
98        self.s[3] = s3;
99
100        Some(())
101    }
102
103    fn stream(_seed: u64, _stream_id: u64) -> Option<Self> {
104        // Xoshiro256** does not support stream IDs; use jump() instead
105        None
106    }
107}
108
109impl Clone for Xoshiro256StarStar {
110    fn clone(&self) -> Self {
111        Self { s: self.s }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn deterministic_output() {
121        let mut rng1 = Xoshiro256StarStar::seed_from_u64(42);
122        let mut rng2 = Xoshiro256StarStar::seed_from_u64(42);
123        for _ in 0..1000 {
124            assert_eq!(rng1.next_u64(), rng2.next_u64());
125        }
126    }
127
128    #[test]
129    fn different_seeds_differ() {
130        let mut rng1 = Xoshiro256StarStar::seed_from_u64(42);
131        let mut rng2 = Xoshiro256StarStar::seed_from_u64(43);
132        let mut same = true;
133        for _ in 0..100 {
134            if rng1.next_u64() != rng2.next_u64() {
135                same = false;
136                break;
137            }
138        }
139        assert!(!same);
140    }
141
142    #[test]
143    fn jump_advances_state() {
144        let mut rng = Xoshiro256StarStar::seed_from_u64(1234);
145        let before = rng.next_u64();
146        let mut rng2 = Xoshiro256StarStar::seed_from_u64(1234);
147        let _ = rng2.next_u64();
148        rng2.jump();
149        let after = rng2.next_u64();
150        // After jump, output should differ
151        assert_ne!(before, after);
152    }
153
154    #[test]
155    fn stream_not_supported() {
156        assert!(Xoshiro256StarStar::stream(42, 0).is_none());
157    }
158
159    #[test]
160    fn next_f64_range() {
161        let mut rng = Xoshiro256StarStar::seed_from_u64(999);
162        for _ in 0..10_000 {
163            let v = rng.next_f64();
164            assert!((0.0..1.0).contains(&v));
165        }
166    }
167}