Skip to main content

stdbr_core/
rand.rs

1//! Small non-cryptographic random sources used by document generators.
2
3const ZERO_SEED_FALLBACK: u64 = 0x9E37_79B9_7F4A_7C15;
4
5/// A minimal source of random bits for document generation.
6///
7/// Implement this trait to inject an application-provided random source into
8/// the `*_with_rng` generation functions. Document generation does not require
9/// cryptographically secure randomness, but generated values must not be used
10/// as secrets.
11pub trait RandomSource {
12    /// Returns the next 64 random bits.
13    fn next_u64(&mut self) -> u64;
14}
15
16/// A small deterministic random source initialized from a `u64` seed.
17///
18/// Equal seeds produce equal streams. This generator is not cryptographically
19/// secure. Seed zero is accepted.
20#[derive(Debug, Clone)]
21pub struct SeededRng {
22    state: u64,
23}
24
25impl SeededRng {
26    /// Creates a deterministic random source from `seed`.
27    pub const fn new(seed: u64) -> Self {
28        Self {
29            state: if seed == 0 { ZERO_SEED_FALLBACK } else { seed },
30        }
31    }
32}
33
34impl RandomSource for SeededRng {
35    fn next_u64(&mut self) -> u64 {
36        self.state ^= self.state << 13;
37        self.state ^= self.state >> 7;
38        self.state ^= self.state << 17;
39        self.state
40    }
41}
42
43#[cfg(feature = "std")]
44pub(crate) fn simple_seed() -> u64 {
45    use std::hash::{BuildHasher, Hasher};
46    std::collections::hash_map::RandomState::new()
47        .build_hasher()
48        .finish()
49}
50
51#[cfg(not(feature = "std"))]
52pub(crate) fn simple_seed() -> u64 {
53    let stack_var: u8 = 0;
54    let addr = &stack_var as *const u8 as u64;
55    addr.wrapping_mul(6_364_136_223_846_793_005)
56        .wrapping_add(1_442_695_040_888_963_407)
57}
58
59/// Uniformly samples `0..upper` using rejection sampling.
60pub(crate) fn below<R: RandomSource + ?Sized>(rng: &mut R, upper: u64) -> u64 {
61    assert!(upper > 0, "random upper bound must be positive");
62    let threshold = upper.wrapping_neg() % upper;
63    loop {
64        let value = rng.next_u64();
65        if value >= threshold {
66            return value % upper;
67        }
68    }
69}
70
71pub(crate) fn below_u32<R: RandomSource + ?Sized>(rng: &mut R, upper: u32) -> u32 {
72    u32::try_from(below(rng, u64::from(upper))).expect("bounded random value must fit in u32")
73}
74
75pub(crate) fn below_u8<R: RandomSource + ?Sized>(rng: &mut R, upper: u8) -> u8 {
76    u8::try_from(below(rng, u64::from(upper))).expect("bounded random value must fit in u8")
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn zero_seed_does_not_lock_the_generator() {
85        let mut rng = SeededRng::new(0);
86        assert_ne!(rng.next_u64(), 0);
87        assert_ne!(rng.next_u64(), 0);
88    }
89
90    #[test]
91    fn bounded_values_stay_below_the_limit() {
92        let mut rng = SeededRng::new(42);
93        for _ in 0..100 {
94            assert!(below(&mut rng, 10) < 10);
95        }
96    }
97}