Skip to main content

ferray_random/bitgen/
sfc64.rs

1// ferray-random: SFC64 (Small Fast Counting) BitGenerator implementation
2//
3// Sebastian Vigna's SFC64 — a 4-state 64-bit chaotic counting generator.
4// Period >= 2^64 (typical: 2^255). Used by NumPy as the `SFC64`
5// BitGenerator since 1.17.
6
7#![allow(clippy::unreadable_literal)]
8
9use super::BitGenerator;
10
11/// SFC64 (Small Fast Counting) pseudo-random number generator.
12///
13/// Four-word 64-bit state generator by Sebastian Vigna. Very fast,
14/// statistically robust on standard test suites. Period at least
15/// 2^64 from any starting state and typically 2^255.
16pub struct Sfc64 {
17    a: u64,
18    b: u64,
19    c: u64,
20    counter: u64,
21}
22
23impl Sfc64 {
24    #[inline]
25    fn advance(&mut self) -> u64 {
26        let tmp = self.a.wrapping_add(self.b).wrapping_add(self.counter);
27        self.counter = self.counter.wrapping_add(1);
28        self.a = self.b ^ (self.b >> 11);
29        self.b = self.c.wrapping_add(self.c << 3);
30        self.c = self.c.rotate_left(24).wrapping_add(tmp);
31        tmp
32    }
33}
34
35impl BitGenerator for Sfc64 {
36    fn next_u64(&mut self) -> u64 {
37        self.advance()
38    }
39
40    fn seed_from_u64(seed: u64) -> Self {
41        // NumPy's SFC64 init: set a = b = c = seed, counter = 1, then
42        // advance 12 times to "warm up" the state. We follow the
43        // upstream reference exactly.
44        let mut rng = Self {
45            a: seed,
46            b: seed,
47            c: seed,
48            counter: 1,
49        };
50        for _ in 0..12 {
51            let _ = rng.advance();
52        }
53        rng
54    }
55
56    fn jump(&mut self) -> Option<()> {
57        None
58    }
59
60    fn stream(_seed: u64, _stream_id: u64) -> Option<Self> {
61        None
62    }
63}
64
65impl Clone for Sfc64 {
66    fn clone(&self) -> Self {
67        Self {
68            a: self.a,
69            b: self.b,
70            c: self.c,
71            counter: self.counter,
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn deterministic_output() {
82        let mut a = Sfc64::seed_from_u64(0xc0ffee);
83        let mut b = Sfc64::seed_from_u64(0xc0ffee);
84        for _ in 0..1000 {
85            assert_eq!(a.next_u64(), b.next_u64());
86        }
87    }
88
89    #[test]
90    fn different_seeds_differ() {
91        let mut a = Sfc64::seed_from_u64(1);
92        let mut b = Sfc64::seed_from_u64(2);
93        let mut diff = false;
94        for _ in 0..100 {
95            if a.next_u64() != b.next_u64() {
96                diff = true;
97                break;
98            }
99        }
100        assert!(diff);
101    }
102
103    #[test]
104    fn full_range() {
105        let mut rng = Sfc64::seed_from_u64(0x1234_5678_9abc_def0);
106        let mut hi = false;
107        let mut lo = false;
108        for _ in 0..10_000 {
109            let v = rng.next_u64();
110            if v > u64::MAX / 2 {
111                hi = true;
112            } else {
113                lo = true;
114            }
115            if hi && lo {
116                break;
117            }
118        }
119        assert!(hi && lo);
120    }
121}