From c2e2398f795fdded3b17e987116c06c58f847f98 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Fri, 9 Dec 2022 13:43:07 -0700 Subject: [PATCH] docs. --- src/random.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/src/random.rs b/src/random.rs index af490da..445281e 100644 --- a/src/random.rs +++ b/src/random.rs @@ -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(u32); impl GenericLcg32 { + /// 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 GenericLcg32 { } } +/// 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(&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(&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; + } } } }