Random Numbers#
Status: slice 1 built (2026-10-07) — std/random has Random, a seedable, splittable SplitMix64 generator that
is a value, with the integers of a range, floats, shuffling, choice, sampling and splitting (section 2). Entropy of
std/os/entropy (section 4) is the operating system's source of randomness, for keys, nonces and an unpredictable
seed. The package unblocks std/identifier (URI.md section 14, slice 11).
A source of randomness is a value that can be seeded and split, not a global generator. Two programs that start from the same seed make the same numbers, and a computation that hands a split of its generator to each of its parts makes the same numbers whichever part runs first. That is the shape of JAX's keys, and it is what reproducible training (COMPUTE.md), reproducible tests and deterministic simulations need.
1. What is decided#
- A value, passed as an argument. A function that needs randomness takes it, as
Uuid.version7(source, at:)does in URI.md section 12. A package that makes random values therefore needs no capability, and a sandboxed script has randomness exactly when it was handed a generator. - Seedable:
Random.seeded 42makes the same sequence on every machine and in both back ends. - Splittable: a generator yields independent generators for parallel work (
parallel(), tasks, the systems of ECS.md), so the numbers do not depend on the order the work runs in. - Value semantics decide the mutation: drawing a number changes the generator, so drawing takes a
varbinding, and a copy of a generator repeats its numbers. That is visible at the call, never a surprise.
2. Decided#
Decided by the owner on 2026-10-07 and built in slice 1 as std/random, module std/random, type Random.
- The algorithm is SplitMix64, and it is a public promise. The state is 64 bits and advances by an odd increment,
the gamma, on every draw; the draw is the new state through the mixing function of the reference implementation
(
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9,z = (z ^ (z >> 27)) * 0x94d049bb133111eb,z ^ (z >> 31)).Random.seeded seedstarts at the 64 bits of the seed (the two's complement of a negative one) with the golden gamma0x9e3779b97f4a7c15. It is ordinary TorbScript overUInt64and its wrapping arithmetic: no native, no capability, no seed refresh, and the same bits in the VM and in native code. The published outputs are pinned by known-answer tests: seed 0 begins0xe220a8397b1dcdaf,0x6e789e6aa1b965f4,0x06c45d188009454f, and seed 1234567 begins 6457827717110365317, 3203168211198807973, 9817491932198370423. - Stable means frozen. The algorithm, the mappings below and the order in which the operations draw are part of the contract, so numbers recorded with one version of the toolchain are the numbers of every later one - golden files, reproducible tests and reproducible training depend on it. Changing any of them is a breaking change and needs its own decision; a second algorithm would be a second type behind the same surface (a counter-based generator, if training needs independence that is proved and not measured), never a quiet change of this one.
- The distributions of slice 1, each defined by the draws it makes:
uint64(): the output itself.float(): aFloat64in[0, 1), the top 53 bits of one output (uint64() >> 11) divided by 2^53.int(range): a whole number of aRange<Int>, unbiased by rejection and never by a remainder alone. Withcountnumbers in the range, the outputs below the remainder of 2^64 divided bycountare drawn again; the first one above it answersrange.startplus its remainder bycount. A range of all 2^64 numbers answersrange.startplus one output. A range that holds no number panics, and says so in# Panics.shuffle(var items)andshuffled(items): Fisher-Yates from the end, forindexfrom the last down to 1 the item atint(0..=index)swaps intoindex.choice(items): the item atint(0..items.length()), orNonefor an empty list.sample(items, count):countdifferent positions in a random order, a shuffle that stops early: forindexfrom 0 up tocount, the item atint(index..items.length())swaps intoindex. More than the list holds panics.split(): the child takes the next output as its state and, as its gamma, the state after it mixed bymixGammaof Java'sSplittableRandom(MurmurHash3's finalizer, made odd, and flipped by0xaaaaaaaaaaaaaaaawhen fewer than 24 bit transitions are left); the parent has moved on by two draws. That splits are independent is an empirical statement, as for every generator of this family; the known-answer tests of a split pin the construction and cannot prove independence.
- What waits. Normal and every other continuous distribution belong to
std/statistics: they needlnandsqrtand carry accuracy promises that are that package's to make. A boolean draw isint(0..=1)until a use shows it needs a name. A cryptographic generator is not this package's job: SplitMix64 can be recovered from a few outputs, and keys, nonces and tokens come fromEntropy(section 4).
3. Slices#
| # | Slice | Needs |
|---|---|---|
| 1 | std/random: generator, seeding, splitting, integers, floats, shuffling, choosing | built (section 2) |
| 2 | An unpredictable seed from the operating system | built as Entropy of std/os (section 4), before slice 1 |
| 3 | std/identifier (URI.md slice 11) | 1 |
4. Cryptographic randomness, as built#
Decided and built 2026-09-29, because the first-party packages needed nonces before anything needed a generator (FIRST-PARTY.md section 8, gap 5). It answers the two questions that were open about it.
- Where:
std/os, modulestd/os/entropy, typeEntropy- notstd/random. The operating system's entropy is a capability, andstd/randomstays pure: a package that makes random values from a generator it was handed needs no capability, one that asks the system for bytes does. A seeded generator and the system's source are two types in two packages, so one is never mistaken for the other; slice 1 takes its seed as a value,Random.seeded Entropy.int64(), and never importsstd/os. - The capability is
entropy, its own (capabilityOfofcompiler/src/package/archive.trb), and not "the operating system": entropy reveals nothing about the machine - it makes a program unrepeatable, which is what the clock does too - so a SCRAM client that makes a nonce is not reported as asking about the machine, and a sandbox grants it alone withmodules "std/os/entropy".Entropyre-exported bystd/osreaches the same capability. - The surface:
Entropy.bytes(count): List<UInt8>andEntropy.int64(): Int64, eight bytes whose 64 bits are all random, for a seed. NoResult: where the system does not answer, it panics. None of the sources below fails once the system has started, there is no safe way on without randomness, and a failure a caller could handle invites a weaker source in its place - Go 1.24 madecrypto/rand.Readcrash for the same reason. - The sources, in
runtime/platform.c(torb_platform_random_bytes), which the IO core's DNS identifiers now use as well:BCryptGenRandomwith the system's preferred generator on Windows, loaded on first use;getrandom(2)on Linux and FreeBSD, which needs no file and so works in a chroot without/devand with a full table of descriptors;getentropyon macOS and in the browser, where emscripten answers it withcrypto.getRandomValues.