canic_utils/
rand.rs

1//!
2//! Randomness helpers built atop `tinyrand`, seeded with wall-clock time.
3//! Provides a shared RNG for tests and lightweight sampling (non-cryptographic).
4//!
5
6use crate::time::now_nanos;
7use std::sync::{LazyLock, Mutex};
8use tinyrand::{Rand, Seeded, StdRand};
9
10///
11/// Global RNG protected by a mutex, seeded from `now_nanos()`.
12///
13
14pub static STD_RAND: LazyLock<Mutex<StdRand>> =
15    LazyLock::new(|| Mutex::new(StdRand::seed(now_nanos())));
16
17///
18/// Produce an 8-bit random value (samples from `next_u16`).
19///
20#[must_use]
21pub fn next_u8() -> u8 {
22    (next_u16() & 0xFF) as u8
23}
24
25///
26/// Produce a 16-bit random value from the shared RNG.
27///
28#[must_use]
29pub fn next_u16() -> u16 {
30    STD_RAND.lock().expect("mutex").next_u16()
31}
32
33///
34/// Produce a 32-bit random value from the shared RNG.
35///
36#[must_use]
37pub fn next_u32() -> u32 {
38    STD_RAND.lock().expect("mutex").next_u32()
39}
40
41///
42/// Produce a 64-bit random value from the shared RNG.
43///
44#[must_use]
45pub fn next_u64() -> u64 {
46    STD_RAND.lock().expect("mutex").next_u64()
47}
48
49///
50/// Produce a 128-bit random value from the shared RNG.
51///
52#[must_use]
53pub fn next_u128() -> u128 {
54    STD_RAND.lock().expect("mutex").next_u128()
55}
56
57//
58// TESTS
59//
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_unique_u64s() {
67        use std::collections::HashSet;
68
69        let mut set = HashSet::new();
70        while set.len() < 1000 {
71            let random_value = next_u64();
72            assert!(set.insert(random_value), "value already in set");
73        }
74    }
75
76    #[test]
77    fn test_rng_reseeding() {
78        let mut rng1 = StdRand::seed(now_nanos());
79        let mut rng2 = StdRand::seed(now_nanos() + 1);
80
81        let mut matched = false;
82        for _ in 0..100 {
83            if rng1.next_u64() == rng2.next_u64() {
84                matched = true;
85                break;
86            }
87        }
88        assert!(
89            !matched,
90            "RNGs with different seeds unexpectedly produced the same value"
91        );
92    }
93
94    #[test]
95    fn test_determinism_with_fixed_seed() {
96        let seed = 42;
97        let mut rng1 = StdRand::seed(seed);
98        let mut rng2 = StdRand::seed(seed);
99
100        for _ in 0..100 {
101            assert_eq!(rng1.next_u64(), rng2.next_u64());
102        }
103    }
104
105    #[test]
106    fn test_bit_entropy() {
107        let mut bits = 0u64;
108        for _ in 0..100 {
109            bits |= next_u64();
110        }
111
112        let bit_count = bits.count_ones();
113        assert!(
114            bit_count > 8,
115            "Low entropy: only {bit_count} bits set in 100 samples",
116        );
117    }
118}