tinymt 1.0.9

Rust implementation of TinyMT 64/32 - a lightweight variant of Mersenne Twister PRNG
Documentation
extern crate tinymt;

use rand::{Rng, RngCore, SeedableRng};

use tinymt::{TinyMT32, TinyMT32Seed, TinyMT64, TinyMT64Seed};

pub mod tinymt32;
pub mod tinymt64;

#[test]
fn test_tinymt64_seed() {
  assert_eq!(0u64, u64::from_le_bytes(TinyMT64Seed::default().0));
  assert_eq!(
    [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01],
    TinyMT64Seed::from(0x0102030405060708u64).as_mut()
  );
}

#[test]
fn test_tinymt32_seed() {
  assert_eq!(0u32, u32::from_le_bytes(TinyMT32Seed::default().0));
  assert_eq!([0x04, 0x03, 0x02, 0x01], TinyMT32Seed::from(0x01020304u32).as_mut());
}

#[test]
fn test_chi_squared_tinymt64() {
  let mut random = TinyMT64::from_seed(TinyMT64Seed::from(12345678901234u64));
  test_chi_squared(&mut random);
}

#[test]
fn test_chi_squared_tinymt32() {
  let mut random = TinyMT32::from_seed(TinyMT32Seed::from(1234567890u32));
  test_chi_squared(&mut random);
}

#[test]
fn test_try_fill_bytes_tinymt64() {
  let mut random = TinyMT64::from_seed(TinyMT64Seed::from(12345678901234u64));
  test_try_fill_bytes(&mut random);
}

#[test]
fn test_try_fill_bytes_tinymt32() {
  let mut random = TinyMT32::from_seed(TinyMT32Seed::from(1234567890u32));
  test_try_fill_bytes(&mut random);
}

/// Test that the significance level of the chi-square test for random number sequence generated
/// by the specified PRING is 95% or higher.
fn test_chi_squared(random: &mut dyn RngCore) {
  const DEGREE_OF_FREEDOM: usize = 9;
  const THRESHOLD: f64 = 16.92; // 5% for 9 degrees of freedom
  const SAMPLING_COUNT: usize = 1000000;
  let mut histogram = [0; DEGREE_OF_FREEDOM + 1];
  let length = histogram.len();
  for _ in 0..SAMPLING_COUNT {
    histogram[random.gen_range(0..length)] += 1;
  }
  let expected = SAMPLING_COUNT as f64 / length as f64;
  verify_chi_squared(&histogram[..], expected, THRESHOLD);
}

/// Acquire random bytes using various buffer lengths and perform a 0.5% chi-square test.
fn test_try_fill_bytes(random: &mut dyn RngCore) {
  const SAMPLING_COUNT: usize = 200000;
  const THRESHOLD: f64 = 20.3; // 0.5% for 7 degree of freedom
  for size in 1..256 {
    let mut histogram = [0u32; 8];
    let mut buffer = Vec::<u8>::with_capacity(size);
    let mut total = 0;
    for _ in 0..size {
      buffer.push(0)
    }
    for _ in 0..SAMPLING_COUNT / size {
      random.try_fill_bytes(&mut buffer).unwrap();
      for i in 0..buffer.len() {
        histogram[(buffer[i] >> 5) as usize] += 1;
      }
      total += buffer.len();
    }
    let expected = total as f64 / histogram.len() as f64;
    verify_chi_squared(&histogram[..], expected, THRESHOLD);
  }
}

/// Performs a chi-square test using the specified histogram of uniform random numbers.
fn verify_chi_squared(histogram: &[u32], expected: f64, threshold: f64) {
  let mut chi2: f64 = 0f64;
  for (i, h) in histogram.iter().enumerate() {
    println!("{:2}: {}", i, *h);
    let actual = *h as f64;
    chi2 += (expected - actual) * (expected - actual) / expected;
  }
  println!("χ² := {}, expected := {}", chi2, expected);
  assert!(chi2 < threshold);
}

#[test]
#[cfg(not(any(target_family = "windows", target_family = "wasm")))]
fn profiling() {
  use pprof;
  use rand::Rng;
  let guard = pprof::ProfilerGuardBuilder::default()
    .frequency(1000)
    .blocklist(&["libc", "libgcc", "pthread"])
    .build()
    .unwrap();

  let mut random = TinyMT64::from_entropy();
  for _i in 0..10000 {
    let rn = random.gen_range(0.0..1.0);
    assert!((0.0..1.0).contains(&rn));
  }

  if let Ok(report) = guard.report().build() {
    println!("report: {:?}", &report);
  };
  if let Ok(report) = guard.report().build() {
    let file = std::fs::File::create("flamegraph.svg").unwrap();
    report.flamegraph(file).unwrap();
  };
}