sfc_prng/
sfc64.rs

1// SPDX-FileCopyrightText: 2025 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! An implementation of the sfc64 random number generator.
6
7use rand_core::{RngCore, SeedableRng, impls, le};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11/// A sfc64 random number generator.
12///
13/// The sfc64 algorithm is not suitable for cryptographic uses but is very fast.
14/// This algorithm has a 256-bit state and outputs 64-bit random numbers. The
15/// average period of this algorithm is approximately 2<sup>255</sup>, and the
16/// minimum period is greater than or equal to 2<sup>64</sup>.
17///
18/// The algorithm used here is translated from the reference implementation
19/// provided by [PractRand] version pre0.95, which is licensed under the [public
20/// domain].
21///
22/// # Examples
23///
24/// ```
25/// # use sfc_prng::{
26/// #     Sfc64,
27/// #     rand_core::{RngCore, SeedableRng},
28/// # };
29/// #
30/// let mut rng = Sfc64::from_seed([0; 24]);
31/// assert_eq!(rng.next_u64(), 0xdb90_9c81_8901_599d);
32/// ```
33///
34/// [PractRand]: https://pracrand.sourceforge.net/
35/// [public domain]: https://pracrand.sourceforge.net/license.txt
36#[derive(Clone, Debug, Eq, PartialEq)]
37#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
38pub struct Sfc64 {
39    a: u64,
40    b: u64,
41    c: u64,
42    counter: u64,
43}
44
45impl Sfc64 {
46    /// Creates a new `Sfc64` using the given seeds.
47    ///
48    /// If `rounds` is [`None`], the state is mixed up 18 rounds during
49    /// initialization.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// # use sfc_prng::{Sfc64, rand_core::RngCore};
55    /// #
56    /// let mut rng = Sfc64::new(0, 0, 0, None);
57    /// assert_eq!(rng.next_u64(), 0xdb90_9c81_8901_599d);
58    /// ```
59    #[must_use]
60    #[inline]
61    pub fn new(a: u64, b: u64, c: u64, rounds: Option<u64>) -> Self {
62        let mut state = Self {
63            a,
64            b,
65            c,
66            counter: 1,
67        };
68        let rounds = rounds.unwrap_or(18);
69        for _ in 0..rounds {
70            state.next_u64();
71        }
72        state
73    }
74
75    /// Creates a new `Sfc64` using a [`u64`] seed.
76    ///
77    /// If `rounds` is [`None`], the state is mixed up 12 rounds during
78    /// initialization.
79    ///
80    /// <div class="warning">
81    ///
82    /// Note that the result of this method is different from the result of
83    /// [`Sfc64::seed_from_u64`].
84    ///
85    /// </div>
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// # use sfc_prng::{Sfc64, rand_core::RngCore};
91    /// #
92    /// let mut rng = Sfc64::new_u64(0, None);
93    /// assert_eq!(rng.next_u64(), 0x3acf_a029_e3cc_6041);
94    /// ```
95    #[must_use]
96    #[inline]
97    pub fn new_u64(seed: u64, rounds: Option<u64>) -> Self {
98        let (a, b, c) = (seed, seed, seed);
99        let rounds = rounds.or(Some(12));
100        Self::new(a, b, c, rounds)
101    }
102}
103
104impl RngCore for Sfc64 {
105    #[allow(clippy::cast_possible_truncation)]
106    #[inline]
107    fn next_u32(&mut self) -> u32 {
108        self.next_u64() as u32
109    }
110
111    #[inline]
112    fn next_u64(&mut self) -> u64 {
113        const ROTATION: u32 = 24;
114        const RIGHT_SHIFT: u32 = 11;
115        const LEFT_SHIFT: u32 = 3;
116
117        let tmp = self.a.wrapping_add(self.b).wrapping_add(self.counter);
118        self.a = self.b ^ (self.b >> RIGHT_SHIFT);
119        self.b = self.c.wrapping_add(self.c << LEFT_SHIFT);
120        self.c = self.c.rotate_left(ROTATION).wrapping_add(tmp);
121        self.counter = self.counter.wrapping_add(1);
122        tmp
123    }
124
125    #[inline]
126    fn fill_bytes(&mut self, dst: &mut [u8]) {
127        impls::fill_bytes_via_next(self, dst);
128    }
129}
130
131impl SeedableRng for Sfc64 {
132    type Seed = [u8; 24];
133
134    #[inline]
135    fn from_seed(seed: Self::Seed) -> Self {
136        let mut s = [u64::default(); 3];
137        le::read_u64_into(&seed, &mut s);
138        Self::new(s[0], s[1], s[2], None)
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use core::{any, mem};
145
146    use super::*;
147
148    static EXPECTED_1: [u64; 16] = [
149        0xdb90_9c81_8901_599d,
150        0x8ffd_1953_6521_6f57,
151        0xe8c4_ad5e_258a_c04a,
152        0x8f8e_f2c8_9fdb_63ca,
153        0xf986_5b01_d98d_8e2f,
154        0x4655_5871_a65d_08ba,
155        0x6686_8677_c629_8fcd,
156        0x2ce1_5a7e_6329_f57d,
157        0x0b2f_1833_ca91_ca79,
158        0x4b08_90ac_9bf4_53ca,
159        0xd128_9e0d_ecd4_f85c,
160        0x39ad_57d6_e346_b912,
161        0x98d1_7cc0_0f53_0bda,
162        0x13ac_08e9_8d77_d759,
163        0xcb06_088a_9b16_64a3,
164        0x6a00_df8e_97a8_3fa5,
165    ];
166    static EXPECTED_BYTES_1: [u8; 128] = [
167        0x9d, 0x59, 0x01, 0x89, 0x81, 0x9c, 0x90, 0xdb, 0x57, 0x6f, 0x21, 0x65, 0x53, 0x19, 0xfd,
168        0x8f, 0x4a, 0xc0, 0x8a, 0x25, 0x5e, 0xad, 0xc4, 0xe8, 0xca, 0x63, 0xdb, 0x9f, 0xc8, 0xf2,
169        0x8e, 0x8f, 0x2f, 0x8e, 0x8d, 0xd9, 0x01, 0x5b, 0x86, 0xf9, 0xba, 0x08, 0x5d, 0xa6, 0x71,
170        0x58, 0x55, 0x46, 0xcd, 0x8f, 0x29, 0xc6, 0x77, 0x86, 0x86, 0x66, 0x7d, 0xf5, 0x29, 0x63,
171        0x7e, 0x5a, 0xe1, 0x2c, 0x79, 0xca, 0x91, 0xca, 0x33, 0x18, 0x2f, 0x0b, 0xca, 0x53, 0xf4,
172        0x9b, 0xac, 0x90, 0x08, 0x4b, 0x5c, 0xf8, 0xd4, 0xec, 0x0d, 0x9e, 0x28, 0xd1, 0x12, 0xb9,
173        0x46, 0xe3, 0xd6, 0x57, 0xad, 0x39, 0xda, 0x0b, 0x53, 0x0f, 0xc0, 0x7c, 0xd1, 0x98, 0x59,
174        0xd7, 0x77, 0x8d, 0xe9, 0x08, 0xac, 0x13, 0xa3, 0x64, 0x16, 0x9b, 0x8a, 0x08, 0x06, 0xcb,
175        0xa5, 0x3f, 0xa8, 0x97, 0x8e, 0xdf, 0x00, 0x6a,
176    ];
177
178    const SEED_2: [u8; 24] = [
179        0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23,
180        0x01, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01,
181    ];
182    static EXPECTED_2: [u64; 16] = [
183        0x6de8_9713_e3e2_d060,
184        0x7afc_0f39_1d6a_354d,
185        0x7827_56a8_00e1_098f,
186        0xb91a_7d8c_96ea_b14d,
187        0x6129_e834_d58d_bb5a,
188        0xf7ee_d803_09ca_4e5c,
189        0x1b7c_74c2_b415_d0bd,
190        0xba25_e41a_6615_5825,
191        0x5249_b3b7_0925_f9f3,
192        0x4cd8_cc40_a71d_92b5,
193        0x73b9_0774_f4ed_d216,
194        0xb8e8_17f6_3727_81fe,
195        0x470d_f4fc_f363_f4b2,
196        0x9537_2cb0_387b_c9c5,
197        0x4f3f_01b5_41b1_4300,
198        0x2edf_770d_f7dd_d1c5,
199    ];
200    static EXPECTED_BYTES_2: [u8; 128] = [
201        0x60, 0xd0, 0xe2, 0xe3, 0x13, 0x97, 0xe8, 0x6d, 0x4d, 0x35, 0x6a, 0x1d, 0x39, 0x0f, 0xfc,
202        0x7a, 0x8f, 0x09, 0xe1, 0x00, 0xa8, 0x56, 0x27, 0x78, 0x4d, 0xb1, 0xea, 0x96, 0x8c, 0x7d,
203        0x1a, 0xb9, 0x5a, 0xbb, 0x8d, 0xd5, 0x34, 0xe8, 0x29, 0x61, 0x5c, 0x4e, 0xca, 0x09, 0x03,
204        0xd8, 0xee, 0xf7, 0xbd, 0xd0, 0x15, 0xb4, 0xc2, 0x74, 0x7c, 0x1b, 0x25, 0x58, 0x15, 0x66,
205        0x1a, 0xe4, 0x25, 0xba, 0xf3, 0xf9, 0x25, 0x09, 0xb7, 0xb3, 0x49, 0x52, 0xb5, 0x92, 0x1d,
206        0xa7, 0x40, 0xcc, 0xd8, 0x4c, 0x16, 0xd2, 0xed, 0xf4, 0x74, 0x07, 0xb9, 0x73, 0xfe, 0x81,
207        0x27, 0x37, 0xf6, 0x17, 0xe8, 0xb8, 0xb2, 0xf4, 0x63, 0xf3, 0xfc, 0xf4, 0x0d, 0x47, 0xc5,
208        0xc9, 0x7b, 0x38, 0xb0, 0x2c, 0x37, 0x95, 0x00, 0x43, 0xb1, 0x41, 0xb5, 0x01, 0x3f, 0x4f,
209        0xc5, 0xd1, 0xdd, 0xf7, 0x0d, 0x77, 0xdf, 0x2e,
210    ];
211
212    #[test]
213    fn clone() {
214        let rng = Sfc64::from_seed(Default::default());
215        assert_eq!(rng.clone(), rng);
216    }
217
218    #[test]
219    fn debug() {
220        {
221            let rng = Sfc64::from_seed(Default::default());
222            assert_eq!(
223                format!("{rng:?}"),
224                "Sfc64 { a: 1074220252016367073, b: 14747097319099466665, c: 17960713684764683274, counter: 19 }"
225            );
226        }
227        {
228            let rng = Sfc64::seed_from_u64(1);
229            assert_eq!(
230                format!("{rng:?}"),
231                "Sfc64 { a: 18086042397347456770, b: 1455245525186175675, c: 11873715530299442944, counter: 19 }"
232            );
233        }
234    }
235
236    #[test]
237    fn equality() {
238        assert_eq!(
239            Sfc64::from_seed(Default::default()),
240            Sfc64::from_seed(Default::default())
241        );
242        assert_ne!(
243            Sfc64::from_seed(Default::default()),
244            Sfc64::from_seed([u8::MAX; 24])
245        );
246    }
247
248    #[test]
249    fn new() {
250        {
251            let mut rng = Sfc64::new(u64::default(), u64::default(), u64::default(), None);
252            for e in EXPECTED_1 {
253                assert_eq!(rng.next_u64(), e);
254            }
255        }
256        {
257            let mut rng = Sfc64::new(
258                0x0123_4567_89ab_cdef,
259                0x0123_4567_89ab_cdef,
260                0x0123_4567_89ab_cdef,
261                None,
262            );
263            for e in EXPECTED_2 {
264                assert_eq!(rng.next_u64(), e);
265            }
266        }
267    }
268
269    #[test]
270    fn new_u64() {
271        {
272            // This test vector was generated by the `RNG_output` command of PractRand
273            // version pre0.95.
274            //
275            // To generate a hex dump:
276            //
277            // ```sh
278            // ./RNG_output sfc64 128 0x0 | xxd -i
279            // ```
280            let expected = [
281                0x3acf_a029_e3cc_6041,
282                0xf5b6_515b_f2ee_419c,
283                0x1259_6358_94a2_9b61,
284                0x0b6a_e753_95f8_ebd6,
285                0x2256_2228_5ce3_02e2,
286                0x520d_2861_1395_cb21,
287                0xdb90_9c81_8901_599d,
288                0x8ffd_1953_6521_6f57,
289                0xe8c4_ad5e_258a_c04a,
290                0x8f8e_f2c8_9fdb_63ca,
291                0xf986_5b01_d98d_8e2f,
292                0x4655_5871_a65d_08ba,
293                0x6686_8677_c629_8fcd,
294                0x2ce1_5a7e_6329_f57d,
295                0x0b2f_1833_ca91_ca79,
296                0x4b08_90ac_9bf4_53ca,
297            ];
298
299            let mut rng = Sfc64::new_u64(u64::default(), None);
300            for e in expected {
301                assert_eq!(rng.next_u64(), e);
302            }
303        }
304        {
305            // This test vector was generated by the `RNG_output` command of PractRand
306            // version pre0.95.
307            //
308            // To generate a hex dump:
309            //
310            // ```sh
311            // ./RNG_output sfc64 128 0x123456789abcdef | xxd -i
312            // ```
313            let expected = [
314                0x79d7_8afb_e043_8f43,
315                0x9633_06cd_3e6e_830e,
316                0x983b_2a24_d126_ef1b,
317                0x7d89_3205_05df_8c58,
318                0x5542_a718_fe8e_d209,
319                0x17c3_8abf_86a2_c189,
320                0x6de8_9713_e3e2_d060,
321                0x7afc_0f39_1d6a_354d,
322                0x7827_56a8_00e1_098f,
323                0xb91a_7d8c_96ea_b14d,
324                0x6129_e834_d58d_bb5a,
325                0xf7ee_d803_09ca_4e5c,
326                0x1b7c_74c2_b415_d0bd,
327                0xba25_e41a_6615_5825,
328                0x5249_b3b7_0925_f9f3,
329                0x4cd8_cc40_a71d_92b5,
330            ];
331
332            let mut rng = Sfc64::new_u64(0x0123_4567_89ab_cdef, None);
333            for e in expected {
334                assert_eq!(rng.next_u64(), e);
335            }
336        }
337    }
338
339    #[test]
340    fn next_u32() {
341        {
342            let mut rng = Sfc64::from_seed(Default::default());
343            for e in EXPECTED_1 {
344                assert_eq!(rng.next_u32(), e as u32);
345            }
346        }
347        {
348            let mut rng = Sfc64::from_seed(SEED_2);
349            for e in EXPECTED_2 {
350                assert_eq!(rng.next_u32(), e as u32);
351            }
352        }
353    }
354
355    #[test]
356    fn next_u64() {
357        {
358            let mut rng = Sfc64::from_seed(Default::default());
359            for e in EXPECTED_1 {
360                assert_eq!(rng.next_u64(), e);
361            }
362        }
363        {
364            let mut rng = Sfc64::from_seed(SEED_2);
365            for e in EXPECTED_2 {
366                assert_eq!(rng.next_u64(), e);
367            }
368        }
369        {
370            let seed = [
371                0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
372                0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
373            ];
374            let expected = [
375                0xad6f_dc72_9fee_f3c1,
376                0x2a20_433d_733f_77d5,
377                0x0310_e213_6964_7420,
378                0x331a_176b_c71d_cabc,
379                0x5311_8f35_c249_4d94,
380                0xa3a9_9de7_e77e_16bf,
381                0xa7b1_b70a_3e59_a1ff,
382                0x8e11_27b2_8667_eb3c,
383                0x3fc5_89dc_124c_f6e8,
384                0x81e0_eaaa_ceb8_1d81,
385                0x79f5_3465_2d26_2df6,
386                0x87f7_0c82_14e1_86c5,
387                0x67af_9c00_7b82_5917,
388                0x5134_aec9_998d_8629,
389                0x205a_a249_9406_8634,
390                0x1c76_2918_dba3_e139,
391            ];
392
393            let mut rng = Sfc64::from_seed(seed);
394            for e in expected {
395                assert_eq!(rng.next_u64(), e);
396            }
397        }
398    }
399
400    #[test]
401    fn fill_bytes() {
402        {
403            let mut rng = Sfc64::from_seed(Default::default());
404            let mut dst = [u8::default(); 128];
405            rng.fill_bytes(&mut dst);
406            assert_eq!(dst, EXPECTED_BYTES_1);
407        }
408        {
409            let mut rng = Sfc64::from_seed(SEED_2);
410            let mut dst = [u8::default(); 128];
411            rng.fill_bytes(&mut dst);
412            assert_eq!(dst, EXPECTED_BYTES_2);
413        }
414    }
415
416    #[test]
417    fn fill_bytes_per_chunk() {
418        {
419            let mut rng = Sfc64::from_seed(Default::default());
420            let mut dst = [u8::default(); 8];
421            for e in EXPECTED_BYTES_1.chunks_exact(dst.len()) {
422                rng.fill_bytes(&mut dst);
423                assert_eq!(dst, e);
424            }
425        }
426        {
427            let mut rng = Sfc64::from_seed(SEED_2);
428            let mut dst = [u8::default(); 8];
429            for e in EXPECTED_BYTES_2.chunks_exact(dst.len()) {
430                rng.fill_bytes(&mut dst);
431                assert_eq!(dst, e);
432            }
433        }
434    }
435
436    #[test]
437    fn seed_type() {
438        assert_eq!(
439            any::type_name::<<Sfc64 as SeedableRng>::Seed>(),
440            any::type_name::<[u8; 24]>()
441        );
442        assert_eq!(
443            mem::size_of::<<Sfc64 as SeedableRng>::Seed>(),
444            mem::size_of::<[u8; 24]>()
445        );
446    }
447
448    #[cfg(feature = "serde")]
449    #[test]
450    fn serde() {
451        let mut rng = Sfc64::from_seed(Default::default());
452
453        let json = serde_json::to_string(&rng).unwrap();
454        assert_eq!(
455            json,
456            r#"{"a":1074220252016367073,"b":14747097319099466665,"c":17960713684764683274,"counter":19}"#
457        );
458
459        let mut deserialized_rng = serde_json::from_str::<Sfc64>(&json).unwrap();
460        assert_eq!(deserialized_rng, rng);
461        assert_eq!(deserialized_rng.next_u64(), rng.next_u64());
462    }
463}