std/random

Random, a seedable and splittable SplitMix64 generator that is a value - the same seed makes the same numbers on every machine, with integers of a range, floats, shuffling, choice, sampling and independent splits.

The generated reference of std/random has every public construct with its signature, its doc comment and its examples.

std/random makes pseudo-random numbers from a generator that is a value. Random.seeded seed starts one; every draw advances it, so it lives in a var; the same seed makes the same numbers on every machine, in the VM and in a native binary, because it is ordinary TorbScript with no native. A computation that gives a split of its generator to each of its parts makes the same numbers whichever part runs first, which is what a reproducible test, a simulation and parallel training need (RANDOM.md).

The algorithm is SplitMix64 and its output for a seed is stable: seed 0 begins 16294208416658607535, 7960286522194355700, 487617019471545679, and seed 1234567 begins 6457827717110365317, 3203168211198807973, 9817491932198370423, the published vectors. The mapping to integers (rejection sampling, so there is no modulo bias), to floats (the top 53 bits over 2^53, in [0, 1)) and the order of a shuffle are part of that promise. The package is not for cryptography: SplitMix64 can be recovered from a few outputs. A key, a nonce or a token is made from Entropy of std/os, and Random.seeded Entropy.int64() is the unpredictable seed.

Import#

use Random from "std/random"
use Random from "std/random"

var source = Random.seeded 1234567
print source.uint64()

Declarations#

Random#

type Random {
  static fn seeded(seed: Int): Self
  var fn uint64(): UInt64
  var fn float(): Float64
  var fn int(range: Range<Int>): Int
  var fn split(): Self
  var fn shuffled<Item>(items: List<Item>): List<Item>
  var fn shuffle<Item>(var items: List<Item>)
  var fn choice<Item>(items: List<Item>): Item?
  var fn sample<Item>(items: List<Item>, count: Int): List<Item>
}

Random.seeded(seed) is the generator of a seed; a negative seed is its two's complement, so every Int is a distinct state. uint64 is the next 64 bits and the source of every other draw. float is a Float64 in [0, 1) with 53 random bits. int(range) is a uniform Int of a range, 1..6 or 1..=6; it panics on an empty range, and Int.minimum..=Int.maximum is the whole range of an Int.

use Random from "std/random"

var source = Random.seeded 42
const roll = source.int(1..=6)
const fraction = source.float()
print roll
print fraction

Splitting#

split answers a new, independent generator and advances the receiver. The child starts from the parent's next output with a gamma of its own, as SplittableRandom of the JDK does, so the parent and the child do not repeat each other's numbers.

use Random from "std/random"

var parent = Random.seeded 42
var left = parent.split()
var right = parent.split()
print left.uint64()
print right.uint64()

Shuffling, choice and sampling#

shuffled(items) is a new list of the same items in random order (Fisher-Yates) and shuffle does it to a var list in place. choice(items) is one item, or None for an empty list. sample(items, count) is count distinct positions of the list, in random order; it panics when count is negative or more than the list holds.

use Random from "std/random"

var source = Random.seeded 42
const mixed = source.shuffled([1, 2, 3, 4, 5])
const picked = source.sample([1, 2, 3, 4, 5], 2)
print mixed
print picked

Pitfalls#

  • Drawing changes the generator, so it has to be a var, and a copy of it repeats the original: copy it only to replay a sequence, and split it to make a second one.
  • A seed is not a secret. Anything that has to resist a guess takes its bytes from Entropy.
  • Normal and the other continuous distributions are not here; they wait for std/statistics.
  • std/os - Entropy, the unpredictable seed.
  • RANDOM.md - the decision and the slices.
Esc