#![no_std]
#![allow(clippy::upper_case_acronyms)]
use core::{
convert::{TryFrom, TryInto},
marker::PhantomData,
};
pub trait PicoRandRNG
where
Self::Input: TryFrom<u128>,
Self::Output: TryFrom<u128>,
{
type Input;
type Output;
fn new(seed: Self::Input) -> Self;
fn rand(&mut self) -> Self::Output;
fn rand_range(&mut self, min: usize, max: usize) -> Self::Output;
}
pub trait PicoRandGenerate<R: PicoRandRNG, T> {
fn generate(&mut self) -> T;
}
pub struct WyRand {
seed: u64,
}
impl PicoRandRNG for WyRand {
type Input = u64;
type Output = u64;
fn new(seed: Self::Input) -> Self {
WyRand { seed }
}
fn rand(&mut self) -> Self::Output {
self.seed = self.seed.wrapping_add(0xE7037ED1A0B428DB);
let x = (self.seed as u128)
.wrapping_mul((self.seed ^ 0xE7037ED1A0B428DB) as u128);
((x >> 64) ^ x) as u64
}
fn rand_range(&mut self, min: usize, max: usize) -> Self::Output {
let t = (-(max as i64)).checked_rem(max as i64).unwrap_or(0) as u64;
let (mut x, mut m, mut l);
while {
x = self.rand();
m = (x as u128).wrapping_mul(max as u128);
l = m as u64;
l < t
} {}
((m >> 64) as u64).clamp(min as _, max as _)
}
}
pub struct RNG<R: PicoRandRNG = WyRand, T = u64> {
rng: R,
_marker: PhantomData<fn() -> T>,
}
impl<R: PicoRandRNG, T> RNG<R, T>
where
<R as PicoRandRNG>::Output: TryInto<T>,
{
pub fn new(seed: R::Input) -> Self {
RNG::<R, T> { rng: R::new(seed), _marker: PhantomData }
}
pub fn generate_range(&mut self, min: usize, max: usize) -> T
where
<R as PicoRandRNG>::Output: Into<u128>,
T: Default + TryFrom<u128>,
{
u128::try_from(self.rng.rand_range(min, max))
.unwrap()
.try_into()
.unwrap_or_default() }
}
macro_rules! ImplPicoRandCommon {
(for $($type:tt),+) => {
$(ImplPicoRandCommon!($type);)*
};
($type:ident) => {
impl<R: PicoRandRNG> PicoRandGenerate<R, $type> for RNG<R, $type> where <R as PicoRandRNG>::Output: Into<u128> {
fn generate(&mut self) -> $type {
u128::try_from(self.rng.rand_range($type::MIN as usize, $type::MAX as usize)).unwrap() as _
}
}
};
}
ImplPicoRandCommon!(for u8, u16, u32, u64);
#[cfg(test)]
mod tests {
use super::*;
use paste::paste;
macro_rules! ImplPicoRandTest {
(for $($type:tt),+) => {
$(ImplPicoRandTest!($type);)*
};
($type:ident) => {
paste! {
#[test]
fn [<test_picorand_generate_ $type>]() {
let mut rng = RNG::<WyRand, $type>::new(0xDEADBEEF);
let mut generated: $type;
for _ in 1..100 {
generated = rng.generate();
assert!(generated >= $type::MIN || generated < $type::MAX);
}
}
#[test]
fn [<test_picorand_generate_range_ $type>]() {
let mut rng = RNG::<WyRand, $type>::new(0xDEADBEEF);
let mut generated: $type;
for _ in 1..100 {
generated = rng.generate_range(0xC0, 0xDE);
assert!(generated >= 0xC0 || generated < 0xDE);
}
}
}
};
}
ImplPicoRandTest!(for u8, u16, u32, u64);
}