Skip to main content

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    /// Returns a non-negative random number in the range [0,max).  Care
239    /// is taken to avoid modulo bias.
240    fn unbiased_next_u32(&mut self, max: u32) -> u32 {
241        if max.is_power_of_two() {
242            return self.next_u32() & (max - 1);
243        }
244        let lim = (1 << 31) - 1 - (1 << 31) % max;
245        let mut v = self.next_u32();
246        while v > lim {
247            v = self.next_u32();
248        }
249        v % max
250    }
251    /// Returns a non-negative random number in the range [0,max).  Care
252    /// is taken to avoid modulo bias.
253    fn unbiased_next_u64(&mut self, max: u64) -> u64 {
254        if max.is_power_of_two() {
255            return self.next_u64() & (max - 1);
256        }
257        let lim = (1 << 63) - 1 - (1 << 63) % max;
258        let mut v = self.next_u64();
259        while v > lim {
260            v = self.next_u64();
261        }
262        v % max
263    }
264}
265
266#[cfg(feature = "std")]
267impl Default for Random {
268    fn default() -> Self {
269        let seed = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
270            Ok(e) => e.as_nanos() as u64,
271            Err(_) => DEFAULT_STATE,
272        };
273        Random::new_seed(seed)
274    }
275}
276#[cfg(not(feature = "std"))]
277impl Default for Random {
278    fn default() -> Self {
279        Random::new_seed(DEFAULT_STATE)
280    }
281}
282
283#[cfg(all(test, feature = "std"))]
284mod tests {
285    #![allow(clippy::all)]
286    use crate::random::{PcgRxsMXs64, PcgXslRrRr, PRNG};
287
288    // #[test]
289    // #[ignore]
290    pub fn speedtest_128() -> f64 {
291        let mut rand = PcgXslRrRr::new_seed(0);
292        let start = std::time::Instant::now();
293        let todo = 100_000_000;
294        core::hint::black_box({
295            let mut _v = 0;
296            for _i in 0..todo {
297                _v = rand.next_u128();
298                _v = rand.next_u128();
299                _v = rand.next_u128();
300                _v = rand.next_u128();
301                _v = rand.next_u128();
302                _v = rand.next_u128();
303                _v = rand.next_u128();
304                _v = rand.next_u128();
305            }
306        });
307        let elapsed = start.elapsed().as_secs_f64();
308        let did = todo as f64 * 128. / 1e6;
309        println!("Did {} MB/s", did / elapsed);
310        did
311    }
312    #[allow(unused)]
313    pub fn speedtest_64() -> f64 {
314        let mut rand = PcgRxsMXs64::new_seed(0);
315        let start = std::time::Instant::now();
316        let todo = 1_000_000;
317        core::hint::black_box({
318            let mut _v = 0;
319            for _i in 0..todo {
320                _v = rand.next_u64();
321                _v = rand.next_u64();
322                _v = rand.next_u64();
323                _v = rand.next_u64();
324                _v = rand.next_u64();
325                _v = rand.next_u64();
326                _v = rand.next_u64();
327                _v = rand.next_u64();
328            }
329        });
330        let elapsed = start.elapsed().as_secs_f64();
331        let did = todo as f64 * 64. / 1e6;
332        println!("Did {} MB/s", did / elapsed);
333        did
334    }
335
336    #[test]
337    #[ignore]
338    pub fn multi_speedtest() {
339        let core_ids = core_affinity::get_core_ids().unwrap_or_default();
340        let start = std::time::Instant::now();
341        let mut handles = core_ids
342            .into_iter()
343            .map(|id| {
344                std::thread::spawn(move || {
345                    let _val = core_affinity::set_for_current(id);
346                    speedtest_128()
347                })
348            })
349            .collect::<Vec<_>>();
350
351        let did: f64 = handles
352            .drain(..)
353            .map(|v| v.join().unwrap_or_default())
354            .sum();
355        let elapsed = start.elapsed().as_secs_f64();
356        println!("Did {} MB/s", did / elapsed);
357    }
358}