[go: up one dir, main page]

randomize 2.2.0

A dead simple to use randomization library for rust.
Documentation
//! Functions for [Linear Congruential
//! Generators](https://en.wikipedia.org/wiki/Linear_congruential_generator).
//!
//! An LCG is just one multiply and one add, so it's very fast. However, they're
//! not much in terms of statistical quality, so you need to be using the `u128`
//! version if you want the kind of quality it takes to pass a PRNG test suite.
//! Accordingly, the LCGs given here aren't actually supported with a struct to
//! hold the state and all that. It's just the necessary math expression wrapped
//! in a function.
//!
//! The Period of an LCG is equal to the numeric range of the state type used.
//!
//! * lcg8 is for `u8` and has a period of 2^8
//! * lcg16 is for `u16` and has a period of 2^16
//! * and so on
//!
//! # Constants
//!
//! As you might guess, different LCG multipliers give better or worse quality.
//! There's apparently a thing called a [Spectral
//! Test](https://en.wikipedia.org/wiki/Spectral_test) you can do to rate the
//! quality of different LCG multipliers, but I don't know much about it. The
//! `pcg` module has a good multiplier for each LCG size.
//!
//! Also, the additive value that you pick can affect the output stream as well.
//! This allows some flexibility, because even with a single good multiplier
//! selected you still have many output streams available to you. The main limit
//! here is that the value must be odd, but that's quite a bit of freedom.
//! Again, the `pcg` module offers some defaults if you'd like.
//!
//! There are also some LCG implementations here with pre-baked constants.
//! They're based on the generators used in various games you might have played
//! before. They are "just for fun", and not actually any higher quality than
//! normal for an LCG of their size.
//!
//! # Jump Ahead
//!
//! It's possible to do some funny math and advance an LCG _as if_ you'd gone
//! through N steps in only log(N) time. There is a jump function available for
//! each size LCG offered. If you'd like to jump "backward" instead of forward
//! that also works, since PRNG sequences are looped. Just cast a signed value
//! into an unsigned value (eg: -5i32 as u32) and jump by that much. It has to
//! go forward the "long way", but you arrive at the right value.

use super::*;

/// The `u8` LCG
pub const fn lcg8(state: u8, mult: u8, inc: NonZeroU8) -> u8 {
  state.wrapping_mul(mult).wrapping_add(inc.get() | 1)
}

/// The `u16` LCG
pub const fn lcg16(state: u16, mult: u16, inc: NonZeroU16) -> u16 {
  state.wrapping_mul(mult).wrapping_add(inc.get() | 1)
}

/// The `u32` LCG
pub const fn lcg32(state: u32, mult: u32, inc: NonZeroU32) -> u32 {
  state.wrapping_mul(mult).wrapping_add(inc.get() | 1)
}

/// The `u64` LCG
pub const fn lcg64(state: u64, mult: u64, inc: NonZeroU64) -> u64 {
  state.wrapping_mul(mult).wrapping_add(inc.get() | 1)
}

/// The `u128` LCG
pub const fn lcg128(state: u128, mult: u128, inc: NonZeroU128) -> u128 {
  state.wrapping_mul(mult).wrapping_add(inc.get() | 1)
}

//

/// The LCG from a game with "colosseum" in its name.
pub const fn lcg32_colosseum(state: u32) -> u32 {
  lcg32(state, 0x000343FD, unsafe { NonZeroU32::new_unchecked(0x00269EC3) })
}

/// The LCG from the gen3 and gen4 games.
pub const fn lcg32_gen3gen4(state: u32) -> u32 {
  lcg32(state, 0x41C64E6D, unsafe { NonZeroU32::new_unchecked(0x00006073) })
}

/// The alternate LCG from the gen4 games.
pub const fn lcg32_gen4alt(state: u32) -> u32 {
  lcg32(state, 0x6C078965, unsafe { NonZeroU32::new_unchecked(0x00000001) })
}

/// The LCG from the gen5 and gen6 games.
pub const fn lcg64_gen5gen6(state: u64) -> u64 {
  lcg64(state, 0x5D588B656C078965, unsafe { NonZeroU64::new_unchecked(0x0000000000269EC3) })
}

//

macro_rules! make_jump_lcgX {
  ($(#[$attr:meta])* $f:ident, $u:ty, $nzu:ty) => {
    $(#[$attr])*
    pub fn $f(mut delta: $u, state: $u, mult: $u, inc: $nzu) -> $u {
      let mut cur_mult: $u = mult;
      let mut cur_plus: $u = inc.get();
      let mut acc_mult: $u = 1;
      let mut acc_plus: $u = 0;
      while delta > 0 {
        if (delta & 1) > 0 {
          acc_mult = acc_mult.wrapping_mul(cur_mult);
          acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus);
        }
        cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus);
        cur_mult = cur_mult.wrapping_mul(cur_mult);
        delta /= 2;
      }
      acc_mult.wrapping_mul(state).wrapping_add(acc_plus)
    }
  };
}

make_jump_lcgX! {
  /// Gives the `lcg8` output `delta` steps from now in log(delta) time.
  jump_lcg8, u8, NonZeroU8
}
make_jump_lcgX! {
  /// Gives the `lcg16` output `delta` steps from now in log(delta) time.
  jump_lcg16, u16, NonZeroU16
}
make_jump_lcgX! {
  /// Gives the `lcg32` output `delta` steps from now in log(delta) time.
  jump_lcg32, u32, NonZeroU32
}
make_jump_lcgX! {
  /// Gives the `lcg64` output `delta` steps from now in log(delta) time.
  jump_lcg64, u64, NonZeroU64
}
make_jump_lcgX! {
  /// Gives the `lcg128` output `delta` steps from now in log(delta) time.
  jump_lcg128, u128, NonZeroU128
}