← Back to AILP Home

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