1use patterns_macros::{because, source};
2
3pub struct SplitMixSearch;
4source!(
5 SplitMixSearch,
6 "the avalanche multiplier search by Steele, Lea and Flood"
7);
8
9pub struct WeylSequence;
10source!(
11 WeylSequence,
12 "the equidistribution result an odd addend relies on"
13);
14
15const GOLDEN_GAMMA: u64 = 0x9E3779B97F4A7C15;
16because!(
17 GOLDEN_GAMMA,
18 WeylSequence,
19 "the odd golden ratio addend at this word width, which gives the sequence full period"
20);
21
22const MIX_A: u64 = 0xBF58476D1CE4E5B9;
23because!(
24 MIX_A,
25 SplitMixSearch,
26 "the first multiplier it found"
27);
28
29const MIX_B: u64 = 0x94D049BB133111EB;
30because!(
31 MIX_B,
32 SplitMixSearch,
33 "the second multiplier it found"
34);
35
36const SHIFT_HIGH: u32 = 30;
37because!(
38 SHIFT_HIGH,
39 SplitMixSearch,
40 "the first xor shift distance it found, which moves high bits down before the multiply"
41);
42
43const SHIFT_MID: u32 = 27;
44because!(
45 SHIFT_MID,
46 SplitMixSearch,
47 "the second xor shift distance, tuned against avalanche bias"
48);
49
50const SHIFT_LOW: u32 = 31;
51because!(
52 SHIFT_LOW,
53 SplitMixSearch,
54 "the final xor shift distance it found, which spreads the top bit across the word"
55);
56
57#[derive(Clone, Copy, PartialEq, Eq, Debug)]
58pub struct Seed(pub u64);
59
60pub fn next(seed: Seed) -> (u64, Seed) {
61 let moved = seed.0.wrapping_add(GOLDEN_GAMMA);
62 let a = (moved ^ (moved >> SHIFT_HIGH)).wrapping_mul(MIX_A);
63 let b = (a ^ (a >> SHIFT_MID)).wrapping_mul(MIX_B);
64 (b ^ (b >> SHIFT_LOW), Seed(moved))
65}
66
67pub fn below(seed: Seed, bound: u64) -> (u64, Seed) {
68 let (value, moved) = next(seed);
69 let scaled = (value as u128 * bound as u128) >> u64::BITS;
70 (scaled as u64, moved)
71}
72
73pub fn pick<T: Clone>(seed: Seed, items: &[T]) -> (Option<T>, Seed) {
74 if items.is_empty() {
75 return (None, seed);
76 }
77 let (at, moved) = below(seed, items.len() as u64);
78 (Some(items[at as usize].clone()), moved)
79}
80because!(next, "the next value from the generator, exposed so a caller advances the sequence without reaching into its state");
81because!(below, "a value under a bound taken from the high bits of the word by one multiply, which never divides, so a zero bound yields zero rather than a panic; a bound that does not divide the word leaves the same slight bias a remainder would, and that is not removed here");
82because!(pick, "choosing an element by index from a slice, so a caller never computes an index itself");