# std.Random - Random Number Generation (Zig 0.16.0) Pseudo-random number generators (PRNGs), cryptographically secure random number generators (CSPRNGs), and utilities for generating random values of various types. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html ## Zig 0.16 Entropy Rule Random bytes moved under `std.Io`. `io.random` is the process CSPRNG stream and may use a less-secure seeding fallback on platforms where fresh system entropy is unavailable. `io.randomSecure` requests fresh external entropy and reports failure. ```zig var seed: [32]u8 = undefined; io.random(&seed); const source: std.Random.IoSource = .{ .io = io }; const rng = source.interface(); ``` Use `io.randomSecure(&bytes)` when fresh OS-backed secure entropy is required and errors must be surfaced. The examples below use those `std.Io` patterns for current Zig 0.16 code. ## Quick Reference | Category | Types/Functions | |----------|-----------------| | Default PRNGs | `DefaultPrng` (Xoshiro256), `DefaultCsprng` (ChaCha) | | Fast PRNGs | `Xoshiro256`, `Xoroshiro128`, `Pcg`, `Sfc64`, `RomuTrio`, `Isaac64` | | CSPRNGs | `ChaCha`, `Ascon` | | Utilities | `SplitMix64` (seeding helper) | | Integer | `int`, `uintLessThan`, `uintAtMost`, `intRangeLessThan`, `intRangeAtMost` | | Float | `float`, `floatNorm`, `floatExp` | | Collections | `boolean`, `enumValue`, `shuffle`, `weightedIndex` | | Bytes | `bytes` | ## Choosing a PRNG ``` Need crypto security? |- Yes -> use `io.random`/`io.randomSecure`, or seed ChaCha/Ascon securely `- No -> use Xoshiro256 (DefaultPrng), Sfc64, RomuTrio, or Pcg ``` | PRNG | State | Output | Use Case | |------|-------|--------|----------| | `Xoshiro256` | 256-bit | 64-bit | Default, fast, good quality | | `Xoroshiro128` | 128-bit | 64-bit | Smaller state than Xoshiro256 | | `Pcg` | 128-bit | 32-bit | Compact, statistically excellent | | `Sfc64` | 256-bit | 64-bit | Very fast | | `RomuTrio` | 192-bit | 64-bit | Fast, small code size | | `Isaac64` | 8KB | 64-bit | Non-default PRNG; do not select it as the documented CSPRNG path | | `ChaCha` | Internal state | stream | CSPRNG, forward secure | | `Ascon` | 320-bit | stream | CSPRNG, lightweight | ## Basic Usage ### Quick Start with DefaultPrng ```zig const std = @import("std"); pub fn main() void { // Initialize with a seed var prng = std.Random.DefaultPrng.init(12345); const random = prng.random(); // Generate random values const n = random.int(u32); // 0 to maxInt(u32) const dice = random.intRangeLessThan(u8, 1, 7); // 1-6 const coin = random.boolean(); const prob = random.float(f32); // [0, 1) } ``` ### Cryptographically Secure Random ```zig const std = @import("std"); pub fn main(init: std.process.Init) void { const io = init.io; var key: [32]u8 = undefined; io.random(&key); // process CSPRNG stream var source: std.Random.IoSource = .{ .io = io }; const token = source.interface().int(u64); } ``` ### Seeding from System Entropy ```zig var seed: u64 = undefined; io.random(std.mem.asBytes(&seed)); var prng = std.Random.DefaultPrng.init(seed); ``` ## PRNG Initialization ### Xoshiro256 (Default) ```zig var prng = std.Random.Xoshiro256.init(seed); const random = prng.random(); // Jump ahead 2^128 steps (for parallel streams) prng.jump(); ``` ### ChaCha (CSPRNG) ```zig // Requires 32-byte secret seed var secret_seed: [std.Random.ChaCha.secret_seed_length]u8 = undefined; try io.randomSecure(&secret_seed); // or io.random when process-CSPRNG semantics suffice var csprng = std.Random.ChaCha.init(secret_seed); const random = csprng.random(); // Add entropy to refresh internal state csprng.addEntropy(&additional_entropy); ``` ### Pcg ```zig var prng = std.Random.Pcg.init(seed); const random = prng.random(); ``` ### Other PRNGs ```zig // All follow the same pattern var xoro = std.Random.Xoroshiro128.init(seed); var sfc = std.Random.Sfc64.init(seed); var romu = std.Random.RomuTrio.init(seed); var isaac = std.Random.Isaac64.init(seed); var ascon = std.Random.Ascon.init(secret_seed); ``` ## Generating Random Values ### Integers ```zig const random = prng.random(); // Full range of type const u8_val = random.int(u8); // 0 to 255 const i32_val = random.int(i32); // minInt to maxInt // Less than upper bound: [0, less_than) const index = random.uintLessThan(usize, array.len); const digit = random.uintLessThan(u8, 10); // 0-9 // At most (inclusive): [0, at_most] const die = random.uintAtMost(u8, 5); // 0-5 // Range (exclusive upper): [at_least, less_than) const temp = random.intRangeLessThan(i16, -40, 50); // Range (inclusive): [at_least, at_most] const year = random.intRangeAtMost(u16, 2000, 2024); ``` ### Biased Variants (Constant Time) For timing-sensitive code where bias is acceptable: ```zig // Slightly biased but constant-time const n = random.uintLessThanBiased(u32, 100); const m = random.uintAtMostBiased(u32, 99); const r = random.intRangeLessThanBiased(i32, -50, 50); const s = random.intRangeAtMostBiased(i32, -50, 50); ``` ### Floating Point ```zig // Uniform in [0, 1) const uniform: f32 = random.float(f32); const uniform64: f64 = random.float(f64); // Scale to range [a, b) const scaled = a + (b - a) * random.float(f64); // Normal distribution (mean=0, stddev=1) const normal: f64 = random.floatNorm(f64); // Custom mean/stddev: value * stddev + mean const custom_normal = random.floatNorm(f64) * 10.0 + 50.0; // Exponential distribution (rate=1) const exponential: f64 = random.floatExp(f64); // Custom rate: value / rate const custom_exp = random.floatExp(f64) / 0.5; ``` ### Boolean ```zig const coin_flip = random.boolean(); if (random.boolean()) { // 50% chance } ``` ### Enum Values ```zig const Direction = enum { north, south, east, west }; // Random enum value (evenly distributed) const dir = random.enumValue(Direction); // With explicit index type for cross-platform consistency const dir2 = random.enumValueWithIndex(Direction, u32); ``` ### Bytes ```zig var buffer: [32]u8 = undefined; random.bytes(&buffer); // Generate a random string var id: [16]u8 = undefined; random.bytes(&id); const hex = std.fmt.fmtSliceHexLower(&id); ``` ## Collections ### Shuffle ```zig var items = [_]u32{ 1, 2, 3, 4, 5 }; random.shuffle(u32, &items); // With explicit index type for reproducibility random.shuffleWithIndex(u32, &items, u32); ``` ### Weighted Selection ```zig const weights = [_]f32{ 0.5, 0.3, 0.2 }; // 50%, 30%, 20% const choice = random.weightedIndex(f32, &weights); // With integer weights const int_weights = [_]u32{ 5, 3, 2 }; const int_choice = random.weightedIndex(u32, &int_weights); ``` Weights must be finite/nonnegative for floating-point use (or valid nonnegative integer weights), their sum must be positive, and accumulation must remain representable. Validate untrusted weight arrays before calling. ### Random Element from Slice ```zig fn randomElement(comptime T: type, random: std.Random, slice: []const T) T { std.debug.assert(slice.len != 0); const index = random.uintLessThan(usize, slice.len); return slice[index]; } const colors = [_][]const u8{ "red", "green", "blue" }; const color = randomElement([]const u8, random, &colors); ``` ### Random Sample (Without Replacement) ```zig fn sample(comptime T: type, random: std.Random, source: []const T, dest: []T, indices: []usize) void { std.debug.assert(dest.len <= source.len); std.debug.assert(indices.len >= source.len); // Fisher-Yates partial shuffle for (indices[0..source.len], 0..) |*idx, i| idx.* = i; for (dest, 0..) |*d, i| { const j = random.intRangeLessThan(usize, i, source.len); std.mem.swap(usize, &indices[i], &indices[j]); d.* = source[indices[i]]; } } ``` ## Common Patterns ### Reproducible Sequences ```zig // Same seed = same sequence const seed: u64 = 42; var prng1 = std.Random.DefaultPrng.init(seed); var prng2 = std.Random.DefaultPrng.init(seed); std.debug.assert(prng1.random().int(u64) == prng2.random().int(u64)); ``` ### Thread-Local PRNGs ```zig threadlocal var tls_prng: ?std.Random.DefaultPrng = null; fn getThreadRandom(io: std.Io) std.Random { if (tls_prng == null) { var seed: u64 = undefined; io.random(std.mem.asBytes(&seed)); tls_prng = std.Random.DefaultPrng.init(seed); } return tls_prng.?.random(); } ``` ### Parallel Streams with Jump ```zig fn createParallelStreams(base_seed: u64, n: usize, allocator: std.mem.Allocator) ![]std.Random.Xoshiro256 { std.debug.assert(n != 0); const prngs = try allocator.alloc(std.Random.Xoshiro256, n); prngs[0] = std.Random.Xoshiro256.init(base_seed); for (prngs[1..], 1..) |*prng, i| { prng.* = prngs[i - 1]; prng.jump(); // advance 2^128 steps } return prngs; } ``` The caller owns the returned slice and must free it with the same allocator. ### Monte Carlo Simulation ```zig fn estimatePi(random: std.Random, samples: usize) f64 { var inside: usize = 0; for (0..samples) |_| { const x = random.float(f64); const y = random.float(f64); if (x * x + y * y <= 1.0) inside += 1; } return 4.0 * @as(f64, @floatFromInt(inside)) / @as(f64, @floatFromInt(samples)); } ``` ### Random Password Generator ```zig const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"; fn generatePassword(random: std.Random, buf: []u8) void { for (buf) |*c| { c.* = charset[random.uintLessThan(usize, charset.len)]; } } // Usage var password: [16]u8 = undefined; var source: std.Random.IoSource = .{ .io = io }; generatePassword(source.interface(), &password); ``` ### Gaussian Random with Box-Muller The built-in `floatNorm` uses ziggurat algorithm. For explicit Box-Muller: ```zig fn boxMullerPair(random: std.Random) struct { f64, f64 } { const u1 = 1.0 - random.float(f64); // (0, 1] const u2 = random.float(f64); // [0, 1) const r = @sqrt(-2.0 * @log(u1)); const theta = 2.0 * std.math.pi * u2; return .{ r * @cos(theta), r * @sin(theta) }; } ``` ## Custom PRNG Implementation Implement a custom PRNG by providing a `fill` function: ```zig const MyPrng = struct { state: u64, pub fn init(seed: u64) MyPrng { return .{ .state = seed }; } pub fn random(self: *MyPrng) std.Random { return std.Random.init(self, fill); } fn fill(self: *MyPrng, buf: []u8) void { for (buf) |*b| { // Simple LCG (not for production!) self.state = self.state *% 6364136223846793005 +% 1; b.* = @truncate(self.state >> 56); } } }; ``` ## Notes - `DefaultPrng` is `Xoshiro256` - fast, high quality, not cryptographic - `DefaultCsprng` is `ChaCha` - cryptographically secure with forward secrecy - For crypto: use caller-provided `std.Io` randomness, choosing `randomSecure` when fresh external entropy and explicit errors are required - `uintLessThan`/`intRangeLessThan` may reject values (not constant-time) - Use biased variants (`*Biased`) for timing-sensitive applications - `jump()` on Xoshiro256 advances 2^128 steps for parallel streams - `float()` returns values in [0, 1) covering all representable values - `floatNorm()` and `floatExp()` use efficient ziggurat algorithm