multirand 0.2.0

A threaded pseudo-random number generator
Documentation
//! # threadrandom
//!
//! Defines the `ThreadRandom` struct and associated impls

use std::mem::transmute;
use std::sync::{Arc, Mutex};
use std::thread::Builder;

use crate::cmn;

#[derive(Debug, Clone)]
/// A threaded pseudo-random number generator
///
/// Each instance of `ThreadRandom` spawns a thread and gives it an `Arc<Mutex<u64>>`
///
/// The `state` value is continuously mutated within the thread to cut down on predictability.
///
/// # Examples
/// Using `ThreadRandom` to populate a vector with random values:
/// ```
/// use multirand::ThreadRandom;
/// use std::collections::HashSet;
/// use std::vec::Vec;
///
/// fn main() {
///     let rand = ThreadRandom::os().unwrap();
///
///     let mut v = Vec::<u128>::new();
///
///     for _ in 0..100 {
///         v.push(rand.random::<u128>().unwrap());
///     }
///
///     let h = v.clone().into_iter().collect::<HashSet<u128>>();
///
///     assert!(h.len() == v.len());
/// }
/// ```
pub struct ThreadRandom {
    state: Arc<Mutex<[u32; 4]>>,
}

impl ThreadRandom {
    /// Perform an LFSR operation on the given `[u32 ; 4]`
    fn randomise(input: [u32; 4]) -> [u32; 4] {
        let mut out = input;

        // state >> 32;
        let tmp = out[3];
        out[3] = out[2];
        out[2] = out[1];
        out[1] = out[0];

        // basic lfsr on state[0]
        out[0] ^= out[0] << 13;
        out[0] ^= out[0] >> 7;
        out[0] ^= out[0] << 17;

        // Maximal-length (setwise coprime) taps
        let i = (out[0] >> 4) & 1;
        let j = (out[0] >> 7) & 1;
        let k = (out[0] >> 11) & 1;
        let l = (out[0] >> 25) & 1;

        out[0] = tmp ^ (out[0] >> 1) ^ ((i ^ j ^ k ^ l) << 31);

        return out;
    }

    /// Constructs a new `ThreadRandom` with the given seed
    fn from_seed(v: [u32; 4]) -> Option<Self> {
        let s = Arc::new(Mutex::new(v));
        let s1 = Arc::clone(&s);
        let _ = Builder::new()
            .name("multirand".into())
            .stack_size(1024 * 32) //??? Idk if tinkering with this value is a good idea but in theory it doesnt need much
            .spawn(move || {
                loop {
                    let mut num = s1.lock().unwrap();
                    *num = Self::randomise(*num);
                }
            })
            .ok()?;
        Some(Self { state: s })
    }

    /// Constructs a new `ThreadRandom` seeded with the OS rng source (e.g. /dev/random on linux)
    ///
    /// May provide a better distribution
    ///
    /// # Blocking & None
    ///
    /// Depends on a seed from `getrandom::fill`, and returns `None` if it cannot get the OS rng values, though this is rare per the `getrandom` and `rand` docs.
    ///
    /// Also returns `None` if the underlying call to `thread::Builder::spawn` fails, meaning to OS could not spawn a thread.
    ///
    /// The call to `getrandom::fill` may also block during system startup while it gathers entropy.
    ///
    /// # Unsafe
    ///
    /// This function implements an unsafe block: to acquire the os rng source and convert
    /// it to a usable value, a call to `std::mem::transmute` is made, converting a `mut [u8; 16]` into a `[u32; 4]`
    ///
    /// # Example
    /// ```
    /// use multirand::ThreadRandom;
    ///
    /// let rand = ThreadRandom::os().unwrap();
    /// println!("{}", rand.random::<u64>().unwrap())
    /// ```
    pub fn os() -> Option<Self> {
        let mut buf = [0u8; 16];
        getrandom::fill(&mut buf).ok()?;
        Self::from_seed(unsafe { transmute::<[u8; 16], [u32; 4]>(buf) })
    }

