Skip to main content

rigidity_scenes/
rng.rs

1//! A deterministic pseudo-random generator.
2//!
3//! Home-grown rather than `rand`: the scenes are a measuring standard, and
4//! reproducibility matters more here than distribution quality. An
5//! external generator may change algorithm in a minor release, and every
6//! number recorded in the tests would stop matching.
7//!
8//! The algorithm is splitmix64: ten lines, good bit quality, period 2⁶⁴.
9
10/// A generator with explicit state.
11#[derive(Debug, Clone)]
12pub struct Rng {
13    state: u64,
14}
15
16impl Rng {
17    /// Creates a generator from a seed.
18    pub fn new(seed: u64) -> Self {
19        Self { state: seed }
20    }
21
22    fn next_u64(&mut self) -> u64 {
23        self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
24        let mut z = self.state;
25        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
26        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
27        z ^ (z >> 31)
28    }
29
30    /// A uniform number in `[0, 1)`.
31    pub fn unit(&mut self) -> f64 {
32        // 53 bits of mantissa: no more fits into an f64.
33        (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
34    }
35
36    /// A uniform number in `[-1, 1)`.
37    pub fn symmetric(&mut self) -> f64 {
38        self.unit() * 2.0 - 1.0
39    }
40
41    /// A normal number with standard deviation `sigma`.
42    ///
43    /// The Box–Muller transform. The second value of the pair is thrown
44    /// away: keeping it would mean state that depends on call history, and
45    /// the order of calls would start to affect the result.
46    pub fn normal(&mut self, sigma: f64) -> f64 {
47        let u1 = self.unit().max(f64::MIN_POSITIVE);
48        let u2 = self.unit();
49        sigma * (-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * u2).cos()
50    }
51}