irox_tools/
random.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! Pseudo-Random Number Generators (PRNGs)
7//!
8
9use core::ops::{BitXor, BitXorAssign};
10use irox_bits::MutBits;
11
12/// Default starting state/seed if the system clock fails
13const DEFAULT_STATE: u64 = 0x4d595df4d0f33173u64;
14
15/// incremental multiplier for each state
16const MULTIPLIER: u64 = 6364136223846793005u64;
17
18/// incremental incrementer for each state
19const INCREMENT: u64 = 1442695040888963407u64;
20
21const MULTIPLIER_128: u128 = 0x2360ED051FC65DA44385DF649FCCF645u128;
22const INCREMENT_128: u128 = 0x5851F42D4C957F2D14057B7EF767814Fu128;
23
24pub type Random = PcgXshRR;
25///
26/// Basic Random Number Generator based on the `PCG-XSH-RR`.  64 bit state, 32 bit output.
27pub struct PcgXshRR {
28    state: u64,
29}
30
31impl PcgXshRR {
32    ///
33    /// Creates a random seeded with this number.
34    pub fn new_seed(seed: u64) -> Self {
35        Self {
36            state: seed.wrapping_mul(2).wrapping_add(1),
37        }
38    }
39}
40
41impl PRNG for PcgXshRR {
42    ///
43    /// Gets the next random [`u32`] for this random sequence
44    fn next_u32(&mut self) -> u32 {
45        // standard PCG-XSH-RR
46        let count = (self.state >> 59) as u32;
47        let mut x = self.state;
48        self.state = x.wrapping_mul(MULTIPLIER).wrapping_add(INCREMENT);
49        x.bitxor_assign(x >> 18);
50        let x = (x >> 27) as u32;
51        x.rotate_right(count)
52    }
53}
54
55///
56/// `PCG-XSH-RS`, 32-bit output, 64-bit state - slightly better speed than `PCG-XSH-RR`, but with worse statistical
57/// properties.
58pub struct PcgXshRs {
59    state: u64,
60}
61
62impl PcgXshRs {
63    ///
64    /// Creates a random seeded with this number.
65    pub fn new_seed(seed: u64) -> Self {
66        Self {
67            state: seed.wrapping_mul(2).wrapping_add(1),
68        }
69    }
70}
71
72impl PRNG for PcgXshRs {
73    fn next_u32(&mut self) -> u32 {
74        let state = self.state;
75        self.state = state.wrapping_mul(MULTIPLIER).wrapping_add(INCREMENT);
76        let shift = 22 + (state >> 61);
77
78        (state.bitxor(state >> 22) >> shift) as u32
79    }
80}
81
82///
83/// `PCG-RXS-M-XS-64`, 64-bit output, 64-bit state - Insecure, but 2nd fastest after `PCG-XSL-RR-RR`
84pub struct PcgRxsMXs64 {
85    state: u64,
86}
87
88impl PcgRxsMXs64 {
89    ///
90    /// Creates a random seeded with this number.
91    pub fn new_seed(seed: u64) -> Self {
92        Self {
93            state: seed.wrapping_mul(2).wrapping_add(1),
94        }
95    }
96}
97
98impl PRNG for PcgRxsMXs64 {
99    fn next_u32(&mut self) -> u32 {
100        self.next_u64() as u32
101    }
102
103    fn next_u64(&mut self) -> u64 {
104        let state = self.state;
105        self.state = state.wrapping_mul(MULTIPLIER).wrapping_add(INCREMENT);
106        let word = ((state >> ((state >> 59).wrapping_add(5))) ^ state)
107            .wrapping_mul(12605985483714917081u64);
108        (word >> 43) ^ word
109    }
110}
111
112///
113/// `PCG-XSL-RR-RR`, 128-bit state, 128-bit output.  Fastest PRNG in the west.  Most insecure of them all.
114pub struct PcgXslRrRr {
115    state: u128,
116}
117impl PcgXslRrRr {
118    ///
119    /// Creates a random seeded with this number.
120    pub fn new_seed(seed: u128) -> Self {
121        Self {
122            state: seed.wrapping_mul(2).wrapping_add(1),
123        }
124    }
125}
126
127impl PRNG for PcgXslRrRr {
128    fn next_u32(&mut self) -> u32 {
129        self.next_u128() as u32
130    }
131
132    fn next_u128(&mut self) -> u128 {
133        let state = self.state;
134        self.state = state
135            .wrapping_mul(MULTIPLIER_128)
136            .wrapping_add(INCREMENT_128);
137        let rot1 = (state >> 122) as u32;
138        let high = (state >> 64) as u64;
139        let newlow = (high ^ state as u64).rotate_right(rot1);
140        let newhigh = high.rotate_right((newlow & 0x3F) as u32);
141        ((newhigh as u128) << 64) | newlow as u128
142    }
143}
144
145pub struct PcgMcgXslRr {
146    state: u128,
147}
148impl PcgMcgXslRr {
149    pub fn new_seed(seed: u128) -> Self {
150        Self {
151            state: seed.wrapping_mul(2).wrapping_add(1),
152        }
153    }
154}
155impl PRNG for PcgMcgXslRr {
156    fn next_u32(&mut self) -> u32 {
157        self.next_u64() as u32
158    }
159
160    fn next_u64(&mut self) -> u64 {
161        let state = self.state.wrapping_mul(MULTIPLIER_128);
162        self.state = state;
163        let rot = (state >> 122) as u32;
164        ((state >> 64) as u64)
165            .bitxor(state as u64)
166            .rotate_right(rot)
167    }
168}
169
170pub trait PRNG {
171    ///
172    /// Gets the next random [`u32`] for this random sequence
173    fn next_u32(&mut self) -> u32;
174    ///
175    /// Gets the next random [`u8`] for this random sequence
176    fn next_u8(&mut self) -> u8 {
177        self.next_u32() as u8
178    }
179    ///
180    /// Gets the next random [`u16`] for this random sequence
181    fn next_u16(&mut self) -> u16 {
182        self.next_u32() as u16
183    }
184    ///
185    /// Gets the next random [`u64`] for this random sequence
186    fn next_u64(&mut self) -> u64 {
187        let a: u64 = self.next_u32() as u64;
188        let b: u64 = self.next_u32() as u64;
189        (a << 32) | b
190    }
191    ///
192    /// Gets the next random [`u128`] for this random sequence
193    fn next_u128(&mut self) -> u128 {
194        let a: u128 = self.next_u64() as u128;
195        let b: u128 = self.next_u64() as u128;
196        (a << 64) | b
197    }
198    ///
199    /// Gets the next random [`f32`] for this random sequence
200    fn next_f32(&mut self) -> f32 {
201        f32::from_bits(self.next_u32())
202    }
203    ///
204    /// Gets the next random [`f64`] for this random sequence
205    fn next_f64(&mut self) -> f64 {
206        f64::from_bits(self.next_u64())
207    }
208
209    fn fill(&mut self, mut data: &mut [u8]) {
210        while !data.is_empty() {
211            let val = self.next_u64().to_ne_bytes();
212            for v in val {
213                if data.write_u8(v).is_err() {
214                    return;
215                }
216            }
217        }
218    }
219
220    ///
221    /// Gets a random number in the range `[0..=1]`
222    fn next_unit_f64(&mut self) -> f64 {
223        self.next_u64() as f64 / u64::MAX as f64
224    }
225    ///
226    /// Gets a random number in the range `[min..=max]`
227    fn next_in_range(&mut self, min: f64, max: f64) -> f64 {
228        let off = (max - min) * self.next_unit_f64();
229        min + off
230    }
231    ///
232    /// Gets a random number in the range `center +/- (range/2)`
233    fn next_in_distribution(&mut self, center: f64, range: f64) -> f64 {
234        let off = (self.next_unit_f64() - 0.5) * range;
235        center + off
236    }
237}
238
239#[cfg(feature = "std")]
240impl Default for Random {
241    fn default() -> Self {
242        let seed = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
243            Ok(e) => e.as_nanos() as u64,
244            Err(_) => DEFAULT_STATE,
245        };
246        Random::new_seed(seed)
247    }
248}
249#[cfg(not(feature = "std"))]
250impl Default for Random {
251    fn default() -> Self {
252        Random::new_seed(DEFAULT_STATE)
253    }
254}
255
256#[cfg(all(test, feature = "std"))]
257mod tests {
258    #![allow(clippy::all)]
259    use crate::random::{PcgRxsMXs64, PcgXslRrRr, PRNG};
260
261    // #[test]
262    // #[ignore]
263    pub fn speedtest_128() -> f64 {
264        let mut rand = PcgXslRrRr::new_seed(0);
265        let start = std::time::Instant::now();
266        let todo = 100_000_000;
267        core::hint::black_box({
268            let mut _v = 0;
269            for _i in 0..todo {
270                _v = rand.next_u128();
271                _v = rand.next_u128();
272                _v = rand.next_u128();
273                _v = rand.next_u128();
274                _v = rand.next_u128();
275                _v = rand.next_u128();
276                _v = rand.next_u128();
277                _v = rand.next_u128();
278            }
279        });
280        let elapsed = start.elapsed().as_secs_f64();
281        let did = todo as f64 * 128. / 1e6;
282        println!("Did {} MB/s", did / elapsed);
283        did
284    }
285    #[allow(unused)]
286    pub fn speedtest_64() -> f64 {
287        let mut rand = PcgRxsMXs64::new_seed(0);
288        let start = std::time::Instant::now();
289        let todo = 1_000_000;
290        core::hint::black_box({
291            let mut _v = 0;
292            for _i in 0..todo {
293                _v = rand.next_u64();
294                _v = rand.next_u64();
295                _v = rand.next_u64();
296                _v = rand.next_u64();
297                _v = rand.next_u64();
298                _v = rand.next_u64();
299                _v = rand.next_u64();
300                _v = rand.next_u64();
301            }
302        });
303        let elapsed = start.elapsed().as_secs_f64();
304        let did = todo as f64 * 64. / 1e6;
305        println!("Did {} MB/s", did / elapsed);
306        did
307    }
308
309    #[test]
310    #[ignore]
311    pub fn multi_speedtest() {
312        let core_ids = core_affinity::get_core_ids().unwrap_or_default();
313        let start = std::time::Instant::now();
314        let mut handles = core_ids
315            .into_iter()
316            .map(|id| {
317                std::thread::spawn(move || {
318                    let _val = core_affinity::set_for_current(id);
319                    speedtest_128()
320                })
321            })
322            .collect::<Vec<_>>();
323
324        let did: f64 = handles
325            .drain(..)
326            .map(|v| v.join().unwrap_or_default())
327            .sum();
328        let elapsed = start.elapsed().as_secs_f64();
329        println!("Did {} MB/s", did / elapsed);
330    }
331}