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/// Produce an 8-bit random value (samples from `next_u16`).
18#[must_use]
19pub fn next_u8() -> u8 {
20    (next_u16() & 0xFF) as u8
21}
22
23/// Produce a 16-bit random value from the shared RNG.
24#[must_use]
25pub fn next_u16() -> u16 {
26    STD_RAND.lock().expect("mutex").next_u16()
27}
28
29/// Produce a 32-bit random value from the shared RNG.
30#[must_use]
31pub fn next_u32() -> u32 {
32    STD_RAND.lock().expect("mutex").next_u32()
33}
34
35/// Produce a 64-bit random value from the shared RNG.
36#[must_use]
37pub fn next_u64() -> u64 {
38    STD_RAND.lock().expect("mutex").next_u64()
39}
40
41/// Produce a 128-bit random value from the shared RNG.
42#[must_use]
43pub fn next_u128() -> u128 {
44    STD_RAND.lock().expect("mutex").next_u128()
45}
46
47//
48// TESTS
49//
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_unique_u64s() {
57        use std::collections::HashSet;
58
59        let mut set = HashSet::new();
60        while set.len() < 1000 {
61            let random_value = next_u64();
62            assert!(set.insert(random_value), "value already in set");
63        }
64    }
65
66    #[test]
67    fn test_rng_reseeding() {
68        let mut rng1 = StdRand::seed(now_nanos());
69        let mut rng2 = StdRand::seed(now_nanos() + 1);
70
71        let mut matched = false;
72        for _ in 0..100 {
73            if rng1.next_u64() == rng2.next_u64() {
74                matched = true;
75                break;
76            }
77        }
78        assert!(
79            !matched,
80            "RNGs with different seeds unexpectedly produced the same value"
81        );
82    }
83
84    #[test]
85    fn test_determinism_with_fixed_seed() {
86        let seed = 42;
87        let mut rng1 = StdRand::seed(seed);
88        let mut rng2 = StdRand::seed(seed);
89
90        for _ in 0..100 {
91            assert_eq!(rng1.next_u64(), rng2.next_u64());
92        }
93    }
94
95    #[test]
96    fn test_bit_entropy() {
97        let mut bits = 0u64;
98        for _ in 0..100 {
99            bits |= next_u64();
100        }
101
102        let bit_count = bits.count_ones();
103        assert!(
104            bit_count > 8,
105            "Low entropy: only {bit_count} bits set in 100 samples",
106        );
107    }
108}