This commit is contained in:
Lokathor 2022-12-09 13:43:07 -07:00
parent df65520930
commit c2e2398f79

View file

@ -2,39 +2,88 @@
// process of having to pick what multiplier and increment to use behind a
// newtype that selects some default constants.
/// A [Linear Congruential Generator][wp-lcg] with 32-bits of output.
///
/// [wp-lcg]: https://en.wikipedia.org/wiki/Linear_congruential_generator
///
/// This holds a single `u32` as the state. Advancing the generator requires
/// only a multiply and an add, so it's fairly fast as a PRNG. The output
/// quality isn't particularly great as a result, and if you need less than 32
/// bits of randomness at a time you're advised to use the *upper* bits from
/// each output of this generator, which will be the better bits. The [Gen32]
/// impl of this type will handle that for you, if you use that trait.
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
// This particular `MUL` value comes from "Tables of linear congruential
// generators of different sizes and good lattice structure" by Pierre L'Ecuyer.
// We select 1 as the `ADD` value just to have an odd increment, and because
// `ADD` values of 3 bits or less can be encoded with an immediate
// instruction in both a32 and t32 code.
pub struct Lcg32(GenericLcg32<32310901, 1>);
impl Lcg32 {
/// Wraps the `u32` as an `Lcg32`.
///
/// This doesn't do any manipulation of the input state to try and help seed
/// the value, it just uses the value directly.
#[inline]
pub const fn new(state: u32) -> Self {
Self(GenericLcg32::new(state))
}
/// Advances the generator one step, producing a `u32` of output.
#[inline]
pub fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}
/// Advances the generator by `delta` steps all at once.
///
/// Because the generator output sequence loops, large `delta` values allow
/// you to "reverse" the generator.
#[inline]
pub fn jump_state(&mut self, delta: u32) {
self.0.jump_state(delta)
}
}
/// A [Linear Congruential Generator][wp-lcg] with a const generic multiplier
/// and increment.
///
/// [wp-lcg]: https://en.wikipedia.org/wiki/Linear_congruential_generator
///
/// The `ADD` value can be any value at all. Different `ADD` values will reorder
/// the sequence that you get from a particular `MUL` value. For best results
/// `ADD` should be an odd value. An even `ADD` value gives a generator with a
/// significantly shorter period than an odd `ADD` value.
///
/// The `MUL` value must be carefully selected, because it has an overwhelming
/// impact on the generator's output quality. See the linked wikipedia article
/// (and its references) if you want information on what `MUL` values you might
/// want to use.
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
struct GenericLcg32<const MUL: u32, const ADD: u32>(u32);
impl<const MUL: u32, const ADD: u32> GenericLcg32<MUL, ADD> {
/// Wraps the `u32` as a generic LCG.
///
/// This doesn't do any manipulation of the input state to try and help seed
/// the value, it just uses the value directly.
#[inline]
pub const fn new(state: u32) -> Self {
Self(state)
}
/// Advances the generator one step, producing a `u32` of output.
#[inline]
pub fn next_u32(&mut self) -> u32 {
let next_state = self.0.wrapping_mul(MUL).wrapping_add(ADD);
next_state
}
/// Advances the generator by `delta` steps all at once.
///
/// Because the generator output sequence loops, large `delta` values allow
/// you to "reverse" the generator.
#[inline]
pub fn jump_state(&mut self, mut delta: u32) {
let mut cur_mult: u32 = MUL;
@ -54,24 +103,34 @@ impl<const MUL: u32, const ADD: u32> GenericLcg32<MUL, ADD> {
}
}
/// A trait for pseudorandom number generators that have `u32`
/// output from each step of the generator.
pub trait Gen32 {
/// Advance the generator to produce the next `u32`.
fn next_u32(&mut self) -> u32;
/// Produce a `u16`.
#[inline]
fn next_u16(&mut self) -> u16 {
(self.next_u32() >> 16) as u16
}
/// Produce a `u8`.
#[inline]
fn next_u8(&mut self) -> u8 {
(self.next_u32() >> 24) as u8
}
/// Produce a `bool`.
#[inline]
fn next_bool(&mut self) -> bool {
(self.next_u16() as i32) < 0
}
/// Produce a value that's strictly less than `b`.
///
/// ## Panics
/// * If `b` is zero.
#[inline]
#[track_caller]
fn next_bounded(&mut self, b: u16) -> u16 {
@ -91,34 +150,50 @@ pub trait Gen32 {
high
}
/// Pick a random element of the slice, by value.
///
/// ## Panics
/// * If the length is 0.
#[inline]
fn pick<T: Copy>(&mut self, buf: &[T]) -> T {
let len16: u16 = saturating_usize_as_u16(buf.len());
buf[self.next_bounded(len16) as usize]
}
/// Pick a random element of the slice, by shared reference.
///
/// ## Panics
/// * If the length is 0.
#[inline]
fn pick_ref<'b, T>(&mut self, buf: &'b [T]) -> &'b T {
let len16: u16 = saturating_usize_as_u16(buf.len());
&buf[self.next_bounded(len16) as usize]
}
/// Pick a random element of the slice, by unique reference.
///
/// ## Panics
/// * If the length is 0.
#[inline]
fn pick_mut<'b, T>(&mut self, buf: &'b mut [T]) -> &'b mut T {
let len16: u16 = saturating_usize_as_u16(buf.len());
&mut buf[self.next_bounded(len16) as usize]
}
/// Shuffles the elements of the slice.
///
/// On an empty slice this is a no-op.
#[inline]
fn shuffle<T>(&mut self, buf: &mut [T]) {
let mut possibility_count: u16 = saturating_usize_as_u16(buf.len());
let mut this_index: usize = 0;
let end_index = buf.len() - 1;
while this_index < end_index {
let offset = self.next_bounded(possibility_count) as usize;
buf.swap(this_index, this_index + offset);
possibility_count -= 1;
this_index += 1;
if let Some(end_index) = buf.len().checked_sub(1) {
let mut possibility_count: u16 = saturating_usize_as_u16(buf.len());
let mut this_index: usize = 0;
while this_index < end_index {
let offset = self.next_bounded(possibility_count) as usize;
buf.swap(this_index, this_index + offset);
possibility_count -= 1;
this_index += 1;
}
}
}
}