aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Random.zig
diff options
context:
space:
mode:
authore4m2 <git@e4m2.com>2024-02-08 14:43:20 +0100
committere4m2 <git@e4m2.com>2024-02-08 14:43:20 +0100
commit9af077d71eaf2c004d63405fc5c017e237cb9c6c (patch)
treee9a82339e59f49ae2b6fc57c893b3e16afaed9aa /lib/std/Random.zig
parent919a3bae1c5f2024b09e127a15c752d9dc0aa9a6 (diff)
downloadzig-9af077d71eaf2c004d63405fc5c017e237cb9c6c.tar.gz
zig-9af077d71eaf2c004d63405fc5c017e237cb9c6c.zip
std.rand: Move to std.Random
Diffstat (limited to 'lib/std/Random.zig')
-rw-r--r--lib/std/Random.zig438
1 files changed, 438 insertions, 0 deletions
diff --git a/lib/std/Random.zig b/lib/std/Random.zig
new file mode 100644
index 0000000000..8f4ee0872c
--- /dev/null
+++ b/lib/std/Random.zig
@@ -0,0 +1,438 @@
+//! The engines provided here should be initialized from an external source.
+//! For a thread-local cryptographically secure pseudo random number generator,
+//! use `std.crypto.random`.
+//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will
+//! be faster and use substantially less stack space.
+
+const std = @import("std.zig");
+const math = std.math;
+const mem = std.mem;
+const assert = std.debug.assert;
+const maxInt = std.math.maxInt;
+pub const Random = @This(); // Remove pub when `std.rand` namespace is removed.
+
+/// Fast unbiased random numbers.
+pub const DefaultPrng = Xoshiro256;
+
+/// Cryptographically secure random numbers.
+pub const DefaultCsprng = ChaCha;
+
+pub const Ascon = @import("Random/Ascon.zig");
+pub const ChaCha = @import("Random/ChaCha.zig");
+
+pub const Isaac64 = @import("Random/Isaac64.zig");
+pub const Pcg = @import("Random/Pcg.zig");
+pub const Xoroshiro128 = @import("Random/Xoroshiro128.zig");
+pub const Xoshiro256 = @import("Random/Xoshiro256.zig");
+pub const Sfc64 = @import("Random/Sfc64.zig");
+pub const RomuTrio = @import("Random/RomuTrio.zig");
+pub const SplitMix64 = @import("Random/Splitmix64.zig");
+pub const ziggurat = @import("Random/ziggurat.zig");
+
+ptr: *anyopaque,
+fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
+
+pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
+ const Ptr = @TypeOf(pointer);
+ assert(@typeInfo(Ptr) == .Pointer); // Must be a pointer
+ assert(@typeInfo(Ptr).Pointer.size == .One); // Must be a single-item pointer
+ assert(@typeInfo(@typeInfo(Ptr).Pointer.child) == .Struct); // Must point to a struct
+ const gen = struct {
+ fn fill(ptr: *anyopaque, buf: []u8) void {
+ const self: Ptr = @ptrCast(@alignCast(ptr));
+ fillFn(self, buf);
+ }
+ };
+
+ return .{
+ .ptr = pointer,
+ .fillFn = gen.fill,
+ };
+}
+
+/// Read random bytes into the specified buffer until full.
+pub fn bytes(r: Random, buf: []u8) void {
+ r.fillFn(r.ptr, buf);
+}
+
+pub fn boolean(r: Random) bool {
+ return r.int(u1) != 0;
+}
+
+/// Returns a random value from an enum, evenly distributed.
+///
+/// Note that this will not yield consistent results across all targets
+/// due to dependence on the representation of `usize` as an index.
+/// See `enumValueWithIndex` for further commentary.
+pub inline fn enumValue(r: Random, comptime EnumType: type) EnumType {
+ return r.enumValueWithIndex(EnumType, usize);
+}
+
+/// Returns a random value from an enum, evenly distributed.
+///
+/// An index into an array of all named values is generated using the
+/// specified `Index` type to determine the return value.
+/// This allows for results to be independent of `usize` representation.
+///
+/// Prefer `enumValue` if this isn't important.
+///
+/// See `uintLessThan`, which this function uses in most cases,
+/// for commentary on the runtime of this function.
+pub fn enumValueWithIndex(r: Random, comptime EnumType: type, comptime Index: type) EnumType {
+ comptime assert(@typeInfo(EnumType) == .Enum);
+
+ // We won't use int -> enum casting because enum elements can have
+ // arbitrary values. Instead we'll randomly pick one of the type's values.
+ const values = comptime std.enums.values(EnumType);
+ comptime assert(values.len > 0); // can't return anything
+ comptime assert(maxInt(Index) >= values.len - 1); // can't access all values
+ comptime if (values.len == 1) return values[0];
+
+ const index = if (comptime values.len - 1 == maxInt(Index))
+ r.int(Index)
+ else
+ r.uintLessThan(Index, values.len);
+
+ const MinInt = MinArrayIndex(Index);
+ return values[@as(MinInt, @intCast(index))];
+}
+
+/// Returns a random int `i` such that `minInt(T) <= i <= maxInt(T)`.
+/// `i` is evenly distributed.
+pub fn int(r: Random, comptime T: type) T {
+ const bits = @typeInfo(T).Int.bits;
+ const UnsignedT = std.meta.Int(.unsigned, bits);
+ const ceil_bytes = comptime std.math.divCeil(u16, bits, 8) catch unreachable;
+ const ByteAlignedT = std.meta.Int(.unsigned, ceil_bytes * 8);
+
+ var rand_bytes: [ceil_bytes]u8 = undefined;
+ r.bytes(&rand_bytes);
+
+ // use LE instead of native endian for better portability maybe?
+ // TODO: endian portability is pointless if the underlying prng isn't endian portable.
+ // TODO: document the endian portability of this library.
+ const byte_aligned_result = mem.readInt(ByteAlignedT, &rand_bytes, .little);
+ const unsigned_result: UnsignedT = @truncate(byte_aligned_result);
+ return @bitCast(unsigned_result);
+}
+
+/// Constant-time implementation off `uintLessThan`.
+/// The results of this function may be biased.
+pub fn uintLessThanBiased(r: Random, comptime T: type, less_than: T) T {
+ comptime assert(@typeInfo(T).Int.signedness == .unsigned);
+ assert(0 < less_than);
+ return limitRangeBiased(T, r.int(T), less_than);
+}
+
+/// Returns an evenly distributed random unsigned integer `0 <= i < less_than`.
+/// This function assumes that the underlying `fillFn` produces evenly distributed values.
+/// Within this assumption, the runtime of this function is exponentially distributed.
+/// If `fillFn` were backed by a true random generator,
+/// the runtime of this function would technically be unbounded.
+/// However, if `fillFn` is backed by any evenly distributed pseudo random number generator,
+/// this function is guaranteed to return.
+/// If you need deterministic runtime bounds, use `uintLessThanBiased`.
+pub fn uintLessThan(r: Random, comptime T: type, less_than: T) T {
+ comptime assert(@typeInfo(T).Int.signedness == .unsigned);
+ const bits = @typeInfo(T).Int.bits;
+ assert(0 < less_than);
+
+ // adapted from:
+ // http://www.pcg-random.org/posts/bounded-rands.html
+ // "Lemire's (with an extra tweak from me)"
+ var x = r.int(T);
+ var m = math.mulWide(T, x, less_than);
+ var l: T = @truncate(m);
+ if (l < less_than) {
+ var t = -%less_than;
+
+ if (t >= less_than) {
+ t -= less_than;
+ if (t >= less_than) {
+ t %= less_than;
+ }
+ }
+ while (l < t) {
+ x = r.int(T);
+ m = math.mulWide(T, x, less_than);
+ l = @truncate(m);
+ }
+ }
+ return @intCast(m >> bits);
+}
+
+/// Constant-time implementation off `uintAtMost`.
+/// The results of this function may be biased.
+pub fn uintAtMostBiased(r: Random, comptime T: type, at_most: T) T {
+ assert(@typeInfo(T).Int.signedness == .unsigned);
+ if (at_most == maxInt(T)) {
+ // have the full range
+ return r.int(T);
+ }
+ return r.uintLessThanBiased(T, at_most + 1);
+}
+
+/// Returns an evenly distributed random unsigned integer `0 <= i <= at_most`.
+/// See `uintLessThan`, which this function uses in most cases,
+/// for commentary on the runtime of this function.
+pub fn uintAtMost(r: Random, comptime T: type, at_most: T) T {
+ assert(@typeInfo(T).Int.signedness == .unsigned);
+ if (at_most == maxInt(T)) {
+ // have the full range
+ return r.int(T);
+ }
+ return r.uintLessThan(T, at_most + 1);
+}
+
+/// Constant-time implementation off `intRangeLessThan`.
+/// The results of this function may be biased.
+pub fn intRangeLessThanBiased(r: Random, comptime T: type, at_least: T, less_than: T) T {
+ assert(at_least < less_than);
+ const info = @typeInfo(T).Int;
+ if (info.signedness == .signed) {
+ // Two's complement makes this math pretty easy.
+ const UnsignedT = std.meta.Int(.unsigned, info.bits);
+ const lo: UnsignedT = @bitCast(at_least);
+ const hi: UnsignedT = @bitCast(less_than);
+ const result = lo +% r.uintLessThanBiased(UnsignedT, hi -% lo);
+ return @bitCast(result);
+ } else {
+ // The signed implementation would work fine, but we can use stricter arithmetic operators here.
+ return at_least + r.uintLessThanBiased(T, less_than - at_least);
+ }
+}
+
+/// Returns an evenly distributed random integer `at_least <= i < less_than`.
+/// See `uintLessThan`, which this function uses in most cases,
+/// for commentary on the runtime of this function.
+pub fn intRangeLessThan(r: Random, comptime T: type, at_least: T, less_than: T) T {
+ assert(at_least < less_than);
+ const info = @typeInfo(T).Int;
+ if (info.signedness == .signed) {
+ // Two's complement makes this math pretty easy.
+ const UnsignedT = std.meta.Int(.unsigned, info.bits);
+ const lo: UnsignedT = @bitCast(at_least);
+ const hi: UnsignedT = @bitCast(less_than);
+ const result = lo +% r.uintLessThan(UnsignedT, hi -% lo);
+ return @bitCast(result);
+ } else {
+ // The signed implementation would work fine, but we can use stricter arithmetic operators here.
+ return at_least + r.uintLessThan(T, less_than - at_least);
+ }
+}
+
+/// Constant-time implementation off `intRangeAtMostBiased`.
+/// The results of this function may be biased.
+pub fn intRangeAtMostBiased(r: Random, comptime T: type, at_least: T, at_most: T) T {
+ assert(at_least <= at_most);
+ const info = @typeInfo(T).Int;
+ if (info.signedness == .signed) {
+ // Two's complement makes this math pretty easy.
+ const UnsignedT = std.meta.Int(.unsigned, info.bits);
+ const lo: UnsignedT = @bitCast(at_least);
+ const hi: UnsignedT = @bitCast(at_most);
+ const result = lo +% r.uintAtMostBiased(UnsignedT, hi -% lo);
+ return @bitCast(result);
+ } else {
+ // The signed implementation would work fine, but we can use stricter arithmetic operators here.
+ return at_least + r.uintAtMostBiased(T, at_most - at_least);
+ }
+}
+
+/// Returns an evenly distributed random integer `at_least <= i <= at_most`.
+/// See `uintLessThan`, which this function uses in most cases,
+/// for commentary on the runtime of this function.
+pub fn intRangeAtMost(r: Random, comptime T: type, at_least: T, at_most: T) T {
+ assert(at_least <= at_most);
+ const info = @typeInfo(T).Int;
+ if (info.signedness == .signed) {
+ // Two's complement makes this math pretty easy.
+ const UnsignedT = std.meta.Int(.unsigned, info.bits);
+ const lo: UnsignedT = @bitCast(at_least);
+ const hi: UnsignedT = @bitCast(at_most);
+ const result = lo +% r.uintAtMost(UnsignedT, hi -% lo);
+ return @bitCast(result);
+ } else {
+ // The signed implementation would work fine, but we can use stricter arithmetic operators here.
+ return at_least + r.uintAtMost(T, at_most - at_least);
+ }
+}
+
+/// Return a floating point value evenly distributed in the range [0, 1).
+pub fn float(r: Random, comptime T: type) T {
+ // Generate a uniformly random value for the mantissa.
+ // Then generate an exponentially biased random value for the exponent.
+ // This covers every possible value in the range.
+ switch (T) {
+ f32 => {
+ // Use 23 random bits for the mantissa, and the rest for the exponent.
+ // If all 41 bits are zero, generate additional random bits, until a
+ // set bit is found, or 126 bits have been generated.
+ const rand = r.int(u64);
+ var rand_lz = @clz(rand);
+ if (rand_lz >= 41) {
+ // TODO: when #5177 or #489 is implemented,
+ // tell the compiler it is unlikely (1/2^41) to reach this point.
+ // (Same for the if branch and the f64 calculations below.)
+ rand_lz = 41 + @clz(r.int(u64));
+ if (rand_lz == 41 + 64) {
+ // It is astronomically unlikely to reach this point.
+ rand_lz += @clz(r.int(u32) | 0x7FF);
+ }
+ }
+ const mantissa: u23 = @truncate(rand);
+ const exponent = @as(u32, 126 - rand_lz) << 23;
+ return @bitCast(exponent | mantissa);
+ },
+ f64 => {
+ // Use 52 random bits for the mantissa, and the rest for the exponent.
+ // If all 12 bits are zero, generate additional random bits, until a
+ // set bit is found, or 1022 bits have been generated.
+ const rand = r.int(u64);
+ var rand_lz: u64 = @clz(rand);
+ if (rand_lz >= 12) {
+ rand_lz = 12;
+ while (true) {
+ // It is astronomically unlikely for this loop to execute more than once.
+ const addl_rand_lz = @clz(r.int(u64));
+ rand_lz += addl_rand_lz;
+ if (addl_rand_lz != 64) {
+ break;
+ }
+ if (rand_lz >= 1022) {
+ rand_lz = 1022;
+ break;
+ }
+ }
+ }
+ const mantissa = rand & 0xFFFFFFFFFFFFF;
+ const exponent = (1022 - rand_lz) << 52;
+ return @bitCast(exponent | mantissa);
+ },
+ else => @compileError("unknown floating point type"),
+ }
+}
+
+/// Return a floating point value normally distributed with mean = 0, stddev = 1.
+///
+/// To use different parameters, use: floatNorm(...) * desiredStddev + desiredMean.
+pub fn floatNorm(r: Random, comptime T: type) T {
+ const value = ziggurat.next_f64(r, ziggurat.NormDist);
+ switch (T) {
+ f32 => return @floatCast(value),
+ f64 => return value,
+ else => @compileError("unknown floating point type"),
+ }
+}
+
+/// Return an exponentially distributed float with a rate parameter of 1.
+///
+/// To use a different rate parameter, use: floatExp(...) / desiredRate.
+pub fn floatExp(r: Random, comptime T: type) T {
+ const value = ziggurat.next_f64(r, ziggurat.ExpDist);
+ switch (T) {
+ f32 => return @floatCast(value),
+ f64 => return value,
+ else => @compileError("unknown floating point type"),
+ }
+}
+
+/// Shuffle a slice into a random order.
+///
+/// Note that this will not yield consistent results across all targets
+/// due to dependence on the representation of `usize` as an index.
+/// See `shuffleWithIndex` for further commentary.
+pub inline fn shuffle(r: Random, comptime T: type, buf: []T) void {
+ r.shuffleWithIndex(T, buf, usize);
+}
+
+/// Shuffle a slice into a random order, using an index of a
+/// specified type to maintain distribution across targets.
+/// Asserts the index type can represent `buf.len`.
+///
+/// Indexes into the slice are generated using the specified `Index`
+/// type, which determines distribution properties. This allows for
+/// results to be independent of `usize` representation.
+///
+/// Prefer `shuffle` if this isn't important.
+///
+/// See `intRangeLessThan`, which this function uses,
+/// for commentary on the runtime of this function.
+pub fn shuffleWithIndex(r: Random, comptime T: type, buf: []T, comptime Index: type) void {
+ const MinInt = MinArrayIndex(Index);
+ if (buf.len < 2) {
+ return;
+ }
+
+ // `i <= j < max <= maxInt(MinInt)`
+ const max: MinInt = @intCast(buf.len);
+ var i: MinInt = 0;
+ while (i < max - 1) : (i += 1) {
+ const j: MinInt = @intCast(r.intRangeLessThan(Index, i, max));
+ mem.swap(T, &buf[i], &buf[j]);
+ }
+}
+
+/// Randomly selects an index into `proportions`, where the likelihood of each
+/// index is weighted by that proportion.
+/// It is more likely for the index of the last proportion to be returned
+/// than the index of the first proportion in the slice, and vice versa.
+///
+/// This is useful for selecting an item from a slice where weights are not equal.
+/// `T` must be a numeric type capable of holding the sum of `proportions`.
+pub fn weightedIndex(r: std.rand.Random, comptime T: type, proportions: []const T) usize {
+ // This implementation works by summing the proportions and picking a
+ // random point in [0, sum). We then loop over the proportions,
+ // accumulating until our accumulator is greater than the random point.
+
+ const sum = s: {
+ var sum: T = 0;
+ for (proportions) |v| sum += v;
+ break :s sum;
+ };
+
+ const point = switch (@typeInfo(T)) {
+ .Int => |int_info| switch (int_info.signedness) {
+ .signed => r.intRangeLessThan(T, 0, sum),
+ .unsigned => r.uintLessThan(T, sum),
+ },
+ // take care that imprecision doesn't lead to a value slightly greater than sum
+ .Float => @min(r.float(T) * sum, sum - std.math.floatEps(T)),
+ else => @compileError("weightedIndex does not support proportions of type " ++
+ @typeName(T)),
+ };
+
+ assert(point < sum);
+
+ var accumulator: T = 0;
+ for (proportions, 0..) |p, index| {
+ accumulator += p;
+ if (point < accumulator) return index;
+ } else unreachable;
+}
+
+/// Convert a random integer 0 <= random_int <= maxValue(T),
+/// into an integer 0 <= result < less_than.
+/// This function introduces a minor bias.
+pub fn limitRangeBiased(comptime T: type, random_int: T, less_than: T) T {
+ comptime assert(@typeInfo(T).Int.signedness == .unsigned);
+ const bits = @typeInfo(T).Int.bits;
+
+ // adapted from:
+ // http://www.pcg-random.org/posts/bounded-rands.html
+ // "Integer Multiplication (Biased)"
+ const m = math.mulWide(T, random_int, less_than);
+ return @intCast(m >> bits);
+}
+
+/// Returns the smallest of `Index` and `usize`.
+fn MinArrayIndex(comptime Index: type) type {
+ const index_info = @typeInfo(Index).Int;
+ assert(index_info.signedness == .unsigned);
+ return if (index_info.bits >= @typeInfo(usize).Int.bits) usize else Index;
+}
+
+test {
+ std.testing.refAllDecls(@This());
+ _ = @import("Random/test.zig");
+}