From 9af077d71eaf2c004d63405fc5c017e237cb9c6c Mon Sep 17 00:00:00 2001 From: e4m2 Date: Thu, 8 Feb 2024 14:43:20 +0100 Subject: [PATCH] std.rand: Move to std.Random --- lib/std/Random.zig | 438 ++++++++++++++++++++ lib/std/{rand => Random}/Ascon.zig | 0 lib/std/{rand => Random}/ChaCha.zig | 0 lib/std/{rand => Random}/Isaac64.zig | 0 lib/std/{rand => Random}/Pcg.zig | 0 lib/std/{rand => Random}/RomuTrio.zig | 0 lib/std/{rand => Random}/Sfc64.zig | 0 lib/std/Random/SplitMix64.zig | 21 + lib/std/{rand => Random}/Xoroshiro128.zig | 0 lib/std/{rand => Random}/Xoshiro256.zig | 0 lib/std/{rand => Random}/benchmark.zig | 0 lib/std/{rand => Random}/test.zig | 8 +- lib/std/{rand => Random}/ziggurat.zig | 0 lib/std/rand.zig | 460 ---------------------- lib/std/std.zig | 5 +- 15 files changed, 466 insertions(+), 466 deletions(-) create mode 100644 lib/std/Random.zig rename lib/std/{rand => Random}/Ascon.zig (100%) rename lib/std/{rand => Random}/ChaCha.zig (100%) rename lib/std/{rand => Random}/Isaac64.zig (100%) rename lib/std/{rand => Random}/Pcg.zig (100%) rename lib/std/{rand => Random}/RomuTrio.zig (100%) rename lib/std/{rand => Random}/Sfc64.zig (100%) create mode 100644 lib/std/Random/SplitMix64.zig rename lib/std/{rand => Random}/Xoroshiro128.zig (100%) rename lib/std/{rand => Random}/Xoshiro256.zig (100%) rename lib/std/{rand => Random}/benchmark.zig (100%) rename lib/std/{rand => Random}/test.zig (98%) rename lib/std/{rand => Random}/ziggurat.zig (100%) delete mode 100644 lib/std/rand.zig 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"); +} diff --git a/lib/std/rand/Ascon.zig b/lib/std/Random/Ascon.zig similarity index 100% rename from lib/std/rand/Ascon.zig rename to lib/std/Random/Ascon.zig diff --git a/lib/std/rand/ChaCha.zig b/lib/std/Random/ChaCha.zig similarity index 100% rename from lib/std/rand/ChaCha.zig rename to lib/std/Random/ChaCha.zig diff --git a/lib/std/rand/Isaac64.zig b/lib/std/Random/Isaac64.zig similarity index 100% rename from lib/std/rand/Isaac64.zig rename to lib/std/Random/Isaac64.zig diff --git a/lib/std/rand/Pcg.zig b/lib/std/Random/Pcg.zig similarity index 100% rename from lib/std/rand/Pcg.zig rename to lib/std/Random/Pcg.zig diff --git a/lib/std/rand/RomuTrio.zig b/lib/std/Random/RomuTrio.zig similarity index 100% rename from lib/std/rand/RomuTrio.zig rename to lib/std/Random/RomuTrio.zig diff --git a/lib/std/rand/Sfc64.zig b/lib/std/Random/Sfc64.zig similarity index 100% rename from lib/std/rand/Sfc64.zig rename to lib/std/Random/Sfc64.zig diff --git a/lib/std/Random/SplitMix64.zig b/lib/std/Random/SplitMix64.zig new file mode 100644 index 0000000000..9f085162e3 --- /dev/null +++ b/lib/std/Random/SplitMix64.zig @@ -0,0 +1,21 @@ +//! Generator to extend 64-bit seed values into longer sequences. +//! +//! The number of cycles is thus limited to 64-bits regardless of the engine, but this +//! is still plenty for practical purposes. + +const SplitMix64 = @This(); + +s: u64, + +pub fn init(seed: u64) SplitMix64 { + return SplitMix64{ .s = seed }; +} + +pub fn next(self: *SplitMix64) u64 { + self.s +%= 0x9e3779b97f4a7c15; + + var z = self.s; + z = (z ^ (z >> 30)) *% 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) *% 0x94d049bb133111eb; + return z ^ (z >> 31); +} diff --git a/lib/std/rand/Xoroshiro128.zig b/lib/std/Random/Xoroshiro128.zig similarity index 100% rename from lib/std/rand/Xoroshiro128.zig rename to lib/std/Random/Xoroshiro128.zig diff --git a/lib/std/rand/Xoshiro256.zig b/lib/std/Random/Xoshiro256.zig similarity index 100% rename from lib/std/rand/Xoshiro256.zig rename to lib/std/Random/Xoshiro256.zig diff --git a/lib/std/rand/benchmark.zig b/lib/std/Random/benchmark.zig similarity index 100% rename from lib/std/rand/benchmark.zig rename to lib/std/Random/benchmark.zig diff --git a/lib/std/rand/test.zig b/lib/std/Random/test.zig similarity index 98% rename from lib/std/rand/test.zig rename to lib/std/Random/test.zig index d498985097..8ceacbc934 100644 --- a/lib/std/rand/test.zig +++ b/lib/std/Random/test.zig @@ -1,9 +1,9 @@ const std = @import("../std.zig"); const math = std.math; -const DefaultPrng = std.rand.DefaultPrng; -const Random = std.rand.Random; -const SplitMix64 = std.rand.SplitMix64; -const DefaultCsprng = std.rand.DefaultCsprng; +const Random = std.Random; +const DefaultPrng = Random.DefaultPrng; +const SplitMix64 = Random.SplitMix64; +const DefaultCsprng = Random.DefaultCsprng; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; diff --git a/lib/std/rand/ziggurat.zig b/lib/std/Random/ziggurat.zig similarity index 100% rename from lib/std/rand/ziggurat.zig rename to lib/std/Random/ziggurat.zig diff --git a/lib/std/rand.zig b/lib/std/rand.zig deleted file mode 100644 index 708eed13be..0000000000 --- a/lib/std/rand.zig +++ /dev/null @@ -1,460 +0,0 @@ -//! 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 builtin = @import("builtin"); -const assert = std.debug.assert; -const mem = std.mem; -const math = std.math; -const maxInt = std.math.maxInt; - -/// Fast unbiased random numbers. -pub const DefaultPrng = Xoshiro256; - -/// Cryptographically secure random numbers. -pub const DefaultCsprng = ChaCha; - -pub const Ascon = @import("rand/Ascon.zig"); -pub const ChaCha = @import("rand/ChaCha.zig"); - -pub const Isaac64 = @import("rand/Isaac64.zig"); -pub const Pcg = @import("rand/Pcg.zig"); -pub const Xoroshiro128 = @import("rand/Xoroshiro128.zig"); -pub const Xoshiro256 = @import("rand/Xoshiro256.zig"); -pub const Sfc64 = @import("rand/Sfc64.zig"); -pub const RomuTrio = @import("rand/RomuTrio.zig"); -pub const ziggurat = @import("rand/ziggurat.zig"); - -pub const Random = struct { - 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; - } - - /// 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; - } -}; - -/// 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); -} - -// Generator to extend 64-bit seed values into longer sequences. -// -// The number of cycles is thus limited to 64-bits regardless of the engine, but this -// is still plenty for practical purposes. -pub const SplitMix64 = struct { - s: u64, - - pub fn init(seed: u64) SplitMix64 { - return SplitMix64{ .s = seed }; - } - - pub fn next(self: *SplitMix64) u64 { - self.s +%= 0x9e3779b97f4a7c15; - - var z = self.s; - z = (z ^ (z >> 30)) *% 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) *% 0x94d049bb133111eb; - return z ^ (z >> 31); - } -}; - -test { - std.testing.refAllDecls(@This()); - _ = @import("rand/test.zig"); -} diff --git a/lib/std/std.zig b/lib/std/std.zig index 047da005c3..6f60ad0f00 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -36,6 +36,7 @@ pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceE pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue; pub const Progress = @import("Progress.zig"); +pub const Random = @import("Random.zig"); pub const RingBuffer = @import("RingBuffer.zig"); pub const SegmentedList = @import("segmented_list.zig").SegmentedList; pub const SemanticVersion = @import("SemanticVersion.zig"); @@ -159,8 +160,8 @@ pub const pdb = @import("pdb.zig"); /// and spawning of child processes. pub const process = @import("process.zig"); -/// Fast pseudo-random number generators (i.e. not cryptographically secure). -pub const rand = @import("rand.zig"); +/// Deprecated: use `Random` instead. +pub const rand = Random; /// Sorting. pub const sort = @import("sort.zig");