1use core::convert::Infallible;
5
6use rand::{
7 SeedableRng,
8 rand_core::{TryRng, utils},
9};
10use serde::{Deserialize, Serialize};
11
12use crate::util::read_le_u64;
13
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50pub struct SFC64 {
51 state: [u64; 3],
53 counter: u64,
56}
57
58impl SFC64 {
59 #[inline]
62 pub(crate) fn initialize(a: u64, b: u64, c: u64, counter: u64) -> Self {
63 let mut rng = Self {
64 state: [a, b, c],
65 counter,
66 };
67 (0..12).for_each(|_| {
68 rng.step();
69 });
70 rng
71 }
72 #[inline]
74 fn step(&mut self) -> u64 {
75 const BARREL_SHIFT: u32 = 24;
76 const RSHIFT: u64 = 11;
77 const LSHIFT: u64 = 3;
78 let out = self.state[0]
79 .wrapping_add(self.state[1])
80 .wrapping_add(self.counter);
81 self.state[0] = self.state[1] ^ (self.state[1] >> RSHIFT);
82 self.state[1] = self.state[2].wrapping_add(self.state[2] << LSHIFT);
83 self.state[2] = self.state[2].rotate_left(BARREL_SHIFT).wrapping_add(out);
84 self.counter = self.counter.wrapping_add(1); out
86 }
87}
88
89impl TryRng for SFC64 {
90 type Error = Infallible;
91
92 #[inline]
93 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
94 Ok(self.step())
95 }
96 #[inline]
97 #[expect(
98 clippy::cast_possible_truncation,
99 reason = "the truncation is intended"
100 )]
101 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
102 Ok(self.step() as u32)
103 }
104 #[inline]
105 fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
106 utils::fill_bytes_via_next_word(dst, || Ok(self.step()))
107 }
108}
109impl SeedableRng for SFC64 {
110 type Seed = [u8; 24];
111 #[inline]
112 fn from_seed(seed: Self::Seed) -> Self {
113 let seed = &mut seed.as_slice();
114 Self::initialize(read_le_u64(seed), read_le_u64(seed), read_le_u64(seed), 0)
115 }
116 #[inline]
117 fn seed_from_u64(state: u64) -> Self {
118 Self::initialize(state, 0, 0, 1)
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use rand::{Rng, SeedableRng};
126 use rstest::rstest;
127
128 #[rustfmt::skip]
158 const SFC64_REFERENCE_OUTPUT: [u64; 17] = [
159 4_237_781_876_154_851_393, 17_705_428_440_413_258_140,
160 1_322_197_197_711_907_681, 822_724_228_132_957_142,
161 2_474_202_602_039_083_746, 5_912_426_283_212_852_001,
162 15_821_317_571_115_833_757, 10_375_476_962_501_160_791,
163 16_772_721_532_102_950_986, 10_344_472_337_605_944_266,
164 17_980_158_625_826_311_727, 5_068_054_200_821_024_954,
165 7_387_740_087_731_720_141, 3_233_965_506_304_800_125,
166 805_889_469_043_559_033, 5_406_730_423_683_535_818,
167 15_071_469_935_640_508_508
168 ];
169
170 #[rstest::fixture]
171 fn large_uniform_sample() -> Vec<u64> {
172 const N: u32 = 2_u32.pow(23);
173 let mut rng = SFC64::initialize(456_981, 0xcafe, 9_345_663_908, 123_456_789);
174 (0..N).map(|_| rng.next_u64()).collect::<Vec<_>>()
175 }
176
177 #[rstest::fixture]
178 fn zeros(large_uniform_sample: Vec<u64>) -> u32 {
179 large_uniform_sample
180 .iter()
181 .fold(0, |acc, x| acc + x.count_zeros())
182 }
183 #[rstest::fixture]
184 fn ones(large_uniform_sample: Vec<u64>) -> u32 {
185 large_uniform_sample
186 .iter()
187 .fold(0, |acc, x| acc + x.count_ones())
188 }
189
190 #[test]
191 fn test_sfc64_against_numpy() {
192 let mut rng = SFC64::seed_from_u64(0);
193 (0..17).for_each(|i| {
194 assert_eq!(
195 rng.next_u64(),
196 SFC64_REFERENCE_OUTPUT[i],
197 "failed at index {i}"
198 );
199 });
200 }
201
202 #[test]
208 fn test_sfc64_deep() {
209 let mut rng = SFC64::seed_from_u64(0);
210 (0..(2_u64.pow(17) - 1)).for_each(|_| {
211 rng.next_u64();
212 });
213 assert_eq!(rng.next_u64(), 4_977_758_738_274_538_201);
214 }
215
216 #[rstest]
217 fn test_uniformity_mean(large_uniform_sample: Vec<u64>, zeros: u32, ones: u32) {
218 let standard_error = ((4 * 64 * large_uniform_sample.len()) as f64)
219 .sqrt()
220 .recip();
221 approxim::assert_abs_diff_eq!(
222 f64::from(zeros) / f64::from(ones),
223 1.0,
224 epsilon = standard_error
225 );
226 }
227 #[rstest]
228 fn test_uniformity_variance(large_uniform_sample: Vec<u64>, ones: u32) {
229 let n_bits: u128 = large_uniform_sample.len() as u128 * 64;
230
231 let variance = (u128::from(ones) * (n_bits - u128::from(ones))) as f64
232 / (n_bits * (n_bits - 1)) as f64;
233 let epsilon = (n_bits as f64).sqrt().recip(); approxim::assert_abs_diff_eq!(variance, 0.25, epsilon = epsilon);
235 }
236
237 #[rstest]
238 fn test_autocorrelation(
239 large_uniform_sample: Vec<u64>,
240 #[values(1, 2, 4, 8, 64, 256, 65536)] lag: usize,
241 ) {
242 let n = large_uniform_sample.len();
243 let sample_f64: Vec<f64> = large_uniform_sample.iter().map(|&x| x as f64).collect();
244
245 let mean = sample_f64.iter().sum::<f64>() / n as f64;
246 let var = sample_f64.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n as f64;
247 let cov = (0..n - lag)
248 .map(|i| (sample_f64[i] - mean) * (sample_f64[i + lag] - mean))
249 .sum::<f64>()
250 / (n - lag) as f64;
251 let autocorr = cov / var;
252
253 let epsilon = 3.0 / (n as f64).sqrt();
254 approxim::assert_abs_diff_eq!(autocorr, 0.0, epsilon = epsilon);
255 }
256}