pith_digest/splitmix64.rs
1//! A deterministic 64-bit PRNG (splitmix64) for tests and mixing.
2
3/// The splitmix64 "golden gamma" increment.
4const GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
5
6/// A splitmix64 generator (Steele, Lea & Flood 2014).
7///
8/// Carries its 64-bit state, advances by the golden gamma and runs
9/// the xorshift-multiply output function. Two bytes of state for a
10/// passing statistical battery is the reason this and not an LCG is
11/// the shared PRNG: tests that need deterministic "random" data seed
12/// it with a constant, never with the clock.
13///
14/// ```
15/// let mut rng = pith_digest::SplitMix64::new(0);
16/// assert_eq!(rng.next_u64(), 0xe220_a839_7b1d_cdaf);
17/// assert_eq!(rng.next_u64(), 0x6e78_9e6a_a1b9_65f4);
18/// ```
19#[derive(Clone)]
20pub struct SplitMix64 {
21 state: u64,
22}
23
24impl SplitMix64 {
25 /// Creates a generator seeded with `seed`.
26 pub const fn new(seed: u64) -> Self {
27 SplitMix64 { state: seed }
28 }
29
30 /// Produces the next 64-bit output and advances the state.
31 pub fn next_u64(&mut self) -> u64 {
32 self.state = self.state.wrapping_add(GAMMA);
33 let mut z = self.state;
34 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
35 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
36 z ^ (z >> 31)
37 }
38
39 /// Produces `n` pseudo-random bytes, filling `out` exactly.
40 ///
41 /// Deterministic in `out.len()` and the seed alone, so a test can
42 /// regenerate the same corpus on any machine.
43 pub fn fill_bytes(&mut self, out: &mut [u8]) {
44 for chunk in out.chunks_mut(8) {
45 let word = self.next_u64().to_le_bytes();
46 chunk.copy_from_slice(&word[..chunk.len()]);
47 }
48 }
49}