rand
import rand from "rand";rand makes pseudo-random numbers from a seed: for games, simulations,
sampling, shuffling and test data. A generator is a small struct you own;
the same seed gives the same numbers on every platform, in the VM and
natively, so a run can be repeated exactly.
var g: rand.Xoshiro256 = rand.xoshiro256(2026);
let die: i64 = g.range_i64(1, 7); // 1 to 6
let x: f64 = g.next_f64(); // 0.0 <= x < 1.0These generators are not for security: anyone who sees a few outputs can predict the rest. Don’t use them for passwords, tokens or keys.
The module is pure: it computes, nothing else. It works in t1 and t2.
Generators
There are two, with the same methods. Pick Xoshiro256 unless you have a
reason not to.
Xoshiro256 | Pcg32 | |
|---|---|---|
| Algorithm | xoshiro256** | PCG-XSH-RR 64/32 |
| Native output | 64 bits | 32 bits |
| State | 32 bytes | 16 bytes |
| Make one | rand.xoshiro256(seed) | rand.pcg32(seed), rand.pcg32_stream(seed, stream) |
xoshiro256
pub fn xoshiro256(seed: u64) -> Xoshiro256A generator started from seed. Any seed is fine, 0 included: the state is
spread out from it with SplitMix64.
pcg32 and pcg32_stream
pub fn pcg32(seed: u64) -> Pcg32
pub fn pcg32_stream(seed: u64, stream: u64) -> Pcg32A PCG32 generator. pcg32_stream also picks one of 2^63 streams: two
generators with the same seed on different streams give unrelated
numbers, handy for giving each part of a program its own sequence. The
results match the reference pcg32_srandom_r(seed, stream) from
pcg-random.org.
Methods
Both generators have these, as g.next_u64() and so on.
next_u64, next_u32
pub fn Xoshiro256.next_u64(*self) -> u64
pub fn Xoshiro256.next_u32(*self) -> u32The next 64 or 32 random bits.
range_i64
pub fn Xoshiro256.range_i64(*self, lo: i64, hi: i64) -> i64A random integer from lo up to but not including hi, every value
equally likely. % on a random number would favour small values; this
doesn’t (it uses Lemire’s method). Any range works, negative bounds and
spans past 2^63 included. Traps with rand: empty range when
lo >= hi.
let card: i64 = g.range_i64(0, 52); // 0 to 51
let delta: i64 = g.range_i64(-3, 4); // -3 to 3next_f64
pub fn Xoshiro256.next_f64(*self) -> f64A random float in [0, 1): never 1.0. It has 53 random bits, the full
precision of an f64 in that range. For a float in [a, b), use
a + (b - a) * g.next_f64().
Shuffle and choice
pub @tape fn gen_shuffle(T: type)Shuffling and picking from a slice of T. gen_shuffle(T) emits, into your
module:
pub fn shuffle_$T(rng: *rand.Xoshiro256, s: []T)
pub fn choice_$T(rng: *rand.Xoshiro256, s: []const T) -> ?T
pub fn shuffle_pcg32_$T(rng: *rand.Pcg32, s: []T)
pub fn choice_pcg32_$T(rng: *rand.Pcg32, s: []const T) -> ?Tshuffle puts the elements in a random order, every order equally likely
(Fisher–Yates). Strings and structs holding them are moved, never copied.
choice returns a random element, or null when the slice is empty.
The ones for i64 already exist: rand.shuffle_i64, rand.choice_i64,
rand.shuffle_pcg32_i64, rand.choice_pcg32_i64.
import rand from "rand";
pub struct Card { name: string; rank: i64; }
@tape { rand.gen_shuffle(Card); }
var g: rand.Xoshiro256 = rand.xoshiro256(7);
shuffle_Card(&g, deck[0..52]);
var nums: [5]i64 = [1, 2, 3, 4, 5];
let pick: ?i64 = rand.choice_i64(&g, nums[0..5]);The struct must be pub, because the generated functions are.
Seeding
A fixed seed repeats the same run, which is what tests and replays want.
For numbers that differ on every run, seed from the OS’s random source
with os.random_bytes (rand itself stays pure):
import os from "os";
import bits from "bits";
var seed: [8]u8;
os.random_bytes(seed[0..8]) or { return 1; };
var g: rand.Xoshiro256 = rand.xoshiro256(bits.load_u64_le(seed[0..8], 0));Example
stdlib/examples/rand.tape rolls dice, shuffles a deck and takes a random
walk:
var g: rand.Xoshiro256 = rand.xoshiro256(2026);
var counts: [6]i64;
for (i in 0..600) {
let d: i64 = g.range_i64(1, 7);
counts[d - 1] = counts[d - 1] + 1;
}
var deck: [10]i64;
for (i in 0..10) { deck[i] = i + 1; }
rand.shuffle_i64(&g, deck[0..10]);
io.println("drawn: {rand.choice_i64(&g, deck[0..10])!}");600 rolls: 88 112 105 103 100 92
shuffled: 9 10 5 7 3 6 4 1 2 8
drawn: 1
walk after 1000 steps: 7.4763310258561
pcg32 words: 3096658103 2014397322Last modified: