//! 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. //! //! TODO(tiehuis): Benchmark these against other reference implementations. 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 ByteAlignedT = std.meta.Int(.unsigned, @divTrunc(bits + 7, 8) * 8); var rand_bytes: [@sizeOf(ByteAlignedT)]u8 = undefined; r.bytes(rand_bytes[0..]); // 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.readIntSliceLittle(ByteAlignedT, &rand_bytes); const unsigned_result = @as(UnsignedT, @truncate(byte_aligned_result)); return @as(T, @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); const bits = @typeInfo(T).Int.bits; comptime assert(bits <= 64); // TODO: workaround: LLVM ERROR: Unsupported library call operation! assert(0 < less_than); if (bits <= 32) { return @as(T, @intCast(limitRangeBiased(u32, r.int(u32), less_than))); } else { return @as(T, @intCast(limitRangeBiased(u64, r.int(u64), 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; comptime assert(bits <= 64); // TODO: workaround: LLVM ERROR: Unsupported library call operation! assert(0 < less_than); // Small is typically u32 const small_bits = @divTrunc(bits + 31, 32) * 32; const Small = std.meta.Int(.unsigned, small_bits); // Large is typically u64 const Large = std.meta.Int(.unsigned, small_bits * 2); // adapted from: // http://www.pcg-random.org/posts/bounded-rands.html // "Lemire's (with an extra tweak from me)" var x: Small = r.int(Small); var m: Large = @as(Large, x) * @as(Large, less_than); var l: Small = @as(Small, @truncate(m)); if (l < less_than) { var t: Small = -%less_than; if (t >= less_than) { t -= less_than; if (t >= less_than) { t %= less_than; } } while (l < t) { x = r.int(Small); m = @as(Large, x) * @as(Large, less_than); l = @as(Small, @truncate(m)); } } return @as(T, @intCast(m >> small_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 = @as(UnsignedT, @bitCast(at_least)); const hi = @as(UnsignedT, @bitCast(less_than)); const result = lo +% r.uintLessThanBiased(UnsignedT, hi -% lo); return @as(T, @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 = @as(UnsignedT, @bitCast(at_least)); const hi = @as(UnsignedT, @bitCast(less_than)); const result = lo +% r.uintLessThan(UnsignedT, hi -% lo); return @as(T, @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 = @as(UnsignedT, @bitCast(at_least)); const hi = @as(UnsignedT, @bitCast(at_most)); const result = lo +% r.uintAtMostBiased(UnsignedT, hi -% lo); return @as(T, @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 = @as(UnsignedT, @bitCast(at_least)); const hi = @as(UnsignedT, @bitCast(at_most)); const result = lo +% r.uintAtMost(UnsignedT, hi -% lo); return @as(T, @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 = @as(u23, @truncate(rand)); const exponent = @as(u32, 126 - rand_lz) << 23; return @as(f32, @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 @as(f64, @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 @as(f32, @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 @as(f32, @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 = @as(MinInt, @intCast(buf.len)); var i: MinInt = 0; while (i < max - 1) : (i += 1) { const j = @as(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. var sum: T = 0; for (proportions) |v| { sum += v; } const point = if (comptime std.meta.trait.isSignedInt(T)) r.intRangeLessThan(T, 0, sum) else if (comptime std.meta.trait.isUnsignedInt(T)) r.uintLessThan(T, sum) else if (comptime std.meta.trait.isFloat(T)) // take care that imprecision doesn't lead to a value slightly greater than sum @min(r.float(T) * sum, sum - std.math.floatEps(T)) else @compileError("weightedIndex does not support proportions of type " ++ @typeName(T)); std.debug.assert(point < sum); var accumulator: T = 0; for (proportions, 0..) |p, index| { accumulator += p; if (point < accumulator) return index; } 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; const T2 = std.meta.Int(.unsigned, bits * 2); // adapted from: // http://www.pcg-random.org/posts/bounded-rands.html // "Integer Multiplication (Biased)" var m: T2 = @as(T2, random_int) * @as(T2, less_than); return @as(T, @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"); }