Random Number Generation
Nitpick provides a deterministic, high-quality pseudorandom number generator (PRNG) via stdlib/random.npk. The engine is xoshiro256** — a state-of-the-art 256-bit generator with excellent statistical properties, fast execution, and a period of 2²⁵⁶ - 1.
Import
use "std/random.npk".*;
Type:Random API
| Function | Signature | Description |
|---|---|---|
seed(n) |
int64(int64:n) |
Initialize PRNG state from seed n. Returns 0. |
next() |
int64() |
Next raw int64 (full 64-bit range, including negatives) |
range(min, max) |
int64(int64:min, int64:max) |
Uniform integer in [min, max] (inclusive). Rejection sampling — no modulo bias. |
float() |
flt64() |
Uniform float in [0.0, 1.0). Uses IEEE 754 mantissa technique. |
Basic Usage
// Seed the PRNG (same seed → same sequence — deterministic)
drop raw Random.seed(42i64);
// Draw values
int64:r1 = raw Random.next(); // Raw int64
int64:die = raw Random.range(1i64, 6i64); // 1..6 inclusive
flt64:u01 = raw Random.float(); // [0.0, 1.0)
Determinism
The PRNG is fully deterministic — the same seed always produces the same sequence:
drop raw Random.seed(12345i64);
int64:a = raw Random.next();
drop raw Random.seed(12345i64); // Reset to same seed
int64:b = raw Random.next();
// a == b (guaranteed)
This is critical for: - Reproducible simulation runs - Deterministic game logic - Testable procedural generation - Cross-platform bit-exact replication
Seeding Strategies
Reproducible (Testing / Simulation)
Use a fixed constant seed:
drop raw Random.seed(1i64);
Non-Reproducible (Production)
Seed from a time-based source or system entropy. Nitpick does not provide a time() builtin, but you can use an extern:
extern "libc" {
func:time = int64(int64:ptr);
}
drop raw Random.seed(time(0i64));
Range Distribution
Random.range(min, max) uses rejection sampling to avoid modulo bias. For a range of size k = max - min + 1, it rejects values in the last (2⁶⁴ mod k) slots before re-drawing. The expected number of draws is < 2 for all ranges.
// Coin flip
bool:heads = raw Random.range(0i64, 1i64) == 0i64;
// Random byte
int64:byte_val = raw Random.range(0i64, 255i64);
// Card draw from standard deck
int64:card = raw Random.range(0i64, 51i64);
Float Distribution
Random.float() produces values uniformly distributed in [0.0, 1.0) with 53 bits of mantissa precision:
flt64:u = raw Random.float(); // [0.0, 1.0)
// Scale to arbitrary range [a, b)
flt64:a = -5.0; flt64:b = 5.0;
flt64:x = a + (b - a) * raw Random.float();
Shuffling (Fisher-Yates)
// In-place Fisher-Yates shuffle of int64[n]
func:shuffle = void(int64[]:arr, int32:n) {
int32:i = n;
while (i > 1i32) {
i = i - 1i32;
int64:j = raw Random.range(0i64, @cast<int64>(i));
// Swap arr[i] and arr[j]
int64:tmp = arr[i];
arr[i] = arr[@cast<int32>(j)];
arr[@cast<int32>(j)] = tmp;
}
};
Engine: xoshiro256**
The underlying engine is xoshiro256** by Blackman & Vigna. Properties:
| Property | Value |
|---|---|
| State | 256 bits (4 × uint64) |
| Period | 2²⁵⁶ − 1 |
| Output | 64-bit |
| BigCrush / PractRand | Passes |
| Jump function | Supported (for parallel streams) |
State initialization uses SplitMix64 to expand a single 64-bit seed into a full 256-bit state, avoiding the zero-state problem.
Thread Safety
The PRNG state is global and not thread-safe. For concurrent use, each thread should maintain its own seeded instance. This is a known limitation tracked for v0.56.x.
Related
- standard_library/math.md — deterministic math functions
- types/fix256.md — deterministic fixed-point (no random)