    /// Constructs a new `ThreadRandom` with the given seed.
    ///
    /// Each `ThreadRandom` runs its own thread.
    ///
    /// A seed in the range `[u64::MAX, u128::MAX]` should be selected to cut down on zeroes, allowing for better entropy.
    ///
    /// # None
    ///
    /// Returns a `None` value if the underlying call to `thread::Builder::spawn` fails, meaning the OS could not spawn a thread.
    ///
    /// # Unsafe
    ///
    /// This function implements an unsafe block: to convert the seed to a usable value, a call to `std::mem::transmute` is made, converting the `u128` into a `[u32; 4]`
    pub fn new(seed: u128) -> Option<Self> {
        Self::from_seed(unsafe { transmute::<u128, [u32; 4]>(seed) })
    }

    /// Get a random number of type T
    ///
    /// Calls `Self::randomise` in case the value hasn't changed since it was last called.
    ///
    /// # None
    ///
    /// Returns a `None` value if it cannot acquire the mutex (unlikely)
    ///
    /// # Example
    /// ```
    /// use multirand::ThreadRandom;
    ///
    /// let rand = ThreadRandom::os().unwrap();
    /// println!("{}", rand.random::<u64>().unwrap())
    /// ```
    pub fn random<T: cmn::CreateFrom>(&self) -> Option<T> {
        let mut s = (self.state).lock().ok()?;
        *s = Self::randomise(*s);
        Some(T::create(*s))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;
    use std::vec::Vec;

    #[test]
    /// Show that iterative values are distinct from one another
    fn distinct_from_last() {
        let rand = ThreadRandom::os().unwrap();

        let mut last = 0;

        for _ in 0..10 {
            let i = rand.random::<u64>().unwrap();
            dbg!(&rand, i);

            assert!(i != last);

            last = i;
        }
    }

    #[test]
    /// Populate a vector and show that every value is unique
    fn purely_unique() {
        let rand = ThreadRandom::os().unwrap();

        let mut v = Vec::<u128>::new();

        for _ in 0..100 {
            let i = rand.random::<u128>().unwrap();
            dbg!(&rand, i);
            v.push(i);
        }

        let h = v.clone().into_iter().collect::<HashSet<u128>>();

        assert!(h.len() == v.len());
    }

    #[test]
    /// Test that all conversions return Some()
    fn conversions() {
        let rand = ThreadRandom::os().unwrap();

        let boolean = rand.random::<bool>();
        dbg!(&rand, boolean);
        assert!(boolean.is_some());

        let character = rand.random::<char>();
        dbg!(&rand, character);
        assert!(character.is_some());

        let unsigned_8 = rand.random::<u8>();
        dbg!(&rand, unsigned_8);
        assert!(unsigned_8.is_some());

        let unsigned_16 = rand.random::<u16>();
        dbg!(&rand, unsigned_16);
        assert!(unsigned_16.is_some());

        let unsigned_32 = rand.random::<u32>();
        dbg!(&rand, unsigned_32);
        assert!(unsigned_32.is_some());

        let unsigned_64 = rand.random::<u64>();
        dbg!(&rand, unsigned_64);
        assert!(unsigned_64.is_some());

        let unsigned_128 = rand.random::<u128>();
        dbg!(&rand, unsigned_128);
        assert!(unsigned_128.is_some());

        let unsigned_size = rand.random::<usize>();
        dbg!(&rand, unsigned_size);
        assert!(unsigned_size.is_some());

        let signed_8 = rand.random::<i8>();
        dbg!(&rand, signed_8);
        assert!(signed_8.is_some());

        let signed_16 = rand.random::<i16>();
        dbg!(&rand, signed_16);
        assert!(signed_16.is_some());

        let signed_32 = rand.random::<i32>();
        dbg!(&rand, signed_32);
        assert!(signed_32.is_some());

        let signed_64 = rand.random::<i64>();
        dbg!(&rand, signed_64);
        assert!(signed_64.is_some());

        let signed_128 = rand.random::<i128>();
        dbg!(&rand, signed_128);
        assert!(signed_128.is_some());

        let signed_size = rand.random::<isize>();
        dbg!(&rand, signed_size);
        assert!(signed_size.is_some());

        let float_32 = rand.random::<f32>();
        dbg!(&rand, float_32);
        assert!(float_32.is_some());

        let float_64 = rand.random::<f64>();
        dbg!(&rand, float_64);
        assert!(float_64.is_some());
    }
}