1#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Prng {
17 state: u64,
18}
19
20const MULT: u64 = 6_364_136_223_846_793_005;
21const INC: u64 = 1_442_695_040_888_963_407;
22
23impl Prng {
24 #[must_use]
29 pub const fn new(seed: u64) -> Self {
30 let mut rng = Self {
31 state: INC.wrapping_add(seed),
32 };
33 rng.step();
34 rng
35 }
36
37 #[must_use]
43 pub const fn position(&self) -> u64 {
44 self.state
45 }
46
47 pub const fn set_position(&mut self, position: u64) {
49 self.state = position;
50 }
51
52 const fn step(&mut self) {
53 self.state = self.state.wrapping_mul(MULT).wrapping_add(INC);
54 }
55
56 #[allow(clippy::cast_possible_truncation)]
60 pub const fn next_u32(&mut self) -> u32 {
61 let old = self.state;
62 self.step();
63 let xorshifted = (((old >> 18) ^ old) >> 27) as u32;
64 let rot = (old >> 59) as u32;
65 xorshifted.rotate_right(rot)
66 }
67
68 pub const fn next_u64(&mut self) -> u64 {
70 let hi = self.next_u32() as u64;
71 let lo = self.next_u32() as u64;
72 (hi << 32) | lo
73 }
74
75 pub const fn below(&mut self, bound: u64) -> u64 {
84 assert!(bound != 0, "below(0) has no valid output");
85 let threshold = bound.wrapping_neg() % bound;
87 loop {
88 let value = self.next_u64();
89 if value >= threshold {
90 return value % bound;
91 }
92 }
93 }
94
95 pub const fn range(&mut self, low: u64, high: u64) -> u64 {
100 assert!(low < high, "range requires low < high");
101 low + self.below(high - low)
102 }
103
104 #[allow(clippy::cast_possible_truncation)]
108 pub fn choose<'a, T>(&mut self, items: &'a [T]) -> Option<&'a T> {
109 if items.is_empty() {
110 None
111 } else {
112 let index = self.below(items.len() as u64) as usize;
113 Some(&items[index])
114 }
115 }
116
117 #[allow(clippy::cast_possible_truncation)]
121 pub const fn shuffle<T>(&mut self, items: &mut [T]) {
122 let mut i = items.len();
123 while i > 1 {
124 i -= 1;
125 let j = self.below(i as u64 + 1) as usize;
126 items.swap(i, j);
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::Prng;
134
135 #[test]
136 fn same_seed_same_stream() {
137 let mut a = Prng::new(42);
138 let mut b = Prng::new(42);
139 for _ in 0..1000 {
140 assert_eq!(a.next_u64(), b.next_u64());
141 }
142 }
143
144 #[test]
145 fn different_seeds_diverge() {
146 let mut a = Prng::new(1);
147 let mut b = Prng::new(2);
148 assert_ne!(a.next_u64(), b.next_u64());
149 }
150
151 #[test]
152 fn position_round_trips_through_variable_draws() {
153 let mut rng = Prng::new(7);
154 let mark = rng.position();
155 let first: Vec<u64> = (0..20).map(|_| rng.below(100)).collect();
156
157 rng.set_position(mark);
158 let second: Vec<u64> = (0..20).map(|_| rng.below(100)).collect();
159
160 assert_eq!(first, second, "restoring position replays the same draws");
161 }
162
163 #[test]
164 fn below_is_in_bounds() {
165 let mut rng = Prng::new(9);
166 for _ in 0..10_000 {
167 assert!(rng.below(6) < 6);
168 }
169 }
170
171 #[test]
172 fn range_is_in_bounds() {
173 let mut rng = Prng::new(9);
174 for _ in 0..10_000 {
175 let v = rng.range(10, 20);
176 assert!((10..20).contains(&v));
177 }
178 }
179
180 #[test]
181 fn shuffle_is_a_permutation() {
182 let mut rng = Prng::new(3);
183 let mut data: Vec<u32> = (0..50).collect();
184 rng.shuffle(&mut data);
185 let mut sorted = data.clone();
186 sorted.sort_unstable();
187 assert_eq!(sorted, (0..50).collect::<Vec<_>>());
188 }
189
190 #[test]
191 fn choose_empty_is_none() {
192 let mut rng = Prng::new(1);
193 assert!(rng.choose::<u8>(&[]).is_none());
194 }
195}