Random API

A fast, non-cryptographic pseudo-random number generator. Each instance carries its own seed and state, so multiple generators can be used in parallel without interfering.

Overview

The global identifier Random is a namespace; its single static Random.create(...) returns a stateful generator instance whose methods are invoked as r.method(...). Two generators with different seeds produce uncorrelated sequences. This is the recommended way to give different parts of a program their own RNG instead of sharing a single one.

Cryptography: for cryptographically-strong random bytes (key material, nonces, salts), use Crypto.generateRandomBytes(n) from the Crypto module, not this generator.

Conventions

Algorithm. The generator is PCG-based: fast, statistically uniform, and fully deterministic given a fixed seed and state. It is not suitable for cryptographic use.

Seed vs. state. The seed picks one of 264 independent streams; calling setSeed(s) resets the stream's position to the start of that stream. The state is the position within the current stream. getState() after some number of draws can be saved and later restored with setState() to resume the same sequence.

Precision. Seeds and states are unsigned 64-bit integers, but Zym numbers are 64-bit doubles. Values above 253 lose precision when round-tripped through getSeed() / getState(). The PCG state is always a full 64-bit value, so a getState()/setState() round-trip is precision-limited: the immediately-next draw matches, but draws further into the resumed stream may diverge. For reproducible experiments, keep the seed at or below 253 and resume with setSeed rather than setState.

Determinism. Given the same seed and the same number of draws of each kind in the same order, the produced values are byte-identical across runs and platforms. Mixing randi, randf, and randfRange in different orders changes the sequence, so test fixtures should always exercise calls in a fixed order.

Errors. Bad argument types (for example, passing a string to randiRange) raise a Zym runtime error of the form Random.method(args) ....

Creating Generators

Random.create(seed?)

Builds a new generator. With no argument, the seed is randomized from system entropy, so two consecutive Random.create() calls give independent streams. With a seed, the generator is deterministic, so two Random.create(seed) calls with the same seed produce identical sequences.

Returns: A new Random instance.

var r = Random.create()        // seeded from system entropy
var fixed = Random.create(42)  // deterministic stream

Seed & State

r.seed(s)

Alias of setSeed(s).

r.setSeed(s)

Re-seeds the generator. Resets the position within the new stream to the start.

r.getSeed()

Returns the seed currently in use.

r.setState(s)

Sets the position within the current stream. Pair with getState() to save and restore mid-sequence.

r.getState()

Returns the current position within the stream. Changes with every draw.

save and restore mid-sequence
var r = Random.create(123)
r.randi()
r.randi()
var snapshot = r.getState()
print("%v", r.randi())   // X
r.setState(snapshot)
print("%v", r.randi())   // X again
r.randomize()

Re-seeds from the system entropy source. Equivalent to Random.create() but without allocating a new instance. Useful for non-deterministic behavior without picking a seed by hand.

Cost: this call queries the OS entropy source and is significantly slower than a normal randi(). Avoid calling it inside hot loops. Seed once at startup, then draw repeatedly.

Drawing Values

r.randi()

Returns a uniform 32-bit unsigned integer (0–4,294,967,295), returned as a Zym number.

r.randf()

Returns a uniform float in [0, 1), inclusive of 0 and exclusive of 1.

r.randfRange(lo, hi)

Returns a uniform float in [lo, hi], inclusive on both ends.

r.randfn(mean?, deviation?)

Returns a normal-distributed float. Called with no arguments, the distribution has mean 0 and deviation 1; called with both arguments, it uses the given parameters.

r.randiRange(lo, hi)

Returns a uniform integer in [lo, hi], inclusive on both ends.

one-off non-deterministic draw
var r = Random.create()
print("%v", r.randiRange(1, 6))   // d6 roll
r.randWeighted(weights)

Takes a list of non-negative numbers and returns the index of a randomly chosen entry, weighted by the values. Returns −1 if weights is empty or its entries sum to 0.

Examples

Reproducible Sequences

var a = Random.create(42)
var b = Random.create(42)
print("%v %v", a.randi(), b.randi())   // identical
print("%v %v", a.randf(), b.randf())   // identical

Weighted Choice

var r = Random.create(7)
var weights = [10.0, 1.0, 1.0]   // first option ~83% of the time
var counts = [0, 0, 0]
for (var i = 0; i < 1000; i = i + 1) {
    var idx = r.randWeighted(weights)
    counts[idx] = counts[idx] + 1
}
print("%v", counts)