Skip to main content

ferray_random/bitgen/
mod.rs

1// ferray-random: BitGenerator trait and implementations
2
3mod pcg64;
4mod philox;
5mod xoshiro256;
6
7/// SplitMix64: a fast 64-bit hash-based PRNG used exclusively for
8/// seeding other generators (Xoshiro256**, PCG64) from a single u64
9/// seed. The function was previously copy-pasted into both
10/// `pcg64.rs` and `xoshiro256.rs` (#259).
11pub(crate) fn splitmix64(state: &mut u64) -> u64 {
12    *state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
13    let mut z = *state;
14    z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
15    z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
16    z ^ (z >> 31)
17}
18
19pub use pcg64::Pcg64;
20pub use philox::Philox;
21pub use xoshiro256::Xoshiro256StarStar;
22
23/// Trait for pluggable pseudo-random number generators.
24///
25/// All BitGenerators are `Send` (can be transferred between threads) but NOT `Sync`
26/// (they are stateful and require `&mut self`).
27///
28/// Concrete implementations: [`Pcg64`], [`Philox`], [`Xoshiro256StarStar`].
29pub trait BitGenerator: Send {
30    /// Generate the next 64-bit unsigned integer.
31    fn next_u64(&mut self) -> u64;
32
33    /// Create a new generator seeded from a single `u64`.
34    fn seed_from_u64(seed: u64) -> Self
35    where
36        Self: Sized;
37
38    /// Advance the generator state by a large step (2^128 for Xoshiro256**).
39    ///
40    /// Returns `Some(())` if jump is supported, `None` otherwise.
41    /// After calling `jump`, the generator's state has advanced as if
42    /// `2^128` calls to `next_u64` had been made.
43    fn jump(&mut self) -> Option<()>;
44
45    /// Create a new generator from a seed and a stream ID.
46    ///
47    /// Returns `Some(Self)` if the generator supports stream-based parallelism
48    /// (e.g., Philox), `None` otherwise.
49    fn stream(seed: u64, stream_id: u64) -> Option<Self>
50    where
51        Self: Sized;
52
53    /// Generate a uniformly distributed `f64` in [0, 1).
54    ///
55    /// Uses the upper 53 bits of `next_u64()` for full double precision.
56    fn next_f64(&mut self) -> f64 {
57        (self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
58    }
59
60    /// Generate a uniformly distributed `f32` in [0, 1).
61    fn next_f32(&mut self) -> f32 {
62        (self.next_u64() >> 40) as f32 * (1.0 / (1u64 << 24) as f32)
63    }
64
65    /// Fill a byte slice with random bytes.
66    fn fill_bytes(&mut self, dest: &mut [u8]) {
67        let mut i = 0;
68        while i + 8 <= dest.len() {
69            let val = self.next_u64();
70            dest[i..i + 8].copy_from_slice(&val.to_le_bytes());
71            i += 8;
72        }
73        if i < dest.len() {
74            let val = self.next_u64();
75            let bytes = val.to_le_bytes();
76            for (j, byte) in dest[i..].iter_mut().enumerate() {
77                *byte = bytes[j];
78            }
79        }
80    }
81
82    /// Generate a `u64` in the range `[0, bound)` using rejection sampling.
83    fn next_u64_bounded(&mut self, bound: u64) -> u64 {
84        if bound == 0 {
85            return 0;
86        }
87        // Lemire's nearly divisionless method
88        let mut x = self.next_u64();
89        let mut m = (x as u128) * (bound as u128);
90        let mut l = m as u64;
91        if l < bound {
92            let threshold = bound.wrapping_neg() % bound;
93            while l < threshold {
94                x = self.next_u64();
95                m = (x as u128) * (bound as u128);
96                l = m as u64;
97            }
98        }
99        (m >> 64) as u64
100    }
101}