random
Pseudo-random number generator.
dice :: new_rng(42)
roll :: dice.range_i32(1, 7) // from 1 to 6, the same at every run
deck :: [1, 2, 3, 4, 5]
new_rng().shuffle(deck) // another order at every runAlgorithm : xoroshiro128++ (Blackman & Vigna, 2021). u64 output, 2^128
period, passes the BigCrush statistical battery. Fast on every modern
CPU and trivially portable to WASM (no SIMD intrinsics, no platform
primitives). Pure Vader: its one host call is std/time.monotonic(),
read for the seed when new_rng is given none.
NOT suitable for cryptography: the output is reversible from any
256 consecutive u64s. For a cryptographic PRNG plug std/crypto
(post-MVP).
Types
Rng :: struct Mutable RNG state. Two 64-bit words ; the seeder guarantees at least one of them is non-zero (a fully-zero state would lock xoroshiro to emitting zeros forever).
Functions
new_rng :: fn(seed: u64 = u64(i64(monotonic()))) -> Rng Build an Rng from a 64-bit seed: the same seed gives the same draws, and
without one the seed is read from the clock, so the draws change from one
run to the next. The seed is fanned out via splitmix64 to populate both state
words ; a seed == 0 input therefore still produces a usable non-zero state.
Two calls close together without a seed may read the same clock value (its resolution is bounded) ; pass distinct seeds when two streams of one process must differ.
Parameters
| seed | u64 = u64(i64(monotonic())) | any 64-bit value, including 0; read from the clock when left out. |
Returns
Example
replayed :: new_rng(42) // the same draws at every run
fresh :: new_rng() // other draws at every runnext_u32 :: fn(self: Rng) -> u32 Draw the next u32 by taking the upper 32 bits of next_u64: the
upper bits of xoroshiro128++ pass BigCrush, the lower bits are
slightly weaker on certain tests.
Parameters
| self | Rng | the generator, whose state advances. |
Returns
u32 Example
new_rng(42).next_u32() // 395861021range_i32 :: fn(self: Rng, lo: i32, hi: i32) -> i32 Uniform i32 in the half-open range [lo, hi). A degenerate range
(hi <= lo) is empty and returns lo: without this guard the
modulus would divide by zero.
Implementation uses rejection sampling on the top 32 bits to avoid
modulo bias when (hi - lo) doesn't divide 2^32.
Parameters
| self | Rng | the generator, whose state advances. |
| lo | i32 | the smallest value drawn. |
| hi | i32 | the bound above the largest value drawn. |
Returns
i32 Example
rng :: new_rng(42)
rng.range_i32(1, 7) // 6: a die roll
rng.range_i32(5, 5) // 5: an empty range gives `lo`range_u32 :: fn(self: Rng, lo: u32, hi: u32) -> u32 Uniform u32 in the half-open range [lo, hi). A degenerate range
(hi <= lo) is empty and returns lo: without this guard the
modulus would divide by zero.
Parameters
| self | Rng | the generator, whose state advances. |
| lo | u32 | the smallest value drawn. |
| hi | u32 | the bound above the largest value drawn. |
Returns
u32 Example
new_rng(42).range_u32(10, 20) // 11unit_f64 :: fn(self: Rng) -> f64 Uniform f64 in [0.0, 1.0). Uses 52 bits of mantissa: one bit less
than the f64 maximum so the largest possible numerator (2^52 - 1)
divided by 2^52 is the immediate predecessor of 1.0 rather than
rounding up to exactly 1.0 (which (2^53 - 1) / 2^53 does on
IEEE 754 round-to-nearest).
Parameters
| self | Rng | the generator, whose state advances. |
Returns
f64 Example
new_rng(42).unit_f64() // 0.09216857653620125shuffle :: fn<T>(self: Rng, arr: T[]) -> void In-place Fisher-Yates shuffle. Visits each element once in O(n) ; O(1) extra memory (one i32 swap helper). Returns nothing: the input array is permuted directly.
Parameters
| self | Rng | the generator, whose state advances. |
| arr | T[] | the array to permute. |
Example
cards :: [1, 2, 3, 4, 5]
new_rng(42).shuffle(cards)
cards // [5, 1, 3, 4, 2]