amadeus_utils/
exsss.rs

1/// Erlang :exsss (Xorshift116**) PRNG implementation
2///
3/// This provides exact compatibility with Erlang's :rand.seed(:exsss, seed) for
4/// deterministic shuffling in consensus-critical code.
5///
6/// Algorithm: Xorshift116** - a scrambled linear generator with:
7/// - 58 bits precision
8/// - Period of 2^116-1
9/// - Two 64-bit state values (s0, s1)
10/// - Output scrambling via (S*5 rotl 7)*9
11
12/// Xorshift116** state (matches Erlang :exsss)
13#[derive(Debug, Clone, Copy)]
14pub struct Exsss {
15    s0: u64,
16    s1: u64,
17}
18
19impl Exsss {
20    /// Create new generator from 256-bit seed (little-endian)
21    /// Matches Erlang: :rand.seed(:exsss, seed) where seed is <<...::256-little>>
22    pub fn from_seed(seed_bytes: &[u8; 32]) -> Self {
23        // extract integer seed from first bytes (matches Erlang <<seed::256-little>>)
24        let seed = u128::from_le_bytes(seed_bytes[0..16].try_into().unwrap());
25
26        Self::from_seed_u128(seed)
27    }
28
29    /// Create from integer seed (matches Erlang :rand.seed(:exsss, integer))
30    pub fn from_seed_u128(seed: u128) -> Self {
31        // Erlang's seed58(2, X) implementation
32        let (s0, x1) = seed58(seed as u64);
33        let (s1, _x2) = seed58(x1);
34
35        Self { s0, s1 }
36    }
37
38    /// Generate next random u64 value (internal, 58-bit precision)
39    /// Matches Erlang :rand.exsss_next
40    fn next_u64(&mut self) -> u64 {
41        const MASK_58: u64 = (1u64 << 58) - 1;
42
43        // Erlang state format: [S1|S0] (improper list: head=S1, tail=S0)
44        // Our struct stores (s0, s1) matching the seed58 output order
45        // exsss_next([S1|S0]) parameter: S1=head, S0=tail
46        // So when calling with our state: S1=self.s0 (first seed), S0=self.s1 (second seed)
47        let s1 = self.s0;
48        let s0 = self.s1;
49
50        // S0_1 = S0 & MASK_58
51        let s0_1 = s0 & MASK_58;
52
53        // exs_next(S0_1, S1, S1_b):
54        // S1_b = (S1 & MASK_58) ^ ((S1 << 24) & MASK_58)
55        // NewS1 = S1_b ^ S0_1 ^ (S1_b >> 11) ^ (S0_1 >> 41)
56        let s1_masked = s1 & MASK_58;
57        let s1_b = s1_masked ^ ((s1_masked << 24) & MASK_58);
58        let new_s1 = s1_b ^ s0_1 ^ (s1_b >> 11) ^ (s0_1 >> 41);
59
60        // scramble_starstar(S0_1, ...):
61        // V_a = S0_1 + (S0_1 << 2) mod 2^58  // S0_1 * 5
62        // V_b = rotl(V_a, 7) in 58-bit space
63        // Output = (V_b + (V_b << 3)) mod 2^58  // V_b * 9
64        let v_a = (s0_1 + ((s0_1 << 2) & MASK_58)) & MASK_58;
65        let v_b = ((v_a << 7) | (v_a >> 51)) & MASK_58;
66        let output = (v_b + ((v_b << 3) & MASK_58)) & MASK_58;
67
68        // Returns state [S0_1|NewS1]
69        // Our struct: s0=S0_1 (new first), s1=NewS1 (new second)
70        self.s0 = s0_1;
71        self.s1 = new_s1 & MASK_58;
72
73        output
74    }
75
76    /// Generate random float in range [0.0, 1.0) (matches Erlang :rand.uniform())
77    pub fn uniform_float(&mut self) -> f64 {
78        // exsss_uniform: (I >> 5) * 2^-53
79        // where I is 58-bit output from next_u64
80        const TWO_POW_MINUS53: f64 = 1.0 / (1u64 << 53) as f64;
81
82        let i = self.next_u64();
83        let shifted = i >> 5; // 58 - 53 = 5
84        (shifted as f64) * TWO_POW_MINUS53
85    }
86
87    /// Generate random integer in range [1, n] (matches Erlang :rand.uniform(n))
88    pub fn uniform(&mut self, range: u64) -> u64 {
89        self.uniform_internal(range, 0)
90    }
91
92    fn uniform_internal(&mut self, range: u64, depth: u32) -> u64 {
93        const BIT_58: u64 = 1u64 << 58;
94
95        if range == 0 {
96            return 0;
97        }
98
99        let v = self.next_u64(); // already 58-bit
100
101        // Erlang checks: if 0 <= MaxMinusRange (i.e., Range <= BIT_58)
102        // For our use case, range is always small (1000), so this is always true
103
104        // fast path: if v < range, return v + 1
105        if v < range {
106            return v + 1;
107        }
108
109        // rejection sampling
110        let i = v % range;
111        let max_minus_range = BIT_58 - range;
112
113        if v - i <= max_minus_range {
114            i + 1
115        } else {
116            // v in truncated top range, retry
117            self.uniform_internal(range, depth + 1)
118        }
119    }
120
121    /// Shuffle a slice in place (matches Elixir Enum.shuffle)
122    /// Uses sort_by with random floats, not Fisher-Yates
123    pub fn shuffle<T: Clone>(&mut self, slice: &mut [T]) {
124        if slice.len() <= 1 {
125            return;
126        }
127
128        // Generate random key for each element with its value
129        let mut keyed: Vec<(f64, T)> = slice.iter().map(|val| (self.uniform_float(), val.clone())).collect();
130
131        // Sort by random keys
132        keyed.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
133
134        // Write sorted values back
135        for (idx, (_key, val)) in keyed.into_iter().enumerate() {
136            slice[idx] = val;
137        }
138    }
139}
140
141/// Splitmix64 next step (matches Erlang splitmix64_next)
142/// Returns (Z, NewX) where Z is the output and NewX is the updated state
143fn splitmix64_next(x0: u64) -> (u64, u64) {
144    let x = x0.wrapping_add(0x9e3779b97f4a7c15);
145    let mut z = x;
146    z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
147    z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
148    z = z ^ (z >> 31);
149    (z, x)
150}
151
152/// Seed58 (matches Erlang seed58/1)
153/// Returns (Z masked to 58 bits, NewX) skipping zero values
154fn seed58(x0: u64) -> (u64, u64) {
155    const MASK_58: u64 = (1u64 << 58) - 1;
156
157    let (z0, x) = splitmix64_next(x0);
158    let z = z0 & MASK_58;
159
160    if z == 0 {
161        // retry with new x
162        seed58(x)
163    } else {
164        (z, x)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_state_initialization() {
174        // Verify exact state initialization matches Erlang
175        let test_cases = vec![
176            (0u128, 153307352162749871u64, 178066366098138612u64),
177            (42, 132629853624823445, 67522330609774851),
178            (777, 132610673151668814, 220791266393211968),
179            (12345, 149043579997720992, 31205127689074925),
180            (54321, 144632915686665753, 52714770947718356),
181            (99999, 51811462204453670, 95920375662433499),
182            (123456789, 161132163074061945, 185172155811622446),
183        ];
184
185        for (seed, expected_s0, expected_s1) in test_cases {
186            let rng = Exsss::from_seed_u128(seed);
187            assert_eq!(rng.s0, expected_s0, "s0 mismatch for seed {}", seed);
188            assert_eq!(rng.s1, expected_s1, "s1 mismatch for seed {}", seed);
189        }
190    }
191
192    #[test]
193    fn test_seed_test_1() {
194        // Test 1 from Elixir IEx
195        let seed_bytes: [u8; 32] = [
196            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
197            30, 31, 32,
198        ];
199        let mut rng = Exsss::from_seed(&seed_bytes);
200
201        let r1 = rng.uniform(1000);
202        let r2 = rng.uniform(1000);
203        let r3 = rng.uniform(1000);
204
205        // Expected: 829, 169, 221
206        assert_eq!(r1, 829, "first uniform(1000)");
207        assert_eq!(r2, 169, "second uniform(1000)");
208        assert_eq!(r3, 221, "third uniform(1000)");
209    }
210
211    #[test]
212    fn test_shuffle_simple() {
213        // Test 2 from Elixir IEx
214        let seed_bytes = seed_from_u64(12345);
215        let mut rng = Exsss::from_seed(&seed_bytes);
216
217        let mut list = vec![1, 2, 3, 4, 5];
218        rng.shuffle(&mut list);
219
220        // Expected: [3, 4, 2, 1, 5]
221        assert_eq!(list, vec![3, 4, 2, 1, 5]);
222    }
223
224    #[test]
225    fn test_shuffle_zero_seed() {
226        // Test 3 from Elixir IEx
227        let seed_bytes = seed_from_u64(0);
228        let mut rng = Exsss::from_seed(&seed_bytes);
229
230        let mut list: Vec<u32> = (1..=10).collect();
231        rng.shuffle(&mut list);
232
233        // Expected: [5, 2, 1, 7, 9, 4, 8, 6, 10, 3]
234        assert_eq!(list, vec![5, 2, 1, 7, 9, 4, 8, 6, 10, 3]);
235    }
236
237    #[test]
238    fn test_random_sequence() {
239        // Test 5 from Elixir IEx
240        let seed_bytes = seed_from_u64(42);
241        let mut rng = Exsss::from_seed(&seed_bytes);
242
243        let mut randoms = Vec::new();
244        for _ in 0..20 {
245            randoms.push(rng.uniform(1000));
246        }
247
248        // Expected: [294, 431, 615, 198, 771, 458, 832, 264, 842, 111, 320, 936, 44, 92, 979, 44, 402, 648, 714, 722]
249        assert_eq!(
250            randoms,
251            vec![294, 431, 615, 198, 771, 458, 832, 264, 842, 111, 320, 936, 44, 92, 979, 44, 402, 648, 714, 722]
252        );
253    }
254
255    #[test]
256    fn test_shuffle_large() {
257        // Test 7 from Elixir IEx
258        let seed_bytes = seed_from_u64(54321);
259        let mut rng = Exsss::from_seed(&seed_bytes);
260
261        let mut list: Vec<u32> = (1..=99).collect();
262        rng.shuffle(&mut list);
263
264        // Expected first 50: [74, 86, 38, 82, 89, 10, 84, 26, 98, 85, 34, 91, 87, 51, 93, 45, 41, 30, 17, 96, 28, 6, 27, 78, 23, 25, 92, 18, 32, 39, 48, 22, 49, 1, 61, 9, 20, 95, 72, 79, 70, 33, 50, 42, 77, 73, 81, 24, 60, 88]
265        let expected_first_50 = vec![
266            74, 86, 38, 82, 89, 10, 84, 26, 98, 85, 34, 91, 87, 51, 93, 45, 41, 30, 17, 96, 28, 6, 27, 78, 23, 25, 92,
267            18, 32, 39, 48, 22, 49, 1, 61, 9, 20, 95, 72, 79, 70, 33, 50, 42, 77, 73, 81, 24, 60, 88,
268        ];
269        assert_eq!(list[0..50], expected_first_50[..]);
270    }
271
272    #[test]
273    fn test_determinism() {
274        // Test 8 from Elixir IEx
275        let seed_bytes = seed_from_u64(777);
276
277        let mut rng1 = Exsss::from_seed(&seed_bytes);
278        let mut list1 = vec![1, 2, 3, 4, 5, 6, 7, 8];
279        rng1.shuffle(&mut list1);
280
281        let mut rng2 = Exsss::from_seed(&seed_bytes);
282        let mut list2 = vec![1, 2, 3, 4, 5, 6, 7, 8];
283        rng2.shuffle(&mut list2);
284
285        // Expected: [2, 3, 6, 4, 1, 5, 7, 8]
286        assert_eq!(list1, vec![2, 3, 6, 4, 1, 5, 7, 8]);
287        assert_eq!(list1, list2, "determinism check");
288    }
289
290    /// Helper: create 32-byte seed from u64 (for simple integer seeds)
291    fn seed_from_u64(n: u64) -> [u8; 32] {
292        let mut bytes = [0u8; 32];
293        bytes[0..8].copy_from_slice(&n.to_le_bytes());
294        bytes
295    }
296}