use std::cell::Cell;
const RAND_MULTY: u16 = 191 * 8 + 5; const RAND_ADDY: u16 = 0x3141;
const RAND_SEED: u16 = 0xa3bf;
thread_local! {
static SEED: Cell<u16> = const { Cell::new(RAND_SEED) };
static BASE_SEED: Cell<u16> = const { Cell::new(RAND_SEED) };
}
fn advance(cell: &'static std::thread::LocalKey<Cell<u16>>) -> u16 {
cell.with(|seed| {
let s = seed.get().wrapping_mul(RAND_MULTY).wrapping_add(RAND_ADDY);
seed.set(s);
s
})
}
pub(crate) fn local_random() -> f64 {
f64::from(u32::from(advance(&SEED)) + 1) / 65536.0
}
pub(crate) fn calc_random() -> f64 {
f64::from(advance(&BASE_SEED)) / 65535.0
}
pub fn seed_random_from_time() {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let folded = (nanos ^ (nanos >> 16) ^ (nanos >> 32) ^ (nanos >> 48)) as u16;
SEED.with(|seed| seed.set(folded));
BASE_SEED.with(|seed| seed.set(folded));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replays_c_sequence_from_the_fixed_seed() {
let expected = {
let mut s: u16 = RAND_SEED;
let mut out = Vec::new();
for _ in 0..4 {
s = s.wrapping_mul(RAND_MULTY).wrapping_add(RAND_ADDY);
out.push(f64::from(u32::from(s) + 1) / 65536.0);
}
out
};
let got = std::thread::spawn(|| (0..4).map(|_| local_random()).collect::<Vec<_>>())
.join()
.unwrap();
assert_eq!(got, expected);
}
#[test]
fn range_is_zero_exclusive_one_inclusive() {
std::thread::spawn(|| {
let mut hit_one = false;
for _ in 0..65536 {
let r = local_random();
assert!(r > 0.0 && r <= 1.0, "out of (0,1]: {r}");
if r == 1.0 {
hit_one = true;
}
}
assert!(hit_one, "seed 65535 must map to exactly 1.0");
})
.join()
.unwrap();
}
#[test]
fn time_seeding_is_opt_in_and_per_thread() {
std::thread::spawn(|| {
seed_random_from_time();
let _ = local_random(); let _ = calc_random();
})
.join()
.unwrap();
let first = std::thread::spawn(local_random).join().unwrap();
let expected =
f64::from(u32::from(RAND_SEED.wrapping_mul(RAND_MULTY).wrapping_add(RAND_ADDY)) + 1)
/ 65536.0;
assert_eq!(first, expected);
}
#[test]
fn base_generator_replays_calc_random_not_local_random() {
let got = std::thread::spawn(|| (0..4).map(|_| calc_random()).collect::<Vec<_>>())
.join()
.unwrap();
let expected = {
let mut s: u16 = RAND_SEED;
let mut out = Vec::new();
for _ in 0..4 {
s = s.wrapping_mul(RAND_MULTY).wrapping_add(RAND_ADDY);
out.push(f64::from(s) / 65535.0);
}
out
};
assert_eq!(got, expected);
assert_eq!(got[0], 49156.0 / 65535.0);
assert_ne!(got[0], 49157.0 / 65536.0); }
#[test]
fn the_two_seeds_are_independent() {
let (base_first, s_first) = std::thread::spawn(|| {
let _ = local_random(); let _ = local_random();
(calc_random(), local_random())
})
.join()
.unwrap();
let s1 = RAND_SEED.wrapping_mul(RAND_MULTY).wrapping_add(RAND_ADDY);
assert_eq!(base_first, f64::from(s1) / 65535.0, "base seed advanced");
let s3 = s1
.wrapping_mul(RAND_MULTY)
.wrapping_add(RAND_ADDY)
.wrapping_mul(RAND_MULTY)
.wrapping_add(RAND_ADDY);
assert_eq!(s_first, f64::from(u32::from(s3) + 1) / 65536.0);
}
#[test]
fn base_range_includes_zero_and_one() {
std::thread::spawn(|| {
let (mut hit_zero, mut hit_one) = (false, false);
for _ in 0..65536 {
let r = calc_random();
assert!((0.0..=1.0).contains(&r), "out of [0,1]: {r}");
if r == 0.0 {
hit_zero = true;
}
if r == 1.0 {
hit_one = true;
}
}
assert!(hit_zero, "seed 0 must map to exactly 0.0");
assert!(hit_one, "seed 65535 must map to exactly 1.0");
})
.join()
.unwrap();
}
}