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