Skip to main content

ferray_random/bitgen/
mod.rs

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