1#[derive(Debug, Clone)]
8pub struct SplitMix64 {
9 state: u64,
10}
11
12impl SplitMix64 {
13 #[must_use]
15 pub fn new(seed: u64) -> Self {
16 SplitMix64 { state: seed }
17 }
18 pub fn next_u64(&mut self) -> u64 {
20 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
21 let mut z = self.state;
22 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
23 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
24 z ^ (z >> 31)
25 }
26 pub fn below(&mut self, n: u64) -> u64 {
28 self.next_u64() % n
29 }
30 #[must_use]
32 pub fn bit(&mut self) -> bool {
33 self.next_u64() & 1 == 1
34 }
35}
36
37#[must_use]
40#[allow(
41 clippy::cast_possible_truncation,
42 reason = "j ∈ [0,i] ≤ len ≤ usize::MAX,回写 usize 不会截断"
43)]
44pub fn shuffle(len: usize, seed: u64) -> Vec<usize> {
45 let mut a: Vec<usize> = (0..len).collect();
46 let mut rng = SplitMix64::new(seed);
47 for i in (1..len).rev() {
48 let j = rng.below(i as u64 + 1) as usize;
49 a.swap(i, j);
50 }
51 a
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
78 fn splitmix64_matches_the_published_reference_vectors() {
79 let take5 = |seed: u64| -> [u64; 5] {
80 let mut r = SplitMix64::new(seed);
81 [r.next_u64(), r.next_u64(), r.next_u64(), r.next_u64(), r.next_u64()]
82 };
83 assert_eq!(
84 take5(0),
85 [
86 0xE220_A839_7B1D_CDAF,
87 0x6E78_9E6A_A1B9_65F4,
88 0x06C4_5D18_8009_454F,
89 0xF88B_B8A8_724C_81EC,
90 0x1B39_896A_51A8_749B,
91 ],
92 "seed 0 的前五个输出与 Vigna 参考实现不符"
93 );
94 assert_eq!(
95 take5(1),
96 [
97 0x910A_2DEC_8902_5CC1,
98 0xBEEB_8DA1_658E_EC67,
99 0xF893_A2EE_FB32_555E,
100 0x71C1_8690_EE42_C90B,
101 0x71BB_54D8_D101_B5B9,
102 ],
103 "seed 1 的前五个输出与 Vigna 参考实现不符"
104 );
105 assert_eq!(
106 take5(0xDEAD_BEEF),
107 [
108 0x4ADF_B90F_68C9_EB9B,
109 0xDE58_6A31_41A1_0922,
110 0x021F_BC2F_8E1C_FC1D,
111 0x7466_CE73_7BE1_6790,
112 0x3BFA_8764_F685_BD1C,
113 ],
114 "seed 0xDEADBEEF 的前五个输出与 Vigna 参考实现不符"
115 );
116 assert_eq!(
119 take5(1_234_567),
120 [
121 6_457_827_717_110_365_317,
122 3_203_168_211_198_807_973,
123 9_817_491_932_198_370_423,
124 4_593_380_528_125_082_431,
125 16_408_922_859_458_223_821,
126 ],
127 "seed 1234567 的前五个输出与 Rosetta Code 公布的期望值不符"
128 );
129 }
130
131 #[test]
132 fn deterministic_given_seed() {
133 assert_eq!(shuffle(78, 42), shuffle(78, 42)); }
135
136 #[test]
137 fn shuffle_is_permutation() {
138 let mut s = shuffle(78, 12345); s.sort_unstable();
140 assert_eq!(s, (0..78).collect::<Vec<_>>()); }
142
143 #[test]
144 fn different_seeds_differ() {
145 assert_ne!(shuffle(78, 1), shuffle(78, 2));
146 }
147
148 #[test]
149 fn bit_and_below_cover() {
150 let mut rng = SplitMix64::new(7);
151 let mut seen_t = false;
153 let mut seen_f = false;
154 for _ in 0..64 {
155 if rng.bit() {
156 seen_t = true;
157 } else {
158 seen_f = true;
159 }
160 }
161 assert!(seen_t && seen_f);
162 let mut rng2 = SplitMix64::new(7);
164 for _ in 0..100 {
165 assert!(rng2.below(6) < 6);
166 }
167 }
168
169 #[test]
170 fn shuffle_len_one_and_zero() {
171 assert_eq!(shuffle(1, 9), vec![0]);
172 assert!(shuffle(0, 9).is_empty());
173 }
174
175 use proptest::prelude::*;
176 proptest! {
177 #[test]
178 fn prop_splitmix_deterministic(seed in any::<u64>()) {
179 let (mut a, mut b) = (SplitMix64::new(seed), SplitMix64::new(seed));
180 for _ in 0..8 {
181 prop_assert_eq!(a.next_u64(), b.next_u64());
182 }
183 }
184 #[test]
185 fn prop_below_in_range(seed in any::<u64>(), n in 1u64..1_000_000) {
186 let mut r = SplitMix64::new(seed);
187 for _ in 0..16 {
188 prop_assert!(r.below(n) < n);
189 }
190 }
191 #[test]
192 fn prop_shuffle_is_permutation(len in 0usize..256, seed in any::<u64>()) {
193 let mut p = shuffle(len, seed);
194 prop_assert_eq!(p.len(), len);
195 p.sort_unstable();
196 prop_assert!(p.iter().copied().eq(0..len));
197 }
198 }
199}