MathUtils 1.0.1

A high-fidelity Rust mirror of the JavaScript (ECMAScript) Math built-in object.
Documentation
//! Zero-dependency pseudo-random number generator for `Math.random()`.

use std::cell::Cell;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::SystemTime;

static GLOBAL_COUNTER: AtomicU64 = AtomicU64::new(0x853c_49e6_748f_ea9b);

/// State of Xoshiro256++ PRNG
#[derive(Clone, Copy, Debug)]
pub(crate) struct Xoshiro256PlusPlus {
    s: [u64; 4],
}

impl Xoshiro256PlusPlus {
    /// Initialize generator from 64-bit seed using SplitMix64.
    pub fn from_seed(mut seed: u64) -> Self {
        fn splitmix64(state: &mut u64) -> u64 {
            *state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
            let mut z = *state;
            z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
            z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
            z ^ (z >> 31)
        }

        if seed == 0 {
            seed = 0x9e37_79b9_7f4a_7c15;
        }

        let mut sm = seed;
        let s0 = splitmix64(&mut sm);
        let s1 = splitmix64(&mut sm);
        let s2 = splitmix64(&mut sm);
        let s3 = splitmix64(&mut sm);
        Self {
            s: [s0, s1, s2, s3],
        }
    }

    #[inline]
    pub fn next_u64(&mut self) -> u64 {
        let result = (self.s[0].wrapping_add(self.s[3]))
            .rotate_left(23)
            .wrapping_add(self.s[0]);

        let t = self.s[1] << 17;

        self.s[2] ^= self.s[0];
        self.s[3] ^= self.s[1];
        self.s[1] ^= self.s[2];
        self.s[0] ^= self.s[3];

        self.s[2] ^= t;
        self.s[3] = self.s[3].rotate_left(45);

        result
    }

    #[inline]
    pub fn next_f64(&mut self) -> f64 {
        // High 53 bits mapped into [0.0, 1.0)
        let val = self.next_u64() >> 11;
        (val as f64) * (1.0 / ((1u64 << 53) as f64))
    }
}

fn generate_entropy() -> u64 {
    let now = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .map(|d| d.as_nanos() as u64)
        .unwrap_or(0x1234_5678_9abc_def0);
    let counter = GLOBAL_COUNTER.fetch_add(0x9e37_79b9_7f4a_7c15, Ordering::Relaxed);
    let ptr_entropy = &GLOBAL_COUNTER as *const _ as usize as u64;
    let pid = std::process::id() as u64;

    now ^ counter.rotate_left(17) ^ ptr_entropy.rotate_left(31) ^ pid.rotate_left(43)
}

thread_local! {
    static RNG: Cell<Xoshiro256PlusPlus> = Cell::new(Xoshiro256PlusPlus::from_seed(generate_entropy()));
}

/// Returns a pseudo-random floating point number in the range `[0.0, 1.0)`.
///
/// Corresponds to JavaScript `Math.random()`.
///
/// # Examples
///
/// ```
/// use MathUtils::Math;
///
/// let val = Math::random();
/// assert!(val >= 0.0 && val < 1.0);
/// ```
#[inline]
pub fn random() -> f64 {
    RNG.with(|rng| {
        let mut state = rng.get();
        let val = state.next_f64();
        rng.set(state);
        val
    })
}

/// Reseeds the thread-local random number generator with a specific seed value.
///
/// Useful for reproducible randomized tests.
pub fn seed_random(seed: u64) {
    RNG.with(|rng| {
        rng.set(Xoshiro256PlusPlus::from_seed(seed));
    });
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_random_range() {
        for _ in 0..10_000 {
            let val = random();
            assert!(val >= 0.0);
            assert!(val < 1.0);
        }
    }

    #[test]
    fn test_reproducible_seeding() {
        seed_random(42);
        let a1 = random();
        let a2 = random();

        seed_random(42);
        let b1 = random();
        let b2 = random();

        assert_eq!(a1, b1);
        assert_eq!(a2, b2);
    }
}