Skip to content
//! A tiny deterministic PRNG (splitmix64) so synthetic data generation needs no
//! external crate. wasm has no clock, so callers seed from a process-global
//! counter (see `external::next_seed`) to vary output between runs.

pub struct Rng(u64);

impl Rng {
    pub fn new(seed: u64) -> Self {
        Rng(seed ^ 0x9E37_79B9_7F4A_7C15)
    }

    pub fn next_u64(&mut self) -> u64 {
        self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
        let mut z = self.0;
        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
        z ^ (z >> 31)
    }

    /// Uniform integer in `[0, n)`. Returns 0 when `n == 0`.
    pub fn below(&mut self, n: u64) -> u64 {
        if n == 0 {
            0
        } else {
            self.next_u64() % n
        }
    }

    pub fn bool(&mut self) -> bool {
        self.next_u64() & 1 == 1
    }

    /// Uniform integer in the inclusive range `[min, max]`.
    pub fn range_i64(&mut self, min: i64, max: i64) -> i64 {
        if max <= min {
            min
        } else {
            let span = (max - min) as u64 + 1;
            min + self.below(span) as i64
        }
    }

    /// Uniform float in `[min, max]`.
    pub fn range_f64(&mut self, min: f64, max: f64) -> f64 {
        if max <= min {
            min
        } else {
            let unit = self.next_u64() as f64 / u64::MAX as f64;
            min + unit * (max - min)
        }
    }

    /// Pick a reference from a non-empty slice; `None` if empty.
    pub fn pick<'a, T>(&mut self, items: &'a [T]) -> Option<&'a T> {
        if items.is_empty() {
            None
        } else {
            Some(&items[self.below(items.len() as u64) as usize])
        }
    }
}