authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-09 13:42:04-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-02-09 13:42:04-08:00
log32f30399e5cd42af2d670321979aa424803b1819
tree97963ae96b09538a4dc21dd964b955c807666842
parent54bbc73f8502fe073d385361ddb34a43d12eec39
parent60639ec83d0ccb7a26a19ae64452b00141d48354
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #18867 from e4m2/random

std.rand: Move to std.Random

50 files changed, 2422 insertions(+), 2430 deletions(-)

CMakeLists.txt+1-1
......@@ -299,7 +299,7 @@ set(ZIG_STAGE2_SOURCES
299299 "${CMAKE_SOURCE_DIR}/lib/std/Progress.zig"
300300 "${CMAKE_SOURCE_DIR}/lib/std/pdb.zig"
301301 "${CMAKE_SOURCE_DIR}/lib/std/process.zig"
302 "${CMAKE_SOURCE_DIR}/lib/std/rand.zig"
302 "${CMAKE_SOURCE_DIR}/lib/std/Random.zig"
303303 "${CMAKE_SOURCE_DIR}/lib/std/sort.zig"
304304 "${CMAKE_SOURCE_DIR}/lib/compiler_rt.zig"
305305 "${CMAKE_SOURCE_DIR}/lib/compiler_rt/absv.zig"
lib/build_runner.zig+2-2
......@@ -420,7 +420,7 @@ fn runStepNames(
420420
421421 const starting_steps = try arena.dupe(*Step, step_stack.keys());
422422
423 var rng = std.rand.DefaultPrng.init(seed);
423 var rng = std.Random.DefaultPrng.init(seed);
424424 const rand = rng.random();
425425 rand.shuffle(*Step, starting_steps);
426426
......@@ -836,7 +836,7 @@ fn constructGraphAndCheckForDependencyLoop(
836836 b: *std.Build,
837837 s: *Step,
838838 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
839 rand: std.rand.Random,
839 rand: std.Random,
840840) !void {
841841 switch (s.state) {
842842 .precheck_started => {
lib/compiler_rt/paritydi2_test.zig+1-1
......@@ -26,7 +26,7 @@ test "paritydi2" {
2626 try test__paritydi2(@bitCast(@as(u64, 0xffffffff_fffffffe)));
2727 try test__paritydi2(@bitCast(@as(u64, 0xffffffff_ffffffff)));
2828
29 const RndGen = std.rand.DefaultPrng;
29 const RndGen = std.Random.DefaultPrng;
3030 var rnd = RndGen.init(42);
3131 var i: u32 = 0;
3232 while (i < 10_000) : (i += 1) {
lib/compiler_rt/paritysi2_test.zig+1-1
......@@ -26,7 +26,7 @@ test "paritysi2" {
2626 try test__paritysi2(@bitCast(@as(u32, 0xfffffffe)));
2727 try test__paritysi2(@bitCast(@as(u32, 0xffffffff)));
2828
29 const RndGen = std.rand.DefaultPrng;
29 const RndGen = std.Random.DefaultPrng;
3030 var rnd = RndGen.init(42);
3131 var i: u32 = 0;
3232 while (i < 10_000) : (i += 1) {
lib/compiler_rt/parityti2_test.zig+1-1
......@@ -26,7 +26,7 @@ test "parityti2" {
2626 try test__parityti2(@bitCast(@as(u128, 0xffffffff_ffffffff_ffffffff_fffffffe)));
2727 try test__parityti2(@bitCast(@as(u128, 0xffffffff_ffffffff_ffffffff_ffffffff)));
2828
29 const RndGen = std.rand.DefaultPrng;
29 const RndGen = std.Random.DefaultPrng;
3030 var rnd = RndGen.init(42);
3131 var i: u32 = 0;
3232 while (i < 10_000) : (i += 1) {
lib/compiler_rt/popcountdi2_test.zig+1-1
......@@ -25,7 +25,7 @@ test "popcountdi2" {
2525 try test__popcountdi2(@as(i64, @bitCast(@as(u64, 0xffffffff_fffffffe))));
2626 try test__popcountdi2(@as(i64, @bitCast(@as(u64, 0xffffffff_ffffffff))));
2727
28 const RndGen = std.rand.DefaultPrng;
28 const RndGen = std.Random.DefaultPrng;
2929 var rnd = RndGen.init(42);
3030 var i: u32 = 0;
3131 while (i < 10_000) : (i += 1) {
lib/compiler_rt/popcountsi2_test.zig+1-1
......@@ -25,7 +25,7 @@ test "popcountsi2" {
2525 try test__popcountsi2(@as(i32, @bitCast(@as(u32, 0xfffffffe))));
2626 try test__popcountsi2(@as(i32, @bitCast(@as(u32, 0xffffffff))));
2727
28 const RndGen = std.rand.DefaultPrng;
28 const RndGen = std.Random.DefaultPrng;
2929 var rnd = RndGen.init(42);
3030 var i: u32 = 0;
3131 while (i < 10_000) : (i += 1) {
lib/compiler_rt/popcountti2_test.zig+1-1
......@@ -25,7 +25,7 @@ test "popcountti2" {
2525 try test__popcountti2(@as(i128, @bitCast(@as(u128, 0xffffffff_ffffffff_ffffffff_fffffffe))));
2626 try test__popcountti2(@as(i128, @bitCast(@as(u128, 0xffffffff_ffffffff_ffffffff_ffffffff))));
2727
28 const RndGen = std.rand.DefaultPrng;
28 const RndGen = std.Random.DefaultPrng;
2929 var rnd = RndGen.init(42);
3030 var i: u32 = 0;
3131 while (i < 10_000) : (i += 1) {
lib/compiler_rt/udivmodei4.zig+1-1
......@@ -132,7 +132,7 @@ test "__udivei4/__umodei4" {
132132 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
133133 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
134134
135 const RndGen = std.rand.DefaultPrng;
135 const RndGen = std.Random.DefaultPrng;
136136 var rnd = RndGen.init(42);
137137 var i: usize = 10000;
138138 while (i > 0) : (i -= 1) {
lib/std/Random.zig created+438
......@@ -0,0 +1,438 @@
1//! The engines provided here should be initialized from an external source.
2//! For a thread-local cryptographically secure pseudo random number generator,
3//! use `std.crypto.random`.
4//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will
5//! be faster and use substantially less stack space.
6
7const std = @import("std.zig");
8const math = std.math;
9const mem = std.mem;
10const assert = std.debug.assert;
11const maxInt = std.math.maxInt;
12pub const Random = @This(); // Remove pub when `std.rand` namespace is removed.
13
14/// Fast unbiased random numbers.
15pub const DefaultPrng = Xoshiro256;
16
17/// Cryptographically secure random numbers.
18pub const DefaultCsprng = ChaCha;
19
20pub const Ascon = @import("Random/Ascon.zig");
21pub const ChaCha = @import("Random/ChaCha.zig");
22
23pub const Isaac64 = @import("Random/Isaac64.zig");
24pub const Pcg = @import("Random/Pcg.zig");
25pub const Xoroshiro128 = @import("Random/Xoroshiro128.zig");
26pub const Xoshiro256 = @import("Random/Xoshiro256.zig");
27pub const Sfc64 = @import("Random/Sfc64.zig");
28pub const RomuTrio = @import("Random/RomuTrio.zig");
29pub const SplitMix64 = @import("Random/SplitMix64.zig");
30pub const ziggurat = @import("Random/ziggurat.zig");
31
32ptr: *anyopaque,
33fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
34
35pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
36 const Ptr = @TypeOf(pointer);
37 assert(@typeInfo(Ptr) == .Pointer); // Must be a pointer
38 assert(@typeInfo(Ptr).Pointer.size == .One); // Must be a single-item pointer
39 assert(@typeInfo(@typeInfo(Ptr).Pointer.child) == .Struct); // Must point to a struct
40 const gen = struct {
41 fn fill(ptr: *anyopaque, buf: []u8) void {
42 const self: Ptr = @ptrCast(@alignCast(ptr));
43 fillFn(self, buf);
44 }
45 };
46
47 return .{
48 .ptr = pointer,
49 .fillFn = gen.fill,
50 };
51}
52
53/// Read random bytes into the specified buffer until full.
54pub fn bytes(r: Random, buf: []u8) void {
55 r.fillFn(r.ptr, buf);
56}
57
58pub fn boolean(r: Random) bool {
59 return r.int(u1) != 0;
60}
61
62/// Returns a random value from an enum, evenly distributed.
63///
64/// Note that this will not yield consistent results across all targets
65/// due to dependence on the representation of `usize` as an index.
66/// See `enumValueWithIndex` for further commentary.
67pub inline fn enumValue(r: Random, comptime EnumType: type) EnumType {
68 return r.enumValueWithIndex(EnumType, usize);
69}
70
71/// Returns a random value from an enum, evenly distributed.
72///
73/// An index into an array of all named values is generated using the
74/// specified `Index` type to determine the return value.
75/// This allows for results to be independent of `usize` representation.
76///
77/// Prefer `enumValue` if this isn't important.
78///
79/// See `uintLessThan`, which this function uses in most cases,
80/// for commentary on the runtime of this function.
81pub fn enumValueWithIndex(r: Random, comptime EnumType: type, comptime Index: type) EnumType {
82 comptime assert(@typeInfo(EnumType) == .Enum);
83
84 // We won't use int -> enum casting because enum elements can have
85 // arbitrary values. Instead we'll randomly pick one of the type's values.
86 const values = comptime std.enums.values(EnumType);
87 comptime assert(values.len > 0); // can't return anything
88 comptime assert(maxInt(Index) >= values.len - 1); // can't access all values
89 comptime if (values.len == 1) return values[0];
90
91 const index = if (comptime values.len - 1 == maxInt(Index))
92 r.int(Index)
93 else
94 r.uintLessThan(Index, values.len);
95
96 const MinInt = MinArrayIndex(Index);
97 return values[@as(MinInt, @intCast(index))];
98}
99
100/// Returns a random int `i` such that `minInt(T) <= i <= maxInt(T)`.
101/// `i` is evenly distributed.
102pub fn int(r: Random, comptime T: type) T {
103 const bits = @typeInfo(T).Int.bits;
104 const UnsignedT = std.meta.Int(.unsigned, bits);
105 const ceil_bytes = comptime std.math.divCeil(u16, bits, 8) catch unreachable;
106 const ByteAlignedT = std.meta.Int(.unsigned, ceil_bytes * 8);
107
108 var rand_bytes: [ceil_bytes]u8 = undefined;
109 r.bytes(&rand_bytes);
110
111 // use LE instead of native endian for better portability maybe?
112 // TODO: endian portability is pointless if the underlying prng isn't endian portable.
113 // TODO: document the endian portability of this library.
114 const byte_aligned_result = mem.readInt(ByteAlignedT, &rand_bytes, .little);
115 const unsigned_result: UnsignedT = @truncate(byte_aligned_result);
116 return @bitCast(unsigned_result);
117}
118
119/// Constant-time implementation off `uintLessThan`.
120/// The results of this function may be biased.
121pub fn uintLessThanBiased(r: Random, comptime T: type, less_than: T) T {
122 comptime assert(@typeInfo(T).Int.signedness == .unsigned);
123 assert(0 < less_than);
124 return limitRangeBiased(T, r.int(T), less_than);
125}
126
127/// Returns an evenly distributed random unsigned integer `0 <= i < less_than`.
128/// This function assumes that the underlying `fillFn` produces evenly distributed values.
129/// Within this assumption, the runtime of this function is exponentially distributed.
130/// If `fillFn` were backed by a true random generator,
131/// the runtime of this function would technically be unbounded.
132/// However, if `fillFn` is backed by any evenly distributed pseudo random number generator,
133/// this function is guaranteed to return.
134/// If you need deterministic runtime bounds, use `uintLessThanBiased`.
135pub fn uintLessThan(r: Random, comptime T: type, less_than: T) T {
136 comptime assert(@typeInfo(T).Int.signedness == .unsigned);
137 const bits = @typeInfo(T).Int.bits;
138 assert(0 < less_than);
139
140 // adapted from:
141 // http://www.pcg-random.org/posts/bounded-rands.html
142 // "Lemire's (with an extra tweak from me)"
143 var x = r.int(T);
144 var m = math.mulWide(T, x, less_than);
145 var l: T = @truncate(m);
146 if (l < less_than) {
147 var t = -%less_than;
148
149 if (t >= less_than) {
150 t -= less_than;
151 if (t >= less_than) {
152 t %= less_than;
153 }
154 }
155 while (l < t) {
156 x = r.int(T);
157 m = math.mulWide(T, x, less_than);
158 l = @truncate(m);
159 }
160 }
161 return @intCast(m >> bits);
162}
163
164/// Constant-time implementation off `uintAtMost`.
165/// The results of this function may be biased.
166pub fn uintAtMostBiased(r: Random, comptime T: type, at_most: T) T {
167 assert(@typeInfo(T).Int.signedness == .unsigned);
168 if (at_most == maxInt(T)) {
169 // have the full range
170 return r.int(T);
171 }
172 return r.uintLessThanBiased(T, at_most + 1);
173}
174
175/// Returns an evenly distributed random unsigned integer `0 <= i <= at_most`.
176/// See `uintLessThan`, which this function uses in most cases,
177/// for commentary on the runtime of this function.
178pub fn uintAtMost(r: Random, comptime T: type, at_most: T) T {
179 assert(@typeInfo(T).Int.signedness == .unsigned);
180 if (at_most == maxInt(T)) {
181 // have the full range
182 return r.int(T);
183 }
184 return r.uintLessThan(T, at_most + 1);
185}
186
187/// Constant-time implementation off `intRangeLessThan`.
188/// The results of this function may be biased.
189pub fn intRangeLessThanBiased(r: Random, comptime T: type, at_least: T, less_than: T) T {
190 assert(at_least < less_than);
191 const info = @typeInfo(T).Int;
192 if (info.signedness == .signed) {
193 // Two's complement makes this math pretty easy.
194 const UnsignedT = std.meta.Int(.unsigned, info.bits);
195 const lo: UnsignedT = @bitCast(at_least);
196 const hi: UnsignedT = @bitCast(less_than);
197 const result = lo +% r.uintLessThanBiased(UnsignedT, hi -% lo);
198 return @bitCast(result);
199 } else {
200 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
201 return at_least + r.uintLessThanBiased(T, less_than - at_least);
202 }
203}
204
205/// Returns an evenly distributed random integer `at_least <= i < less_than`.
206/// See `uintLessThan`, which this function uses in most cases,
207/// for commentary on the runtime of this function.
208pub fn intRangeLessThan(r: Random, comptime T: type, at_least: T, less_than: T) T {
209 assert(at_least < less_than);
210 const info = @typeInfo(T).Int;
211 if (info.signedness == .signed) {
212 // Two's complement makes this math pretty easy.
213 const UnsignedT = std.meta.Int(.unsigned, info.bits);
214 const lo: UnsignedT = @bitCast(at_least);
215 const hi: UnsignedT = @bitCast(less_than);
216 const result = lo +% r.uintLessThan(UnsignedT, hi -% lo);
217 return @bitCast(result);
218 } else {
219 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
220 return at_least + r.uintLessThan(T, less_than - at_least);
221 }
222}
223
224/// Constant-time implementation off `intRangeAtMostBiased`.
225/// The results of this function may be biased.
226pub fn intRangeAtMostBiased(r: Random, comptime T: type, at_least: T, at_most: T) T {
227 assert(at_least <= at_most);
228 const info = @typeInfo(T).Int;
229 if (info.signedness == .signed) {
230 // Two's complement makes this math pretty easy.
231 const UnsignedT = std.meta.Int(.unsigned, info.bits);
232 const lo: UnsignedT = @bitCast(at_least);
233 const hi: UnsignedT = @bitCast(at_most);
234 const result = lo +% r.uintAtMostBiased(UnsignedT, hi -% lo);
235 return @bitCast(result);
236 } else {
237 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
238 return at_least + r.uintAtMostBiased(T, at_most - at_least);
239 }
240}
241
242/// Returns an evenly distributed random integer `at_least <= i <= at_most`.
243/// See `uintLessThan`, which this function uses in most cases,
244/// for commentary on the runtime of this function.
245pub fn intRangeAtMost(r: Random, comptime T: type, at_least: T, at_most: T) T {
246 assert(at_least <= at_most);
247 const info = @typeInfo(T).Int;
248 if (info.signedness == .signed) {
249 // Two's complement makes this math pretty easy.
250 const UnsignedT = std.meta.Int(.unsigned, info.bits);
251 const lo: UnsignedT = @bitCast(at_least);
252 const hi: UnsignedT = @bitCast(at_most);
253 const result = lo +% r.uintAtMost(UnsignedT, hi -% lo);
254 return @bitCast(result);
255 } else {
256 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
257 return at_least + r.uintAtMost(T, at_most - at_least);
258 }
259}
260
261/// Return a floating point value evenly distributed in the range [0, 1).
262pub fn float(r: Random, comptime T: type) T {
263 // Generate a uniformly random value for the mantissa.
264 // Then generate an exponentially biased random value for the exponent.
265 // This covers every possible value in the range.
266 switch (T) {
267 f32 => {
268 // Use 23 random bits for the mantissa, and the rest for the exponent.
269 // If all 41 bits are zero, generate additional random bits, until a
270 // set bit is found, or 126 bits have been generated.
271 const rand = r.int(u64);
272 var rand_lz = @clz(rand);
273 if (rand_lz >= 41) {
274 // TODO: when #5177 or #489 is implemented,
275 // tell the compiler it is unlikely (1/2^41) to reach this point.
276 // (Same for the if branch and the f64 calculations below.)
277 rand_lz = 41 + @clz(r.int(u64));
278 if (rand_lz == 41 + 64) {
279 // It is astronomically unlikely to reach this point.
280 rand_lz += @clz(r.int(u32) | 0x7FF);
281 }
282 }
283 const mantissa: u23 = @truncate(rand);
284 const exponent = @as(u32, 126 - rand_lz) << 23;
285 return @bitCast(exponent | mantissa);
286 },
287 f64 => {
288 // Use 52 random bits for the mantissa, and the rest for the exponent.
289 // If all 12 bits are zero, generate additional random bits, until a
290 // set bit is found, or 1022 bits have been generated.
291 const rand = r.int(u64);
292 var rand_lz: u64 = @clz(rand);
293 if (rand_lz >= 12) {
294 rand_lz = 12;
295 while (true) {
296 // It is astronomically unlikely for this loop to execute more than once.
297 const addl_rand_lz = @clz(r.int(u64));
298 rand_lz += addl_rand_lz;
299 if (addl_rand_lz != 64) {
300 break;
301 }
302 if (rand_lz >= 1022) {
303 rand_lz = 1022;
304 break;
305 }
306 }
307 }
308 const mantissa = rand & 0xFFFFFFFFFFFFF;
309 const exponent = (1022 - rand_lz) << 52;
310 return @bitCast(exponent | mantissa);
311 },
312 else => @compileError("unknown floating point type"),
313 }
314}
315
316/// Return a floating point value normally distributed with mean = 0, stddev = 1.
317///
318/// To use different parameters, use: floatNorm(...) * desiredStddev + desiredMean.
319pub fn floatNorm(r: Random, comptime T: type) T {
320 const value = ziggurat.next_f64(r, ziggurat.NormDist);
321 switch (T) {
322 f32 => return @floatCast(value),
323 f64 => return value,
324 else => @compileError("unknown floating point type"),
325 }
326}
327
328/// Return an exponentially distributed float with a rate parameter of 1.
329///
330/// To use a different rate parameter, use: floatExp(...) / desiredRate.
331pub fn floatExp(r: Random, comptime T: type) T {
332 const value = ziggurat.next_f64(r, ziggurat.ExpDist);
333 switch (T) {
334 f32 => return @floatCast(value),
335 f64 => return value,
336 else => @compileError("unknown floating point type"),
337 }
338}
339
340/// Shuffle a slice into a random order.
341///
342/// Note that this will not yield consistent results across all targets
343/// due to dependence on the representation of `usize` as an index.
344/// See `shuffleWithIndex` for further commentary.
345pub inline fn shuffle(r: Random, comptime T: type, buf: []T) void {
346 r.shuffleWithIndex(T, buf, usize);
347}
348
349/// Shuffle a slice into a random order, using an index of a
350/// specified type to maintain distribution across targets.
351/// Asserts the index type can represent `buf.len`.
352///
353/// Indexes into the slice are generated using the specified `Index`
354/// type, which determines distribution properties. This allows for
355/// results to be independent of `usize` representation.
356///
357/// Prefer `shuffle` if this isn't important.
358///
359/// See `intRangeLessThan`, which this function uses,
360/// for commentary on the runtime of this function.
361pub fn shuffleWithIndex(r: Random, comptime T: type, buf: []T, comptime Index: type) void {
362 const MinInt = MinArrayIndex(Index);
363 if (buf.len < 2) {
364 return;
365 }
366
367 // `i <= j < max <= maxInt(MinInt)`
368 const max: MinInt = @intCast(buf.len);
369 var i: MinInt = 0;
370 while (i < max - 1) : (i += 1) {
371 const j: MinInt = @intCast(r.intRangeLessThan(Index, i, max));
372 mem.swap(T, &buf[i], &buf[j]);
373 }
374}
375
376/// Randomly selects an index into `proportions`, where the likelihood of each
377/// index is weighted by that proportion.
378/// It is more likely for the index of the last proportion to be returned
379/// than the index of the first proportion in the slice, and vice versa.
380///
381/// This is useful for selecting an item from a slice where weights are not equal.
382/// `T` must be a numeric type capable of holding the sum of `proportions`.
383pub fn weightedIndex(r: Random, comptime T: type, proportions: []const T) usize {
384 // This implementation works by summing the proportions and picking a
385 // random point in [0, sum). We then loop over the proportions,
386 // accumulating until our accumulator is greater than the random point.
387
388 const sum = s: {
389 var sum: T = 0;
390 for (proportions) |v| sum += v;
391 break :s sum;
392 };
393
394 const point = switch (@typeInfo(T)) {
395 .Int => |int_info| switch (int_info.signedness) {
396 .signed => r.intRangeLessThan(T, 0, sum),
397 .unsigned => r.uintLessThan(T, sum),
398 },
399 // take care that imprecision doesn't lead to a value slightly greater than sum
400 .Float => @min(r.float(T) * sum, sum - std.math.floatEps(T)),
401 else => @compileError("weightedIndex does not support proportions of type " ++
402 @typeName(T)),
403 };
404
405 assert(point < sum);
406
407 var accumulator: T = 0;
408 for (proportions, 0..) |p, index| {
409 accumulator += p;
410 if (point < accumulator) return index;
411 } else unreachable;
412}
413
414/// Convert a random integer 0 <= random_int <= maxValue(T),
415/// into an integer 0 <= result < less_than.
416/// This function introduces a minor bias.
417pub fn limitRangeBiased(comptime T: type, random_int: T, less_than: T) T {
418 comptime assert(@typeInfo(T).Int.signedness == .unsigned);
419 const bits = @typeInfo(T).Int.bits;
420
421 // adapted from:
422 // http://www.pcg-random.org/posts/bounded-rands.html
423 // "Integer Multiplication (Biased)"
424 const m = math.mulWide(T, random_int, less_than);
425 return @intCast(m >> bits);
426}
427
428/// Returns the smallest of `Index` and `usize`.
429fn MinArrayIndex(comptime Index: type) type {
430 const index_info = @typeInfo(Index).Int;
431 assert(index_info.signedness == .unsigned);
432 return if (index_info.bits >= @typeInfo(usize).Int.bits) usize else Index;
433}
434
435test {
436 std.testing.refAllDecls(@This());
437 _ = @import("Random/test.zig");
438}
lib/std/Random/Ascon.zig created+58
......@@ -0,0 +1,58 @@
1//! CSPRNG based on the Reverie construction, a permutation-based PRNG
2//! with forward security, instantiated with the Ascon(128,12,8) permutation.
3//!
4//! Compared to ChaCha, this PRNG has a much smaller state, and can be
5//! a better choice for constrained environments.
6//!
7//! References:
8//! - A Robust and Sponge-Like PRNG with Improved Efficiency https://eprint.iacr.org/2016/886.pdf
9//! - Ascon https://ascon.iaik.tugraz.at/files/asconv12-nist.pdf
10
11const std = @import("std");
12const mem = std.mem;
13const Self = @This();
14
15const Ascon = std.crypto.core.Ascon(.little);
16
17state: Ascon,
18
19const rate = 16;
20pub const secret_seed_length = 32;
21
22/// The seed must be uniform, secret and `secret_seed_length` bytes long.
23pub fn init(secret_seed: [secret_seed_length]u8) Self {
24 var self = Self{ .state = Ascon.initXof() };
25 self.addEntropy(&secret_seed);
26 return self;
27}
28
29/// Inserts entropy to refresh the internal state.
30pub fn addEntropy(self: *Self, bytes: []const u8) void {
31 comptime std.debug.assert(secret_seed_length % rate == 0);
32 var i: usize = 0;
33 while (i + rate < bytes.len) : (i += rate) {
34 self.state.addBytes(bytes[i..][0..rate]);
35 self.state.permuteR(8);
36 }
37 if (i != bytes.len) self.state.addBytes(bytes[i..]);
38 self.state.permute();
39}
40
41/// Returns a `std.Random` structure backed by the current RNG.
42pub fn random(self: *Self) std.Random {
43 return std.Random.init(self, fill);
44}
45
46/// Fills the buffer with random bytes.
47pub fn fill(self: *Self, buf: []u8) void {
48 var i: usize = 0;
49 while (true) {
50 const left = buf.len - i;
51 const n = @min(left, rate);
52 self.state.extractBytes(buf[i..][0..n]);
53 if (left == 0) break;
54 self.state.permuteR(8);
55 i += n;
56 }
57 self.state.permuteRatchet(6, rate);
58}
lib/std/Random/ChaCha.zig created+97
......@@ -0,0 +1,97 @@
1//! CSPRNG based on the ChaCha8 stream cipher, with forward security.
2//!
3//! References:
4//! - Fast-key-erasure random-number generators https://blog.cr.yp.to/20170723-random.html
5
6const std = @import("std");
7const mem = std.mem;
8const Self = @This();
9
10const Cipher = std.crypto.stream.chacha.ChaCha8IETF;
11
12const State = [8 * Cipher.block_length]u8;
13
14state: State,
15offset: usize,
16
17const nonce = [_]u8{0} ** Cipher.nonce_length;
18
19pub const secret_seed_length = Cipher.key_length;
20
21/// The seed must be uniform, secret and `secret_seed_length` bytes long.
22pub fn init(secret_seed: [secret_seed_length]u8) Self {
23 var self = Self{ .state = undefined, .offset = 0 };
24 Cipher.stream(&self.state, 0, secret_seed, nonce);
25 return self;
26}
27
28/// Inserts entropy to refresh the internal state.
29pub fn addEntropy(self: *Self, bytes: []const u8) void {
30 var i: usize = 0;
31 while (i + Cipher.key_length <= bytes.len) : (i += Cipher.key_length) {
32 Cipher.xor(
33 self.state[0..Cipher.key_length],
34 self.state[0..Cipher.key_length],
35 0,
36 bytes[i..][0..Cipher.key_length].*,
37 nonce,
38 );
39 }
40 if (i < bytes.len) {
41 var k = [_]u8{0} ** Cipher.key_length;
42 const src = bytes[i..];
43 @memcpy(k[0..src.len], src);
44 Cipher.xor(
45 self.state[0..Cipher.key_length],
46 self.state[0..Cipher.key_length],
47 0,
48 k,
49 nonce,
50 );
51 }
52 self.refill();
53}
54
55/// Returns a `std.Random` structure backed by the current RNG.
56pub fn random(self: *Self) std.Random {
57 return std.Random.init(self, fill);
58}
59
60// Refills the buffer with random bytes, overwriting the previous key.
61fn refill(self: *Self) void {
62 Cipher.stream(&self.state, 0, self.state[0..Cipher.key_length].*, nonce);
63 self.offset = 0;
64}
65
66/// Fills the buffer with random bytes.
67pub fn fill(self: *Self, buf_: []u8) void {
68 const bytes = self.state[Cipher.key_length..];
69 var buf = buf_;
70
71 const avail = bytes.len - self.offset;
72 if (avail > 0) {
73 // Bytes from the current block
74 const n = @min(avail, buf.len);
75 @memcpy(buf[0..n], bytes[self.offset..][0..n]);
76 @memset(bytes[self.offset..][0..n], 0);
77 buf = buf[n..];
78 self.offset += n;
79 }
80 if (buf.len == 0) return;
81
82 self.refill();
83
84 // Full blocks
85 while (buf.len >= bytes.len) {
86 @memcpy(buf[0..bytes.len], bytes);
87 buf = buf[bytes.len..];
88 self.refill();
89 }
90
91 // Remaining bytes
92 if (buf.len > 0) {
93 @memcpy(buf, bytes[0..buf.len]);
94 @memset(bytes[0..buf.len], 0);
95 self.offset = buf.len;
96 }
97}
lib/std/Random/Isaac64.zig created+234
......@@ -0,0 +1,234 @@
1//! ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html
2//!
3//! Follows the general idea of the implementation from here with a few shortcuts.
4//! https://doc.rust-lang.org/rand/src/rand/prng/isaac64.rs.html
5
6const std = @import("std");
7const mem = std.mem;
8const Isaac64 = @This();
9
10r: [256]u64,
11m: [256]u64,
12a: u64,
13b: u64,
14c: u64,
15i: usize,
16
17pub fn init(init_s: u64) Isaac64 {
18 var isaac = Isaac64{
19 .r = undefined,
20 .m = undefined,
21 .a = undefined,
22 .b = undefined,
23 .c = undefined,
24 .i = undefined,
25 };
26
27 // seed == 0 => same result as the unseeded reference implementation
28 isaac.seed(init_s, 1);
29 return isaac;
30}
31
32pub fn random(self: *Isaac64) std.Random {
33 return std.Random.init(self, fill);
34}
35
36fn step(self: *Isaac64, mix: u64, base: usize, comptime m1: usize, comptime m2: usize) void {
37 const x = self.m[base + m1];
38 self.a = mix +% self.m[base + m2];
39
40 const y = self.a +% self.b +% self.m[@as(usize, @intCast((x >> 3) % self.m.len))];
41 self.m[base + m1] = y;
42
43 self.b = x +% self.m[@as(usize, @intCast((y >> 11) % self.m.len))];
44 self.r[self.r.len - 1 - base - m1] = self.b;
45}
46
47fn refill(self: *Isaac64) void {
48 const midpoint = self.r.len / 2;
49
50 self.c +%= 1;
51 self.b +%= self.c;
52
53 {
54 var i: usize = 0;
55 while (i < midpoint) : (i += 4) {
56 self.step(~(self.a ^ (self.a << 21)), i + 0, 0, midpoint);
57 self.step(self.a ^ (self.a >> 5), i + 1, 0, midpoint);
58 self.step(self.a ^ (self.a << 12), i + 2, 0, midpoint);
59 self.step(self.a ^ (self.a >> 33), i + 3, 0, midpoint);
60 }
61 }
62
63 {
64 var i: usize = 0;
65 while (i < midpoint) : (i += 4) {
66 self.step(~(self.a ^ (self.a << 21)), i + 0, midpoint, 0);
67 self.step(self.a ^ (self.a >> 5), i + 1, midpoint, 0);
68 self.step(self.a ^ (self.a << 12), i + 2, midpoint, 0);
69 self.step(self.a ^ (self.a >> 33), i + 3, midpoint, 0);
70 }
71 }
72
73 self.i = 0;
74}
75
76fn next(self: *Isaac64) u64 {
77 if (self.i >= self.r.len) {
78 self.refill();
79 }
80
81 const value = self.r[self.i];
82 self.i += 1;
83 return value;
84}
85
86fn seed(self: *Isaac64, init_s: u64, comptime rounds: usize) void {
87 // We ignore the multi-pass requirement since we don't currently expose full access to
88 // seeding the self.m array completely.
89 @memset(self.m[0..], 0);
90 self.m[0] = init_s;
91
92 // prescrambled golden ratio constants
93 var a = [_]u64{
94 0x647c4677a2884b7c,
95 0xb9f8b322c73ac862,
96 0x8c0ea5053d4712a0,
97 0xb29b2e824a595524,
98 0x82f053db8355e0ce,
99 0x48fe4a0fa5a09315,
100 0xae985bf2cbfc89ed,
101 0x98f5704f6c44c0ab,
102 };
103
104 comptime var i: usize = 0;
105 inline while (i < rounds) : (i += 1) {
106 var j: usize = 0;
107 while (j < self.m.len) : (j += 8) {
108 comptime var x1: usize = 0;
109 inline while (x1 < 8) : (x1 += 1) {
110 a[x1] +%= self.m[j + x1];
111 }
112
113 a[0] -%= a[4];
114 a[5] ^= a[7] >> 9;
115 a[7] +%= a[0];
116 a[1] -%= a[5];
117 a[6] ^= a[0] << 9;
118 a[0] +%= a[1];
119 a[2] -%= a[6];
120 a[7] ^= a[1] >> 23;
121 a[1] +%= a[2];
122 a[3] -%= a[7];
123 a[0] ^= a[2] << 15;
124 a[2] +%= a[3];
125 a[4] -%= a[0];
126 a[1] ^= a[3] >> 14;
127 a[3] +%= a[4];
128 a[5] -%= a[1];
129 a[2] ^= a[4] << 20;
130 a[4] +%= a[5];
131 a[6] -%= a[2];
132 a[3] ^= a[5] >> 17;
133 a[5] +%= a[6];
134 a[7] -%= a[3];
135 a[4] ^= a[6] << 14;
136 a[6] +%= a[7];
137
138 comptime var x2: usize = 0;
139 inline while (x2 < 8) : (x2 += 1) {
140 self.m[j + x2] = a[x2];
141 }
142 }
143 }
144
145 @memset(self.r[0..], 0);
146 self.a = 0;
147 self.b = 0;
148 self.c = 0;
149 self.i = self.r.len; // trigger refill on first value
150}
151
152pub fn fill(self: *Isaac64, buf: []u8) void {
153 var i: usize = 0;
154 const aligned_len = buf.len - (buf.len & 7);
155
156 // Fill complete 64-byte segments
157 while (i < aligned_len) : (i += 8) {
158 var n = self.next();
159 comptime var j: usize = 0;
160 inline while (j < 8) : (j += 1) {
161 buf[i + j] = @as(u8, @truncate(n));
162 n >>= 8;
163 }
164 }
165
166 // Fill trailing, ignoring excess (cut the stream).
167 if (i != buf.len) {
168 var n = self.next();
169 while (i < buf.len) : (i += 1) {
170 buf[i] = @as(u8, @truncate(n));
171 n >>= 8;
172 }
173 }
174}
175
176test "isaac64 sequence" {
177 var r = Isaac64.init(0);
178
179 // from reference implementation
180 const seq = [_]u64{
181 0xf67dfba498e4937c,
182 0x84a5066a9204f380,
183 0xfee34bd5f5514dbb,
184 0x4d1664739b8f80d6,
185 0x8607459ab52a14aa,
186 0x0e78bc5a98529e49,
187 0xfe5332822ad13777,
188 0x556c27525e33d01a,
189 0x08643ca615f3149f,
190 0xd0771faf3cb04714,
191 0x30e86f68a37b008d,
192 0x3074ebc0488a3adf,
193 0x270645ea7a2790bc,
194 0x5601a0a8d3763c6a,
195 0x2f83071f53f325dd,
196 0xb9090f3d42d2d2ea,
197 };
198
199 for (seq) |s| {
200 try std.testing.expect(s == r.next());
201 }
202}
203
204test "isaac64 fill" {
205 var r = Isaac64.init(0);
206
207 // from reference implementation
208 const seq = [_]u64{
209 0xf67dfba498e4937c,
210 0x84a5066a9204f380,
211 0xfee34bd5f5514dbb,
212 0x4d1664739b8f80d6,
213 0x8607459ab52a14aa,
214 0x0e78bc5a98529e49,
215 0xfe5332822ad13777,
216 0x556c27525e33d01a,
217 0x08643ca615f3149f,
218 0xd0771faf3cb04714,
219 0x30e86f68a37b008d,
220 0x3074ebc0488a3adf,
221 0x270645ea7a2790bc,
222 0x5601a0a8d3763c6a,
223 0x2f83071f53f325dd,
224 0xb9090f3d42d2d2ea,
225 };
226
227 for (seq) |s| {
228 var buf0: [8]u8 = undefined;
229 var buf1: [7]u8 = undefined;
230 std.mem.writeInt(u64, &buf0, s, .little);
231 r.fill(&buf1);
232 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
233 }
234}
lib/std/Random/Pcg.zig created+121
......@@ -0,0 +1,121 @@
1//! PCG32 - http://www.pcg-random.org/
2//!
3//! PRNG
4
5const std = @import("std");
6const Pcg = @This();
7
8const default_multiplier = 6364136223846793005;
9
10s: u64,
11i: u64,
12
13pub fn init(init_s: u64) Pcg {
14 var pcg = Pcg{
15 .s = undefined,
16 .i = undefined,
17 };
18
19 pcg.seed(init_s);
20 return pcg;
21}
22
23pub fn random(self: *Pcg) std.Random {
24 return std.Random.init(self, fill);
25}
26
27fn next(self: *Pcg) u32 {
28 const l = self.s;
29 self.s = l *% default_multiplier +% (self.i | 1);
30
31 const xor_s: u32 = @truncate(((l >> 18) ^ l) >> 27);
32 const rot: u32 = @intCast(l >> 59);
33
34 return (xor_s >> @as(u5, @intCast(rot))) | (xor_s << @as(u5, @intCast((0 -% rot) & 31)));
35}
36
37fn seed(self: *Pcg, init_s: u64) void {
38 // Pcg requires 128-bits of seed.
39 var gen = std.Random.SplitMix64.init(init_s);
40 self.seedTwo(gen.next(), gen.next());
41}
42
43fn seedTwo(self: *Pcg, init_s: u64, init_i: u64) void {
44 self.s = 0;
45 self.i = (init_s << 1) | 1;
46 self.s = self.s *% default_multiplier +% self.i;
47 self.s +%= init_i;
48 self.s = self.s *% default_multiplier +% self.i;
49}
50
51pub fn fill(self: *Pcg, buf: []u8) void {
52 var i: usize = 0;
53 const aligned_len = buf.len - (buf.len & 3);
54
55 // Complete 4 byte segments.
56 while (i < aligned_len) : (i += 4) {
57 var n = self.next();
58 comptime var j: usize = 0;
59 inline while (j < 4) : (j += 1) {
60 buf[i + j] = @as(u8, @truncate(n));
61 n >>= 8;
62 }
63 }
64
65 // Remaining. (cuts the stream)
66 if (i != buf.len) {
67 var n = self.next();
68 while (i < buf.len) : (i += 1) {
69 buf[i] = @as(u8, @truncate(n));
70 n >>= 8;
71 }
72 }
73}
74
75test "pcg sequence" {
76 var r = Pcg.init(0);
77 const s0: u64 = 0x9394bf54ce5d79de;
78 const s1: u64 = 0x84e9c579ef59bbf7;
79 r.seedTwo(s0, s1);
80
81 const seq = [_]u32{
82 2881561918,
83 3063928540,
84 1199791034,
85 2487695858,
86 1479648952,
87 3247963454,
88 };
89
90 for (seq) |s| {
91 try std.testing.expect(s == r.next());
92 }
93}
94
95test "pcg fill" {
96 var r = Pcg.init(0);
97 const s0: u64 = 0x9394bf54ce5d79de;
98 const s1: u64 = 0x84e9c579ef59bbf7;
99 r.seedTwo(s0, s1);
100
101 const seq = [_]u32{
102 2881561918,
103 3063928540,
104 1199791034,
105 2487695858,
106 1479648952,
107 3247963454,
108 };
109
110 var i: u32 = 0;
111 while (i < seq.len) : (i += 2) {
112 var buf0: [8]u8 = undefined;
113 std.mem.writeInt(u32, buf0[0..4], seq[i], .little);
114 std.mem.writeInt(u32, buf0[4..8], seq[i + 1], .little);
115
116 var buf1: [7]u8 = undefined;
117 r.fill(&buf1);
118
119 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
120 }
121}
lib/std/Random/RomuTrio.zig created+131
......@@ -0,0 +1,131 @@
1// Website: romu-random.org
2// Reference paper: http://arxiv.org/abs/2002.11331
3// Beware: this PRNG is trivially predictable. While fast, it should *never* be used for cryptographic purposes.
4
5const std = @import("std");
6const math = std.math;
7const RomuTrio = @This();
8
9x_state: u64,
10y_state: u64,
11z_state: u64, // set to nonzero seed
12
13pub fn init(init_s: u64) RomuTrio {
14 var x = RomuTrio{ .x_state = undefined, .y_state = undefined, .z_state = undefined };
15 x.seed(init_s);
16 return x;
17}
18
19pub fn random(self: *RomuTrio) std.Random {
20 return std.Random.init(self, fill);
21}
22
23fn next(self: *RomuTrio) u64 {
24 const xp = self.x_state;
25 const yp = self.y_state;
26 const zp = self.z_state;
27 self.x_state = 15241094284759029579 *% zp;
28 self.y_state = yp -% xp;
29 self.y_state = std.math.rotl(u64, self.y_state, 12);
30 self.z_state = zp -% yp;
31 self.z_state = std.math.rotl(u64, self.z_state, 44);
32 return xp;
33}
34
35pub fn seedWithBuf(self: *RomuTrio, buf: [24]u8) void {
36 const seed_buf = @as([3]u64, @bitCast(buf));
37 self.x_state = seed_buf[0];
38 self.y_state = seed_buf[1];
39 self.z_state = seed_buf[2];
40}
41
42pub fn seed(self: *RomuTrio, init_s: u64) void {
43 // RomuTrio requires 192-bits of seed.
44 var gen = std.Random.SplitMix64.init(init_s);
45
46 self.x_state = gen.next();
47 self.y_state = gen.next();
48 self.z_state = gen.next();
49}
50
51pub fn fill(self: *RomuTrio, buf: []u8) void {
52 var i: usize = 0;
53 const aligned_len = buf.len - (buf.len & 7);
54
55 // Complete 8 byte segments.
56 while (i < aligned_len) : (i += 8) {
57 var n = self.next();
58 comptime var j: usize = 0;
59 inline while (j < 8) : (j += 1) {
60 buf[i + j] = @as(u8, @truncate(n));
61 n >>= 8;
62 }
63 }
64
65 // Remaining. (cuts the stream)
66 if (i != buf.len) {
67 var n = self.next();
68 while (i < buf.len) : (i += 1) {
69 buf[i] = @as(u8, @truncate(n));
70 n >>= 8;
71 }
72 }
73}
74
75test "RomuTrio sequence" {
76 // Unfortunately there does not seem to be an official test sequence.
77 var r = RomuTrio.init(0);
78
79 const seq = [_]u64{
80 16294208416658607535,
81 13964609475759908645,
82 4703697494102998476,
83 3425221541186733346,
84 2285772463536419399,
85 9454187757529463048,
86 13695907680080547496,
87 8328236714879408626,
88 12323357569716880909,
89 12375466223337721820,
90 };
91
92 for (seq) |s| {
93 try std.testing.expectEqual(s, r.next());
94 }
95}
96
97test "RomuTrio fill" {
98 // Unfortunately there does not seem to be an official test sequence.
99 var r = RomuTrio.init(0);
100
101 const seq = [_]u64{
102 16294208416658607535,
103 13964609475759908645,
104 4703697494102998476,
105 3425221541186733346,
106 2285772463536419399,
107 9454187757529463048,
108 13695907680080547496,
109 8328236714879408626,
110 12323357569716880909,
111 12375466223337721820,
112 };
113
114 for (seq) |s| {
115 var buf0: [8]u8 = undefined;
116 var buf1: [7]u8 = undefined;
117 std.mem.writeInt(u64, &buf0, s, .little);
118 r.fill(&buf1);
119 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
120 }
121}
122
123test "RomuTrio buf seeding test" {
124 const buf0 = @as([24]u8, @bitCast([3]u64{ 16294208416658607535, 13964609475759908645, 4703697494102998476 }));
125 const resulting_state = .{ .x = 16294208416658607535, .y = 13964609475759908645, .z = 4703697494102998476 };
126 var r = RomuTrio.init(0);
127 r.seedWithBuf(buf0);
128 try std.testing.expect(r.x_state == resulting_state.x);
129 try std.testing.expect(r.y_state == resulting_state.y);
130 try std.testing.expect(r.z_state == resulting_state.z);
131}
lib/std/Random/Sfc64.zig created+131
......@@ -0,0 +1,131 @@
1//! Sfc64 pseudo-random number generator from Practically Random.
2//! Fastest engine of pracrand and smallest footprint.
3//! See http://pracrand.sourceforge.net/
4
5const std = @import("std");
6const math = std.math;
7const Sfc64 = @This();
8
9a: u64 = undefined,
10b: u64 = undefined,
11c: u64 = undefined,
12counter: u64 = undefined,
13
14const Rotation = 24;
15const RightShift = 11;
16const LeftShift = 3;
17
18pub fn init(init_s: u64) Sfc64 {
19 var x = Sfc64{};
20
21 x.seed(init_s);
22 return x;
23}
24
25pub fn random(self: *Sfc64) std.Random {
26 return std.Random.init(self, fill);
27}
28
29fn next(self: *Sfc64) u64 {
30 const tmp = self.a +% self.b +% self.counter;
31 self.counter += 1;
32 self.a = self.b ^ (self.b >> RightShift);
33 self.b = self.c +% (self.c << LeftShift);
34 self.c = math.rotl(u64, self.c, Rotation) +% tmp;
35 return tmp;
36}
37
38fn seed(self: *Sfc64, init_s: u64) void {
39 self.a = init_s;
40 self.b = init_s;
41 self.c = init_s;
42 self.counter = 1;
43 var i: u32 = 0;
44 while (i < 12) : (i += 1) {
45 _ = self.next();
46 }
47}
48
49pub fn fill(self: *Sfc64, buf: []u8) void {
50 var i: usize = 0;
51 const aligned_len = buf.len - (buf.len & 7);
52
53 // Complete 8 byte segments.
54 while (i < aligned_len) : (i += 8) {
55 var n = self.next();
56 comptime var j: usize = 0;
57 inline while (j < 8) : (j += 1) {
58 buf[i + j] = @as(u8, @truncate(n));
59 n >>= 8;
60 }
61 }
62
63 // Remaining. (cuts the stream)
64 if (i != buf.len) {
65 var n = self.next();
66 while (i < buf.len) : (i += 1) {
67 buf[i] = @as(u8, @truncate(n));
68 n >>= 8;
69 }
70 }
71}
72
73test "Sfc64 sequence" {
74 // Unfortunately there does not seem to be an official test sequence.
75 var r = Sfc64.init(0);
76
77 const seq = [_]u64{
78 0x3acfa029e3cc6041,
79 0xf5b6515bf2ee419c,
80 0x1259635894a29b61,
81 0xb6ae75395f8ebd6,
82 0x225622285ce302e2,
83 0x520d28611395cb21,
84 0xdb909c818901599d,
85 0x8ffd195365216f57,
86 0xe8c4ad5e258ac04a,
87 0x8f8ef2c89fdb63ca,
88 0xf9865b01d98d8e2f,
89 0x46555871a65d08ba,
90 0x66868677c6298fcd,
91 0x2ce15a7e6329f57d,
92 0xb2f1833ca91ca79,
93 0x4b0890ac9bf453ca,
94 };
95
96 for (seq) |s| {
97 try std.testing.expectEqual(s, r.next());
98 }
99}
100
101test "Sfc64 fill" {
102 // Unfortunately there does not seem to be an official test sequence.
103 var r = Sfc64.init(0);
104
105 const seq = [_]u64{
106 0x3acfa029e3cc6041,
107 0xf5b6515bf2ee419c,
108 0x1259635894a29b61,
109 0xb6ae75395f8ebd6,
110 0x225622285ce302e2,
111 0x520d28611395cb21,
112 0xdb909c818901599d,
113 0x8ffd195365216f57,
114 0xe8c4ad5e258ac04a,
115 0x8f8ef2c89fdb63ca,
116 0xf9865b01d98d8e2f,
117 0x46555871a65d08ba,
118 0x66868677c6298fcd,
119 0x2ce15a7e6329f57d,
120 0xb2f1833ca91ca79,
121 0x4b0890ac9bf453ca,
122 };
123
124 for (seq) |s| {
125 var buf0: [8]u8 = undefined;
126 var buf1: [7]u8 = undefined;
127 std.mem.writeInt(u64, &buf0, s, .little);
128 r.fill(&buf1);
129 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
130 }
131}
lib/std/Random/SplitMix64.zig created+21
......@@ -0,0 +1,21 @@
1//! Generator to extend 64-bit seed values into longer sequences.
2//!
3//! The number of cycles is thus limited to 64-bits regardless of the engine, but this
4//! is still plenty for practical purposes.
5
6const SplitMix64 = @This();
7
8s: u64,
9
10pub fn init(seed: u64) SplitMix64 {
11 return SplitMix64{ .s = seed };
12}
13
14pub fn next(self: *SplitMix64) u64 {
15 self.s +%= 0x9e3779b97f4a7c15;
16
17 var z = self.s;
18 z = (z ^ (z >> 30)) *% 0xbf58476d1ce4e5b9;
19 z = (z ^ (z >> 27)) *% 0x94d049bb133111eb;
20 return z ^ (z >> 31);
21}
lib/std/Random/Xoroshiro128.zig created+146
......@@ -0,0 +1,146 @@
1//! Xoroshiro128+ - http://xoroshiro.di.unimi.it/
2//!
3//! PRNG
4
5const std = @import("std");
6const math = std.math;
7const Xoroshiro128 = @This();
8
9s: [2]u64,
10
11pub fn init(init_s: u64) Xoroshiro128 {
12 var x = Xoroshiro128{ .s = undefined };
13
14 x.seed(init_s);
15 return x;
16}
17
18pub fn random(self: *Xoroshiro128) std.Random {
19 return std.Random.init(self, fill);
20}
21
22pub fn next(self: *Xoroshiro128) u64 {
23 const s0 = self.s[0];
24 var s1 = self.s[1];
25 const r = s0 +% s1;
26
27 s1 ^= s0;
28 self.s[0] = math.rotl(u64, s0, @as(u8, 55)) ^ s1 ^ (s1 << 14);
29 self.s[1] = math.rotl(u64, s1, @as(u8, 36));
30
31 return r;
32}
33
34// Skip 2^64 places ahead in the sequence
35pub fn jump(self: *Xoroshiro128) void {
36 var s0: u64 = 0;
37 var s1: u64 = 0;
38
39 const table = [_]u64{
40 0xbeac0467eba5facb,
41 0xd86b048b86aa9922,
42 };
43
44 inline for (table) |entry| {
45 var b: usize = 0;
46 while (b < 64) : (b += 1) {
47 if ((entry & (@as(u64, 1) << @as(u6, @intCast(b)))) != 0) {
48 s0 ^= self.s[0];
49 s1 ^= self.s[1];
50 }
51 _ = self.next();
52 }
53 }
54
55 self.s[0] = s0;
56 self.s[1] = s1;
57}
58
59pub fn seed(self: *Xoroshiro128, init_s: u64) void {
60 // Xoroshiro requires 128-bits of seed.
61 var gen = std.Random.SplitMix64.init(init_s);
62
63 self.s[0] = gen.next();
64 self.s[1] = gen.next();
65}
66
67pub fn fill(self: *Xoroshiro128, buf: []u8) void {
68 var i: usize = 0;
69 const aligned_len = buf.len - (buf.len & 7);
70
71 // Complete 8 byte segments.
72 while (i < aligned_len) : (i += 8) {
73 var n = self.next();
74 comptime var j: usize = 0;
75 inline while (j < 8) : (j += 1) {
76 buf[i + j] = @as(u8, @truncate(n));
77 n >>= 8;
78 }
79 }
80
81 // Remaining. (cuts the stream)
82 if (i != buf.len) {
83 var n = self.next();
84 while (i < buf.len) : (i += 1) {
85 buf[i] = @as(u8, @truncate(n));
86 n >>= 8;
87 }
88 }
89}
90
91test "xoroshiro sequence" {
92 var r = Xoroshiro128.init(0);
93 r.s[0] = 0xaeecf86f7878dd75;
94 r.s[1] = 0x01cd153642e72622;
95
96 const seq1 = [_]u64{
97 0xb0ba0da5bb600397,
98 0x18a08afde614dccc,
99 0xa2635b956a31b929,
100 0xabe633c971efa045,
101 0x9ac19f9706ca3cac,
102 0xf62b426578c1e3fb,
103 };
104
105 for (seq1) |s| {
106 try std.testing.expect(s == r.next());
107 }
108
109 r.jump();
110
111 const seq2 = [_]u64{
112 0x95344a13556d3e22,
113 0xb4fb32dafa4d00df,
114 0xb2011d9ccdcfe2dd,
115 0x05679a9b2119b908,
116 0xa860a1da7c9cd8a0,
117 0x658a96efe3f86550,
118 };
119
120 for (seq2) |s| {
121 try std.testing.expect(s == r.next());
122 }
123}
124
125test "xoroshiro fill" {
126 var r = Xoroshiro128.init(0);
127 r.s[0] = 0xaeecf86f7878dd75;
128 r.s[1] = 0x01cd153642e72622;
129
130 const seq = [_]u64{
131 0xb0ba0da5bb600397,
132 0x18a08afde614dccc,
133 0xa2635b956a31b929,
134 0xabe633c971efa045,
135 0x9ac19f9706ca3cac,
136 0xf62b426578c1e3fb,
137 };
138
139 for (seq) |s| {
140 var buf0: [8]u8 = undefined;
141 var buf1: [7]u8 = undefined;
142 std.mem.writeInt(u64, &buf0, s, .little);
143 r.fill(&buf1);
144 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
145 }
146}
lib/std/Random/Xoshiro256.zig created+145
......@@ -0,0 +1,145 @@
1//! Xoshiro256++ - http://xoroshiro.di.unimi.it/
2//!
3//! PRNG
4
5const std = @import("std");
6const math = std.math;
7const Xoshiro256 = @This();
8
9s: [4]u64,
10
11pub fn init(init_s: u64) Xoshiro256 {
12 var x = Xoshiro256{
13 .s = undefined,
14 };
15
16 x.seed(init_s);
17 return x;
18}
19
20pub fn random(self: *Xoshiro256) std.Random {
21 return std.Random.init(self, fill);
22}
23
24pub fn next(self: *Xoshiro256) u64 {
25 const r = math.rotl(u64, self.s[0] +% self.s[3], 23) +% self.s[0];
26
27 const t = self.s[1] << 17;
28
29 self.s[2] ^= self.s[0];
30 self.s[3] ^= self.s[1];
31 self.s[1] ^= self.s[2];
32 self.s[0] ^= self.s[3];
33
34 self.s[2] ^= t;
35
36 self.s[3] = math.rotl(u64, self.s[3], 45);
37
38 return r;
39}
40
41// Skip 2^128 places ahead in the sequence
42pub fn jump(self: *Xoshiro256) void {
43 var s: u256 = 0;
44
45 var table: u256 = 0x39abdc4529b1661ca9582618e03fc9aad5a61266f0c9392c180ec6d33cfd0aba;
46
47 while (table != 0) : (table >>= 1) {
48 if (@as(u1, @truncate(table)) != 0) {
49 s ^= @as(u256, @bitCast(self.s));
50 }
51 _ = self.next();
52 }
53
54 self.s = @as([4]u64, @bitCast(s));
55}
56
57pub fn seed(self: *Xoshiro256, init_s: u64) void {
58 // Xoshiro requires 256-bits of seed.
59 var gen = std.Random.SplitMix64.init(init_s);
60
61 self.s[0] = gen.next();
62 self.s[1] = gen.next();
63 self.s[2] = gen.next();
64 self.s[3] = gen.next();
65}
66
67pub fn fill(self: *Xoshiro256, buf: []u8) void {
68 var i: usize = 0;
69 const aligned_len = buf.len - (buf.len & 7);
70
71 // Complete 8 byte segments.
72 while (i < aligned_len) : (i += 8) {
73 var n = self.next();
74 comptime var j: usize = 0;
75 inline while (j < 8) : (j += 1) {
76 buf[i + j] = @as(u8, @truncate(n));
77 n >>= 8;
78 }
79 }
80
81 // Remaining. (cuts the stream)
82 if (i != buf.len) {
83 var n = self.next();
84 while (i < buf.len) : (i += 1) {
85 buf[i] = @as(u8, @truncate(n));
86 n >>= 8;
87 }
88 }
89}
90
91test "xoroshiro sequence" {
92 if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest;
93 if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest;
94
95 var r = Xoshiro256.init(0);
96
97 const seq1 = [_]u64{
98 0x53175d61490b23df,
99 0x61da6f3dc380d507,
100 0x5c0fdf91ec9a7bfc,
101 0x02eebf8c3bbe5e1a,
102 0x7eca04ebaf4a5eea,
103 0x0543c37757f08d9a,
104 };
105
106 for (seq1) |s| {
107 try std.testing.expect(s == r.next());
108 }
109
110 r.jump();
111
112 const seq2 = [_]u64{
113 0xae1db5c5e27807be,
114 0xb584c6a7fd8709fe,
115 0xc46a0ee9330fb6e,
116 0xdc0c9606f49ed76e,
117 0x1f5bb6540f6651fb,
118 0x72fa2ca734601488,
119 };
120
121 for (seq2) |s| {
122 try std.testing.expect(s == r.next());
123 }
124}
125
126test "xoroshiro fill" {
127 var r = Xoshiro256.init(0);
128
129 const seq = [_]u64{
130 0x53175d61490b23df,
131 0x61da6f3dc380d507,
132 0x5c0fdf91ec9a7bfc,
133 0x02eebf8c3bbe5e1a,
134 0x7eca04ebaf4a5eea,
135 0x0543c37757f08d9a,
136 };
137
138 for (seq) |s| {
139 var buf0: [8]u8 = undefined;
140 var buf1: [7]u8 = undefined;
141 std.mem.writeInt(u64, &buf0, s, .little);
142 r.fill(&buf1);
143 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
144 }
145}
lib/std/Random/benchmark.zig created+217
......@@ -0,0 +1,217 @@
1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
2
3const std = @import("std");
4const builtin = @import("builtin");
5const time = std.time;
6const Timer = time.Timer;
7const Random = std.Random;
8
9const KiB = 1024;
10const MiB = 1024 * KiB;
11const GiB = 1024 * MiB;
12
13const Rng = struct {
14 ty: type,
15 name: []const u8,
16 init_u8s: ?[]const u8 = null,
17 init_u64: ?u64 = null,
18};
19
20const prngs = [_]Rng{
21 Rng{
22 .ty = Random.Isaac64,
23 .name = "isaac64",
24 .init_u64 = 0,
25 },
26 Rng{
27 .ty = Random.Pcg,
28 .name = "pcg",
29 .init_u64 = 0,
30 },
31 Rng{
32 .ty = Random.RomuTrio,
33 .name = "romutrio",
34 .init_u64 = 0,
35 },
36 Rng{
37 .ty = Random.Sfc64,
38 .name = "sfc64",
39 .init_u64 = 0,
40 },
41 Rng{
42 .ty = Random.Xoroshiro128,
43 .name = "xoroshiro128",
44 .init_u64 = 0,
45 },
46 Rng{
47 .ty = Random.Xoshiro256,
48 .name = "xoshiro256",
49 .init_u64 = 0,
50 },
51};
52
53const csprngs = [_]Rng{
54 Rng{
55 .ty = Random.Ascon,
56 .name = "ascon",
57 .init_u8s = &[_]u8{0} ** 32,
58 },
59 Rng{
60 .ty = Random.ChaCha,
61 .name = "chacha",
62 .init_u8s = &[_]u8{0} ** 32,
63 },
64};
65
66const Result = struct {
67 throughput: u64,
68};
69
70const long_block_size: usize = 8 * 8192;
71const short_block_size: usize = 8;
72
73pub fn benchmark(comptime H: anytype, bytes: usize, comptime block_size: usize) !Result {
74 var rng = blk: {
75 if (H.init_u8s) |init| {
76 break :blk H.ty.init(init[0..].*);
77 }
78 if (H.init_u64) |init| {
79 break :blk H.ty.init(init);
80 }
81 break :blk H.ty.init();
82 };
83
84 var block: [block_size]u8 = undefined;
85
86 var offset: usize = 0;
87 var timer = try Timer.start();
88 const start = timer.lap();
89 while (offset < bytes) : (offset += block.len) {
90 rng.fill(block[0..]);
91 }
92 const end = timer.read();
93
94 const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
95 const throughput = @as(u64, @intFromFloat(@as(f64, @floatFromInt(bytes)) / elapsed_s));
96
97 std.debug.assert(rng.random().int(u64) != 0);
98
99 return Result{
100 .throughput = throughput,
101 };
102}
103
104fn usage() void {
105 std.debug.print(
106 \\throughput_test [options]
107 \\
108 \\Options:
109 \\ --filter [test-name]
110 \\ --count [int]
111 \\ --prngs-only
112 \\ --csprngs-only
113 \\ --short-only
114 \\ --long-only
115 \\ --help
116 \\
117 , .{});
118}
119
120fn mode(comptime x: comptime_int) comptime_int {
121 return if (builtin.mode == .Debug) x / 64 else x;
122}
123
124pub fn main() !void {
125 const stdout = std.io.getStdOut().writer();
126
127 var buffer: [1024]u8 = undefined;
128 var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
129 const args = try std.process.argsAlloc(fixed.allocator());
130
131 var filter: ?[]u8 = "";
132 var count: usize = mode(128 * MiB);
133 var bench_prngs = true;
134 var bench_csprngs = true;
135 var bench_long = true;
136 var bench_short = true;
137
138 var i: usize = 1;
139 while (i < args.len) : (i += 1) {
140 if (std.mem.eql(u8, args[i], "--mode")) {
141 try stdout.print("{}\n", .{builtin.mode});
142 return;
143 } else if (std.mem.eql(u8, args[i], "--filter")) {
144 i += 1;
145 if (i == args.len) {
146 usage();
147 std.os.exit(1);
148 }
149
150 filter = args[i];
151 } else if (std.mem.eql(u8, args[i], "--count")) {
152 i += 1;
153 if (i == args.len) {
154 usage();
155 std.os.exit(1);
156 }
157
158 const c = try std.fmt.parseUnsigned(usize, args[i], 10);
159 count = c * MiB;
160 } else if (std.mem.eql(u8, args[i], "--csprngs-only")) {
161 bench_prngs = false;
162 } else if (std.mem.eql(u8, args[i], "--prngs-only")) {
163 bench_csprngs = false;
164 } else if (std.mem.eql(u8, args[i], "--short-only")) {
165 bench_long = false;
166 } else if (std.mem.eql(u8, args[i], "--long-only")) {
167 bench_short = false;
168 } else if (std.mem.eql(u8, args[i], "--help")) {
169 usage();
170 return;
171 } else {
172 usage();
173 std.os.exit(1);
174 }
175 }
176
177 if (bench_prngs) {
178 if (bench_long) {
179 inline for (prngs) |R| {
180 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
181 try stdout.print("{s} (long outputs)\n", .{R.name});
182 const result_long = try benchmark(R, count, long_block_size);
183 try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
184 }
185 }
186 }
187 if (bench_short) {
188 inline for (prngs) |R| {
189 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
190 try stdout.print("{s} (short outputs)\n", .{R.name});
191 const result_short = try benchmark(R, count, short_block_size);
192 try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
193 }
194 }
195 }
196 }
197 if (bench_csprngs) {
198 if (bench_long) {
199 inline for (csprngs) |R| {
200 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
201 try stdout.print("{s} (cryptographic, long outputs)\n", .{R.name});
202 const result_long = try benchmark(R, count, long_block_size);
203 try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
204 }
205 }
206 }
207 if (bench_short) {
208 inline for (csprngs) |R| {
209 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
210 try stdout.print("{s} (cryptographic, short outputs)\n", .{R.name});
211 const result_short = try benchmark(R, count, short_block_size);
212 try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
213 }
214 }
215 }
216 }
217}
lib/std/Random/test.zig created+473
......@@ -0,0 +1,473 @@
1const std = @import("../std.zig");
2const math = std.math;
3const Random = std.Random;
4const DefaultPrng = Random.DefaultPrng;
5const SplitMix64 = Random.SplitMix64;
6const DefaultCsprng = Random.DefaultCsprng;
7const expect = std.testing.expect;
8const expectEqual = std.testing.expectEqual;
9
10const SequentialPrng = struct {
11 const Self = @This();
12 next_value: u8,
13
14 pub fn init() Self {
15 return Self{
16 .next_value = 0,
17 };
18 }
19
20 pub fn random(self: *Self) Random {
21 return Random.init(self, fill);
22 }
23
24 pub fn fill(self: *Self, buf: []u8) void {
25 for (buf) |*b| {
26 b.* = self.next_value;
27 }
28 self.next_value +%= 1;
29 }
30};
31
32/// Do not use this PRNG! It is meant to be predictable, for the purposes of test reproducibility and coverage.
33/// Its output is just a repeat of a user-specified byte pattern.
34/// Name is a reference to this comic: https://dilbert.com/strip/2001-10-25
35const Dilbert = struct {
36 pattern: []const u8 = undefined,
37 curr_idx: usize = 0,
38
39 pub fn init(pattern: []const u8) !Dilbert {
40 if (pattern.len == 0)
41 return error.EmptyPattern;
42 var self = Dilbert{};
43 self.pattern = pattern;
44 self.curr_idx = 0;
45 return self;
46 }
47
48 pub fn random(self: *Dilbert) Random {
49 return Random.init(self, fill);
50 }
51
52 pub fn fill(self: *Dilbert, buf: []u8) void {
53 for (buf) |*byte| {
54 byte.* = self.pattern[self.curr_idx];
55 self.curr_idx = (self.curr_idx + 1) % self.pattern.len;
56 }
57 }
58
59 test "Dilbert fill" {
60 var r = try Dilbert.init("9nine");
61
62 const seq = [_]u64{
63 0x396E696E65396E69,
64 0x6E65396E696E6539,
65 0x6E696E65396E696E,
66 0x65396E696E65396E,
67 0x696E65396E696E65,
68 };
69
70 for (seq) |s| {
71 var buf0: [8]u8 = undefined;
72 var buf1: [8]u8 = undefined;
73 std.mem.writeInt(u64, &buf0, s, .big);
74 r.fill(&buf1);
75 try std.testing.expect(std.mem.eql(u8, buf0[0..], buf1[0..]));
76 }
77 }
78};
79
80test "Random int" {
81 try testRandomInt();
82 try comptime testRandomInt();
83}
84fn testRandomInt() !void {
85 var rng = SequentialPrng.init();
86 const random = rng.random();
87
88 try expect(random.int(u0) == 0);
89
90 rng.next_value = 0;
91 try expect(random.int(u1) == 0);
92 try expect(random.int(u1) == 1);
93 try expect(random.int(u2) == 2);
94 try expect(random.int(u2) == 3);
95 try expect(random.int(u2) == 0);
96
97 rng.next_value = 0xff;
98 try expect(random.int(u8) == 0xff);
99 rng.next_value = 0x11;
100 try expect(random.int(u8) == 0x11);
101
102 rng.next_value = 0xff;
103 try expect(random.int(u32) == 0xffffffff);
104 rng.next_value = 0x11;
105 try expect(random.int(u32) == 0x11111111);
106
107 rng.next_value = 0xff;
108 try expect(random.int(i32) == -1);
109 rng.next_value = 0x11;
110 try expect(random.int(i32) == 0x11111111);
111
112 rng.next_value = 0xff;
113 try expect(random.int(i8) == -1);
114 rng.next_value = 0x11;
115 try expect(random.int(i8) == 0x11);
116
117 rng.next_value = 0xff;
118 try expect(random.int(u33) == 0x1ffffffff);
119 rng.next_value = 0xff;
120 try expect(random.int(i1) == -1);
121 rng.next_value = 0xff;
122 try expect(random.int(i2) == -1);
123 rng.next_value = 0xff;
124 try expect(random.int(i33) == -1);
125}
126
127test "Random boolean" {
128 try testRandomBoolean();
129 try comptime testRandomBoolean();
130}
131fn testRandomBoolean() !void {
132 var rng = SequentialPrng.init();
133 const random = rng.random();
134
135 try expect(random.boolean() == false);
136 try expect(random.boolean() == true);
137 try expect(random.boolean() == false);
138 try expect(random.boolean() == true);
139}
140
141test "Random enum" {
142 try testRandomEnumValue();
143 try comptime testRandomEnumValue();
144}
145fn testRandomEnumValue() !void {
146 const TestEnum = enum {
147 First,
148 Second,
149 Third,
150 };
151 var rng = SequentialPrng.init();
152 const random = rng.random();
153 rng.next_value = 0;
154 try expect(random.enumValue(TestEnum) == TestEnum.First);
155 try expect(random.enumValue(TestEnum) == TestEnum.First);
156 try expect(random.enumValue(TestEnum) == TestEnum.First);
157}
158
159test "Random intLessThan" {
160 @setEvalBranchQuota(10000);
161 try testRandomIntLessThan();
162 try comptime testRandomIntLessThan();
163}
164fn testRandomIntLessThan() !void {
165 var rng = SequentialPrng.init();
166 const random = rng.random();
167
168 rng.next_value = 0xff;
169 try expect(random.uintLessThan(u8, 4) == 3);
170 try expect(rng.next_value == 0);
171 try expect(random.uintLessThan(u8, 4) == 0);
172 try expect(rng.next_value == 1);
173
174 rng.next_value = 0;
175 try expect(random.uintLessThan(u64, 32) == 0);
176
177 // trigger the bias rejection code path
178 rng.next_value = 0;
179 try expect(random.uintLessThan(u8, 3) == 0);
180 // verify we incremented twice
181 try expect(rng.next_value == 2);
182
183 rng.next_value = 0xff;
184 try expect(random.intRangeLessThan(u8, 0, 0x80) == 0x7f);
185 rng.next_value = 0xff;
186 try expect(random.intRangeLessThan(u8, 0x7f, 0xff) == 0xfe);
187
188 rng.next_value = 0xff;
189 try expect(random.intRangeLessThan(i8, 0, 0x40) == 0x3f);
190 rng.next_value = 0xff;
191 try expect(random.intRangeLessThan(i8, -0x40, 0x40) == 0x3f);
192 rng.next_value = 0xff;
193 try expect(random.intRangeLessThan(i8, -0x80, 0) == -1);
194
195 rng.next_value = 0xff;
196 try expect(random.intRangeLessThan(i3, -4, 0) == -1);
197 rng.next_value = 0xff;
198 try expect(random.intRangeLessThan(i3, -2, 2) == 1);
199}
200
201test "Random intAtMost" {
202 @setEvalBranchQuota(10000);
203 try testRandomIntAtMost();
204 try comptime testRandomIntAtMost();
205}
206fn testRandomIntAtMost() !void {
207 var rng = SequentialPrng.init();
208 const random = rng.random();
209
210 rng.next_value = 0xff;
211 try expect(random.uintAtMost(u8, 3) == 3);
212 try expect(rng.next_value == 0);
213 try expect(random.uintAtMost(u8, 3) == 0);
214
215 // trigger the bias rejection code path
216 rng.next_value = 0;
217 try expect(random.uintAtMost(u8, 2) == 0);
218 // verify we incremented twice
219 try expect(rng.next_value == 2);
220
221 rng.next_value = 0xff;
222 try expect(random.intRangeAtMost(u8, 0, 0x7f) == 0x7f);
223 rng.next_value = 0xff;
224 try expect(random.intRangeAtMost(u8, 0x7f, 0xfe) == 0xfe);
225
226 rng.next_value = 0xff;
227 try expect(random.intRangeAtMost(i8, 0, 0x3f) == 0x3f);
228 rng.next_value = 0xff;
229 try expect(random.intRangeAtMost(i8, -0x40, 0x3f) == 0x3f);
230 rng.next_value = 0xff;
231 try expect(random.intRangeAtMost(i8, -0x80, -1) == -1);
232
233 rng.next_value = 0xff;
234 try expect(random.intRangeAtMost(i3, -4, -1) == -1);
235 rng.next_value = 0xff;
236 try expect(random.intRangeAtMost(i3, -2, 1) == 1);
237
238 try expect(random.uintAtMost(u0, 0) == 0);
239}
240
241test "Random Biased" {
242 var prng = DefaultPrng.init(0);
243 const random = prng.random();
244 // Not thoroughly checking the logic here.
245 // Just want to execute all the paths with different types.
246
247 try expect(random.uintLessThanBiased(u1, 1) == 0);
248 try expect(random.uintLessThanBiased(u32, 10) < 10);
249 try expect(random.uintLessThanBiased(u64, 20) < 20);
250
251 try expect(random.uintAtMostBiased(u0, 0) == 0);
252 try expect(random.uintAtMostBiased(u1, 0) <= 0);
253 try expect(random.uintAtMostBiased(u32, 10) <= 10);
254 try expect(random.uintAtMostBiased(u64, 20) <= 20);
255
256 try expect(random.intRangeLessThanBiased(u1, 0, 1) == 0);
257 try expect(random.intRangeLessThanBiased(i1, -1, 0) == -1);
258 try expect(random.intRangeLessThanBiased(u32, 10, 20) >= 10);
259 try expect(random.intRangeLessThanBiased(i32, 10, 20) >= 10);
260 try expect(random.intRangeLessThanBiased(u64, 20, 40) >= 20);
261 try expect(random.intRangeLessThanBiased(i64, 20, 40) >= 20);
262
263 // uncomment for broken module error:
264 //expect(random.intRangeAtMostBiased(u0, 0, 0) == 0);
265 try expect(random.intRangeAtMostBiased(u1, 0, 1) >= 0);
266 try expect(random.intRangeAtMostBiased(i1, -1, 0) >= -1);
267 try expect(random.intRangeAtMostBiased(u32, 10, 20) >= 10);
268 try expect(random.intRangeAtMostBiased(i32, 10, 20) >= 10);
269 try expect(random.intRangeAtMostBiased(u64, 20, 40) >= 20);
270 try expect(random.intRangeAtMostBiased(i64, 20, 40) >= 20);
271}
272
273test "splitmix64 sequence" {
274 var r = SplitMix64.init(0xaeecf86f7878dd75);
275
276 const seq = [_]u64{
277 0x5dbd39db0178eb44,
278 0xa9900fb66b397da3,
279 0x5c1a28b1aeebcf5c,
280 0x64a963238f776912,
281 0xc6d4177b21d1c0ab,
282 0xb2cbdbdb5ea35394,
283 };
284
285 for (seq) |s| {
286 try expect(s == r.next());
287 }
288}
289
290// Actual Random helper function tests, pcg engine is assumed correct.
291test "Random float correctness" {
292 var prng = DefaultPrng.init(0);
293 const random = prng.random();
294
295 var i: usize = 0;
296 while (i < 1000) : (i += 1) {
297 const val1 = random.float(f32);
298 try expect(val1 >= 0.0);
299 try expect(val1 < 1.0);
300
301 const val2 = random.float(f64);
302 try expect(val2 >= 0.0);
303 try expect(val2 < 1.0);
304 }
305}
306
307// Check the "astronomically unlikely" code paths.
308test "Random float coverage" {
309 var prng = try Dilbert.init(&[_]u8{0});
310 const random = prng.random();
311
312 const rand_f64 = random.float(f64);
313 const rand_f32 = random.float(f32);
314
315 try expect(rand_f32 == 0.0);
316 try expect(rand_f64 == 0.0);
317}
318
319test "Random float chi-square goodness of fit" {
320 const num_numbers = 100000;
321 const num_buckets = 1000;
322
323 var f32_hist = std.AutoHashMap(u32, u32).init(std.testing.allocator);
324 defer f32_hist.deinit();
325 var f64_hist = std.AutoHashMap(u64, u32).init(std.testing.allocator);
326 defer f64_hist.deinit();
327
328 var prng = DefaultPrng.init(0);
329 const random = prng.random();
330
331 var i: usize = 0;
332 while (i < num_numbers) : (i += 1) {
333 const rand_f32 = random.float(f32);
334 const rand_f64 = random.float(f64);
335 const f32_put = try f32_hist.getOrPut(@as(u32, @intFromFloat(rand_f32 * @as(f32, @floatFromInt(num_buckets)))));
336 if (f32_put.found_existing) {
337 f32_put.value_ptr.* += 1;
338 } else {
339 f32_put.value_ptr.* = 1;
340 }
341 const f64_put = try f64_hist.getOrPut(@as(u32, @intFromFloat(rand_f64 * @as(f64, @floatFromInt(num_buckets)))));
342 if (f64_put.found_existing) {
343 f64_put.value_ptr.* += 1;
344 } else {
345 f64_put.value_ptr.* = 1;
346 }
347 }
348
349 var f32_total_variance: f64 = 0;
350 var f64_total_variance: f64 = 0;
351
352 {
353 var j: u32 = 0;
354 while (j < num_buckets) : (j += 1) {
355 const count = @as(f64, @floatFromInt((if (f32_hist.get(j)) |v| v else 0)));
356 const expected = @as(f64, @floatFromInt(num_numbers)) / @as(f64, @floatFromInt(num_buckets));
357 const delta = count - expected;
358 const variance = (delta * delta) / expected;
359 f32_total_variance += variance;
360 }
361 }
362
363 {
364 var j: u64 = 0;
365 while (j < num_buckets) : (j += 1) {
366 const count = @as(f64, @floatFromInt((if (f64_hist.get(j)) |v| v else 0)));
367 const expected = @as(f64, @floatFromInt(num_numbers)) / @as(f64, @floatFromInt(num_buckets));
368 const delta = count - expected;
369 const variance = (delta * delta) / expected;
370 f64_total_variance += variance;
371 }
372 }
373
374 // Accept p-values >= 0.05.
375 // Critical value is calculated by opening a Python interpreter and running:
376 // scipy.stats.chi2.isf(0.05, num_buckets - 1)
377 const critical_value = 1073.6426506574246;
378 try expect(f32_total_variance < critical_value);
379 try expect(f64_total_variance < critical_value);
380}
381
382test "Random shuffle" {
383 var prng = DefaultPrng.init(0);
384 const random = prng.random();
385
386 var seq = [_]u8{ 0, 1, 2, 3, 4 };
387 var seen = [_]bool{false} ** 5;
388
389 var i: usize = 0;
390 while (i < 1000) : (i += 1) {
391 random.shuffle(u8, seq[0..]);
392 seen[seq[0]] = true;
393 try expect(sumArray(seq[0..]) == 10);
394 }
395
396 // we should see every entry at the head at least once
397 for (seen) |e| {
398 try expect(e == true);
399 }
400}
401
402fn sumArray(s: []const u8) u32 {
403 var r: u32 = 0;
404 for (s) |e|
405 r += e;
406 return r;
407}
408
409test "Random range" {
410 var prng = DefaultPrng.init(0);
411 const random = prng.random();
412
413 try testRange(random, -4, 3);
414 try testRange(random, -4, -1);
415 try testRange(random, 10, 14);
416 try testRange(random, -0x80, 0x7f);
417}
418
419fn testRange(r: Random, start: i8, end: i8) !void {
420 try testRangeBias(r, start, end, true);
421 try testRangeBias(r, start, end, false);
422}
423fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void {
424 const count = @as(usize, @intCast(@as(i32, end) - @as(i32, start)));
425 var values_buffer = [_]bool{false} ** 0x100;
426 const values = values_buffer[0..count];
427 var i: usize = 0;
428 while (i < count) {
429 const value: i32 = if (biased) r.intRangeLessThanBiased(i8, start, end) else r.intRangeLessThan(i8, start, end);
430 const index = @as(usize, @intCast(value - start));
431 if (!values[index]) {
432 i += 1;
433 values[index] = true;
434 }
435 }
436}
437
438test "CSPRNG" {
439 var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined;
440 std.crypto.random.bytes(&secret_seed);
441 var csprng = DefaultCsprng.init(secret_seed);
442 const random = csprng.random();
443 const a = random.int(u64);
444 const b = random.int(u64);
445 const c = random.int(u64);
446 try expect(a ^ b ^ c != 0);
447}
448
449test "Random weightedIndex" {
450 // Make sure weightedIndex works for various integers and floats
451 inline for (.{ u64, i4, f32, f64 }) |T| {
452 var prng = DefaultPrng.init(0);
453 const random = prng.random();
454
455 const proportions = [_]T{ 2, 1, 1, 2 };
456 var counts = [_]f64{ 0, 0, 0, 0 };
457
458 const n_trials: u64 = 10_000;
459 var i: usize = 0;
460 while (i < n_trials) : (i += 1) {
461 const pick = random.weightedIndex(T, &proportions);
462 counts[pick] += 1;
463 }
464
465 // We expect the first and last counts to be roughly 2x the second and third
466 const approxEqRel = std.math.approxEqRel;
467 // Define "roughly" to be within 10%
468 const tolerance = 0.1;
469 try std.testing.expect(approxEqRel(f64, counts[0], counts[1] * 2, tolerance));
470 try std.testing.expect(approxEqRel(f64, counts[1], counts[2], tolerance));
471 try std.testing.expect(approxEqRel(f64, counts[2] * 2, counts[3], tolerance));
472 }
473}
lib/std/Random/ziggurat.zig created+170
......@@ -0,0 +1,170 @@
1//! Implements [ZIGNOR][1] (Jurgen A. Doornik, 2005, Nuffield College, Oxford).
2//!
3//! [1]: https://www.doornik.com/research/ziggurat.pdf
4//!
5//! rust/rand used as a reference;
6//!
7//! NOTE: This seems interesting but reference code is a bit hard to grok:
8//! https://sbarral.github.io/etf.
9
10const std = @import("../std.zig");
11const builtin = @import("builtin");
12const math = std.math;
13const Random = std.Random;
14
15pub fn next_f64(random: Random, comptime tables: ZigTable) f64 {
16 while (true) {
17 // We manually construct a float from parts as we can avoid an extra random lookup here by
18 // using the unused exponent for the lookup table entry.
19 const bits = random.int(u64);
20 const i = @as(usize, @as(u8, @truncate(bits)));
21
22 const u = blk: {
23 if (tables.is_symmetric) {
24 // Generate a value in the range [2, 4) and scale into [-1, 1)
25 const repr = ((0x3ff + 1) << 52) | (bits >> 12);
26 break :blk @as(f64, @bitCast(repr)) - 3.0;
27 } else {
28 // Generate a value in the range [1, 2) and scale into (0, 1)
29 const repr = (0x3ff << 52) | (bits >> 12);
30 break :blk @as(f64, @bitCast(repr)) - (1.0 - math.floatEps(f64) / 2.0);
31 }
32 };
33
34 const x = u * tables.x[i];
35 const test_x = if (tables.is_symmetric) @abs(x) else x;
36
37 // equivalent to |u| < tables.x[i+1] / tables.x[i] (or u < tables.x[i+1] / tables.x[i])
38 if (test_x < tables.x[i + 1]) {
39 return x;
40 }
41
42 if (i == 0) {
43 return tables.zero_case(random, u);
44 }
45
46 // equivalent to f1 + DRanU() * (f0 - f1) < 1
47 if (tables.f[i + 1] + (tables.f[i] - tables.f[i + 1]) * random.float(f64) < tables.pdf(x)) {
48 return x;
49 }
50 }
51}
52
53pub const ZigTable = struct {
54 r: f64,
55 x: [257]f64,
56 f: [257]f64,
57
58 // probability density function used as a fallback
59 pdf: fn (f64) f64,
60 // whether the distribution is symmetric
61 is_symmetric: bool,
62 // fallback calculation in the case we are in the 0 block
63 zero_case: fn (Random, f64) f64,
64};
65
66// zigNorInit
67pub fn ZigTableGen(
68 comptime is_symmetric: bool,
69 comptime r: f64,
70 comptime v: f64,
71 comptime f: fn (f64) f64,
72 comptime f_inv: fn (f64) f64,
73 comptime zero_case: fn (Random, f64) f64,
74) ZigTable {
75 var tables: ZigTable = undefined;
76
77 tables.is_symmetric = is_symmetric;
78 tables.r = r;
79 tables.pdf = f;
80 tables.zero_case = zero_case;
81
82 tables.x[0] = v / f(r);
83 tables.x[1] = r;
84
85 for (tables.x[2..256], 0..) |*entry, i| {
86 const last = tables.x[2 + i - 1];
87 entry.* = f_inv(v / last + f(last));
88 }
89 tables.x[256] = 0;
90
91 for (tables.f[0..], 0..) |*entry, i| {
92 entry.* = f(tables.x[i]);
93 }
94
95 return tables;
96}
97
98// N(0, 1)
99pub const NormDist = blk: {
100 @setEvalBranchQuota(30000);
101 break :blk ZigTableGen(true, norm_r, norm_v, norm_f, norm_f_inv, norm_zero_case);
102};
103
104pub const norm_r = 3.6541528853610088;
105pub const norm_v = 0.00492867323399;
106
107pub fn norm_f(x: f64) f64 {
108 return @exp(-x * x / 2.0);
109}
110pub fn norm_f_inv(y: f64) f64 {
111 return @sqrt(-2.0 * @log(y));
112}
113pub fn norm_zero_case(random: Random, u: f64) f64 {
114 var x: f64 = 1;
115 var y: f64 = 0;
116
117 while (-2.0 * y < x * x) {
118 x = @log(random.float(f64)) / norm_r;
119 y = @log(random.float(f64));
120 }
121
122 if (u < 0) {
123 return x - norm_r;
124 } else {
125 return norm_r - x;
126 }
127}
128
129test "normal dist sanity" {
130 var prng = Random.DefaultPrng.init(0);
131 const random = prng.random();
132
133 var i: usize = 0;
134 while (i < 1000) : (i += 1) {
135 _ = random.floatNorm(f64);
136 }
137}
138
139// Exp(1)
140pub const ExpDist = blk: {
141 @setEvalBranchQuota(30000);
142 break :blk ZigTableGen(false, exp_r, exp_v, exp_f, exp_f_inv, exp_zero_case);
143};
144
145pub const exp_r = 7.69711747013104972;
146pub const exp_v = 0.0039496598225815571993;
147
148pub fn exp_f(x: f64) f64 {
149 return @exp(-x);
150}
151pub fn exp_f_inv(y: f64) f64 {
152 return -@log(y);
153}
154pub fn exp_zero_case(random: Random, _: f64) f64 {
155 return exp_r - @log(random.float(f64));
156}
157
158test "exp dist smoke test" {
159 var prng = Random.DefaultPrng.init(0);
160 const random = prng.random();
161
162 var i: usize = 0;
163 while (i < 1000) : (i += 1) {
164 _ = random.floatExp(f64);
165 }
166}
167
168test {
169 _ = NormDist;
170}
lib/std/Thread/RwLock.zig+1-1
......@@ -328,7 +328,7 @@ test "RwLock - concurrent access" {
328328 }
329329
330330 fn writer(self: *Self, thread_idx: usize) !void {
331 var prng = std.rand.DefaultPrng.init(thread_idx);
331 var prng = std.Random.DefaultPrng.init(thread_idx);
332332 var rnd = prng.random();
333333
334334 while (true) {
lib/std/crypto/benchmark.zig+1-1
......@@ -10,7 +10,7 @@ const crypto = std.crypto;
1010const KiB = 1024;
1111const MiB = 1024 * KiB;
1212
13var prng = std.rand.DefaultPrng.init(0);
13var prng = std.Random.DefaultPrng.init(0);
1414const random = prng.random();
1515
1616const Crypto = struct {
lib/std/crypto/kyber_d00.zig+1-1
......@@ -110,7 +110,7 @@ const assert = std.debug.assert;
110110const crypto = std.crypto;
111111const math = std.math;
112112const mem = std.mem;
113const RndGen = std.rand.DefaultPrng;
113const RndGen = std.Random.DefaultPrng;
114114const sha3 = crypto.hash.sha3;
115115
116116// Q is the parameter q ≡ 3329 = 2¹¹ + 2¹⁰ + 2⁸ + 1.
lib/std/crypto/tlcsprng.zig+2-2
......@@ -10,7 +10,7 @@ const os = std.os;
1010
1111/// We use this as a layer of indirection because global const pointers cannot
1212/// point to thread-local variables.
13pub const interface = std.rand.Random{
13pub const interface = std.Random{
1414 .ptr = undefined,
1515 .fillFn = tlsCsprngFill,
1616};
......@@ -43,7 +43,7 @@ const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
4343}) orelse true;
4444const is_haiku = builtin.os.tag == .haiku;
4545
46const Rng = std.rand.DefaultCsprng;
46const Rng = std.Random.DefaultCsprng;
4747
4848const Context = struct {
4949 init_state: enum(u8) { uninitialized = 0, initialized, failed },
lib/std/hash/benchmark.zig+1-1
......@@ -10,7 +10,7 @@ const KiB = 1024;
1010const MiB = 1024 * KiB;
1111const GiB = 1024 * MiB;
1212
13var prng = std.rand.DefaultPrng.init(0);
13var prng = std.Random.DefaultPrng.init(0);
1414const random = prng.random();
1515
1616const Hash = struct {
lib/std/hash_map.zig+2-2
......@@ -1884,7 +1884,7 @@ test "std.hash_map put and remove loop in random order" {
18841884 while (i < size) : (i += 1) {
18851885 try keys.append(i);
18861886 }
1887 var prng = std.rand.DefaultPrng.init(0);
1887 var prng = std.Random.DefaultPrng.init(0);
18881888 const random = prng.random();
18891889
18901890 while (i < iterations) : (i += 1) {
......@@ -1916,7 +1916,7 @@ test "std.hash_map remove one million elements in random order" {
19161916 keys.append(i) catch unreachable;
19171917 }
19181918
1919 var prng = std.rand.DefaultPrng.init(0);
1919 var prng = std.Random.DefaultPrng.init(0);
19201920 const random = prng.random();
19211921 random.shuffle(u32, keys.items);
19221922
lib/std/heap/arena_allocator.zig+1-1
......@@ -250,7 +250,7 @@ test "ArenaAllocator (reset with preheating)" {
250250 var arena_allocator = ArenaAllocator.init(std.testing.allocator);
251251 defer arena_allocator.deinit();
252252 // provides some variance in the allocated data
253 var rng_src = std.rand.DefaultPrng.init(19930913);
253 var rng_src = std.Random.DefaultPrng.init(19930913);
254254 const random = rng_src.random();
255255 var rounds: usize = 25;
256256 while (rounds > 0) {
lib/std/io/test.zig+1-1
......@@ -1,6 +1,6 @@
11const std = @import("std");
22const io = std.io;
3const DefaultPrng = std.rand.DefaultPrng;
3const DefaultPrng = std.Random.DefaultPrng;
44const expect = std.testing.expect;
55const expectEqual = std.testing.expectEqual;
66const expectError = std.testing.expectError;
lib/std/math/big/rational.zig+1-1
......@@ -594,7 +594,7 @@ test "big.rational toFloat" {
594594test "big.rational set/to Float round-trip" {
595595 var a = try Rational.init(testing.allocator);
596596 defer a.deinit();
597 var prng = std.rand.DefaultPrng.init(0x5EED);
597 var prng = std.Random.DefaultPrng.init(0x5EED);
598598 const random = prng.random();
599599 var i: usize = 0;
600600 while (i < 512) : (i += 1) {
lib/std/mem.zig+1-1
......@@ -4423,7 +4423,7 @@ test "read/write(Var)PackedInt" {
44234423
44244424 const foreign_endian: Endian = if (native_endian == .big) .little else .big;
44254425 const expect = std.testing.expect;
4426 var prng = std.rand.DefaultPrng.init(1234);
4426 var prng = std.Random.DefaultPrng.init(1234);
44274427 const random = prng.random();
44284428
44294429 @setEvalBranchQuota(10_000);
lib/std/priority_dequeue.zig+7-7
......@@ -866,7 +866,7 @@ test "std.PriorityDequeue: shrinkAndFree" {
866866}
867867
868868test "std.PriorityDequeue: fuzz testing min" {
869 var prng = std.rand.DefaultPrng.init(0x12345678);
869 var prng = std.Random.DefaultPrng.init(0x12345678);
870870 const random = prng.random();
871871
872872 const test_case_count = 100;
......@@ -878,7 +878,7 @@ test "std.PriorityDequeue: fuzz testing min" {
878878 }
879879}
880880
881fn fuzzTestMin(rng: std.rand.Random, comptime queue_size: usize) !void {
881fn fuzzTestMin(rng: std.Random, comptime queue_size: usize) !void {
882882 const allocator = testing.allocator;
883883 const items = try generateRandomSlice(allocator, rng, queue_size);
884884
......@@ -895,7 +895,7 @@ fn fuzzTestMin(rng: std.rand.Random, comptime queue_size: usize) !void {
895895}
896896
897897test "std.PriorityDequeue: fuzz testing max" {
898 var prng = std.rand.DefaultPrng.init(0x87654321);
898 var prng = std.Random.DefaultPrng.init(0x87654321);
899899 const random = prng.random();
900900
901901 const test_case_count = 100;
......@@ -907,7 +907,7 @@ test "std.PriorityDequeue: fuzz testing max" {
907907 }
908908}
909909
910fn fuzzTestMax(rng: std.rand.Random, queue_size: usize) !void {
910fn fuzzTestMax(rng: std.Random, queue_size: usize) !void {
911911 const allocator = testing.allocator;
912912 const items = try generateRandomSlice(allocator, rng, queue_size);
913913
......@@ -924,7 +924,7 @@ fn fuzzTestMax(rng: std.rand.Random, queue_size: usize) !void {
924924}
925925
926926test "std.PriorityDequeue: fuzz testing min and max" {
927 var prng = std.rand.DefaultPrng.init(0x87654321);
927 var prng = std.Random.DefaultPrng.init(0x87654321);
928928 const random = prng.random();
929929
930930 const test_case_count = 100;
......@@ -936,7 +936,7 @@ test "std.PriorityDequeue: fuzz testing min and max" {
936936 }
937937}
938938
939fn fuzzTestMinMax(rng: std.rand.Random, queue_size: usize) !void {
939fn fuzzTestMinMax(rng: std.Random, queue_size: usize) !void {
940940 const allocator = testing.allocator;
941941 const items = try generateRandomSlice(allocator, rng, queue_size);
942942
......@@ -963,7 +963,7 @@ fn fuzzTestMinMax(rng: std.rand.Random, queue_size: usize) !void {
963963 }
964964}
965965
966fn generateRandomSlice(allocator: std.mem.Allocator, rng: std.rand.Random, size: usize) ![]u32 {
966fn generateRandomSlice(allocator: std.mem.Allocator, rng: std.Random, size: usize) ![]u32 {
967967 var array = std.ArrayList(u32).init(allocator);
968968 try array.ensureTotalCapacity(size);
969969
lib/std/rand.zig deleted-460
......@@ -1,460 +0,0 @@
1//! The engines provided here should be initialized from an external source.
2//! For a thread-local cryptographically secure pseudo random number generator,
3//! use `std.crypto.random`.
4//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will
5//! be faster and use substantially less stack space.
6
7const std = @import("std.zig");
8const builtin = @import("builtin");
9const assert = std.debug.assert;
10const mem = std.mem;
11const math = std.math;
12const maxInt = std.math.maxInt;
13
14/// Fast unbiased random numbers.
15pub const DefaultPrng = Xoshiro256;
16
17/// Cryptographically secure random numbers.
18pub const DefaultCsprng = ChaCha;
19
20pub const Ascon = @import("rand/Ascon.zig");
21pub const ChaCha = @import("rand/ChaCha.zig");
22
23pub const Isaac64 = @import("rand/Isaac64.zig");
24pub const Pcg = @import("rand/Pcg.zig");
25pub const Xoroshiro128 = @import("rand/Xoroshiro128.zig");
26pub const Xoshiro256 = @import("rand/Xoshiro256.zig");
27pub const Sfc64 = @import("rand/Sfc64.zig");
28pub const RomuTrio = @import("rand/RomuTrio.zig");
29pub const ziggurat = @import("rand/ziggurat.zig");
30
31pub const Random = struct {
32 ptr: *anyopaque,
33 fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
34
35 pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
36 const Ptr = @TypeOf(pointer);
37 assert(@typeInfo(Ptr) == .Pointer); // Must be a pointer
38 assert(@typeInfo(Ptr).Pointer.size == .One); // Must be a single-item pointer
39 assert(@typeInfo(@typeInfo(Ptr).Pointer.child) == .Struct); // Must point to a struct
40 const gen = struct {
41 fn fill(ptr: *anyopaque, buf: []u8) void {
42 const self: Ptr = @ptrCast(@alignCast(ptr));
43 fillFn(self, buf);
44 }
45 };
46
47 return .{
48 .ptr = pointer,
49 .fillFn = gen.fill,
50 };
51 }
52
53 /// Read random bytes into the specified buffer until full.
54 pub fn bytes(r: Random, buf: []u8) void {
55 r.fillFn(r.ptr, buf);
56 }
57
58 pub fn boolean(r: Random) bool {
59 return r.int(u1) != 0;
60 }
61
62 /// Returns a random value from an enum, evenly distributed.
63 ///
64 /// Note that this will not yield consistent results across all targets
65 /// due to dependence on the representation of `usize` as an index.
66 /// See `enumValueWithIndex` for further commentary.
67 pub inline fn enumValue(r: Random, comptime EnumType: type) EnumType {
68 return r.enumValueWithIndex(EnumType, usize);
69 }
70
71 /// Returns a random value from an enum, evenly distributed.
72 ///
73 /// An index into an array of all named values is generated using the
74 /// specified `Index` type to determine the return value.
75 /// This allows for results to be independent of `usize` representation.
76 ///
77 /// Prefer `enumValue` if this isn't important.
78 ///
79 /// See `uintLessThan`, which this function uses in most cases,
80 /// for commentary on the runtime of this function.
81 pub fn enumValueWithIndex(r: Random, comptime EnumType: type, comptime Index: type) EnumType {
82 comptime assert(@typeInfo(EnumType) == .Enum);
83
84 // We won't use int -> enum casting because enum elements can have
85 // arbitrary values. Instead we'll randomly pick one of the type's values.
86 const values = comptime std.enums.values(EnumType);
87 comptime assert(values.len > 0); // can't return anything
88 comptime assert(maxInt(Index) >= values.len - 1); // can't access all values
89 comptime if (values.len == 1) return values[0];
90
91 const index = if (comptime values.len - 1 == maxInt(Index))
92 r.int(Index)
93 else
94 r.uintLessThan(Index, values.len);
95
96 const MinInt = MinArrayIndex(Index);
97 return values[@as(MinInt, @intCast(index))];
98 }
99
100 /// Returns a random int `i` such that `minInt(T) <= i <= maxInt(T)`.
101 /// `i` is evenly distributed.
102 pub fn int(r: Random, comptime T: type) T {
103 const bits = @typeInfo(T).Int.bits;
104 const UnsignedT = std.meta.Int(.unsigned, bits);
105 const ceil_bytes = comptime std.math.divCeil(u16, bits, 8) catch unreachable;
106 const ByteAlignedT = std.meta.Int(.unsigned, ceil_bytes * 8);
107
108 var rand_bytes: [ceil_bytes]u8 = undefined;
109 r.bytes(&rand_bytes);
110
111 // use LE instead of native endian for better portability maybe?
112 // TODO: endian portability is pointless if the underlying prng isn't endian portable.
113 // TODO: document the endian portability of this library.
114 const byte_aligned_result = mem.readInt(ByteAlignedT, &rand_bytes, .little);
115 const unsigned_result: UnsignedT = @truncate(byte_aligned_result);
116 return @bitCast(unsigned_result);
117 }
118
119 /// Constant-time implementation off `uintLessThan`.
120 /// The results of this function may be biased.
121 pub fn uintLessThanBiased(r: Random, comptime T: type, less_than: T) T {
122 comptime assert(@typeInfo(T).Int.signedness == .unsigned);
123 assert(0 < less_than);
124 return limitRangeBiased(T, r.int(T), less_than);
125 }
126
127 /// Returns an evenly distributed random unsigned integer `0 <= i < less_than`.
128 /// This function assumes that the underlying `fillFn` produces evenly distributed values.
129 /// Within this assumption, the runtime of this function is exponentially distributed.
130 /// If `fillFn` were backed by a true random generator,
131 /// the runtime of this function would technically be unbounded.
132 /// However, if `fillFn` is backed by any evenly distributed pseudo random number generator,
133 /// this function is guaranteed to return.
134 /// If you need deterministic runtime bounds, use `uintLessThanBiased`.
135 pub fn uintLessThan(r: Random, comptime T: type, less_than: T) T {
136 comptime assert(@typeInfo(T).Int.signedness == .unsigned);
137 const bits = @typeInfo(T).Int.bits;
138 assert(0 < less_than);
139
140 // adapted from:
141 // http://www.pcg-random.org/posts/bounded-rands.html
142 // "Lemire's (with an extra tweak from me)"
143 var x = r.int(T);
144 var m = math.mulWide(T, x, less_than);
145 var l: T = @truncate(m);
146 if (l < less_than) {
147 var t = -%less_than;
148
149 if (t >= less_than) {
150 t -= less_than;
151 if (t >= less_than) {
152 t %= less_than;
153 }
154 }
155 while (l < t) {
156 x = r.int(T);
157 m = math.mulWide(T, x, less_than);
158 l = @truncate(m);
159 }
160 }
161 return @intCast(m >> bits);
162 }
163
164 /// Constant-time implementation off `uintAtMost`.
165 /// The results of this function may be biased.
166 pub fn uintAtMostBiased(r: Random, comptime T: type, at_most: T) T {
167 assert(@typeInfo(T).Int.signedness == .unsigned);
168 if (at_most == maxInt(T)) {
169 // have the full range
170 return r.int(T);
171 }
172 return r.uintLessThanBiased(T, at_most + 1);
173 }
174
175 /// Returns an evenly distributed random unsigned integer `0 <= i <= at_most`.
176 /// See `uintLessThan`, which this function uses in most cases,
177 /// for commentary on the runtime of this function.
178 pub fn uintAtMost(r: Random, comptime T: type, at_most: T) T {
179 assert(@typeInfo(T).Int.signedness == .unsigned);
180 if (at_most == maxInt(T)) {
181 // have the full range
182 return r.int(T);
183 }
184 return r.uintLessThan(T, at_most + 1);
185 }
186
187 /// Constant-time implementation off `intRangeLessThan`.
188 /// The results of this function may be biased.
189 pub fn intRangeLessThanBiased(r: Random, comptime T: type, at_least: T, less_than: T) T {
190 assert(at_least < less_than);
191 const info = @typeInfo(T).Int;
192 if (info.signedness == .signed) {
193 // Two's complement makes this math pretty easy.
194 const UnsignedT = std.meta.Int(.unsigned, info.bits);
195 const lo: UnsignedT = @bitCast(at_least);
196 const hi: UnsignedT = @bitCast(less_than);
197 const result = lo +% r.uintLessThanBiased(UnsignedT, hi -% lo);
198 return @bitCast(result);
199 } else {
200 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
201 return at_least + r.uintLessThanBiased(T, less_than - at_least);
202 }
203 }
204
205 /// Returns an evenly distributed random integer `at_least <= i < less_than`.
206 /// See `uintLessThan`, which this function uses in most cases,
207 /// for commentary on the runtime of this function.
208 pub fn intRangeLessThan(r: Random, comptime T: type, at_least: T, less_than: T) T {
209 assert(at_least < less_than);
210 const info = @typeInfo(T).Int;
211 if (info.signedness == .signed) {
212 // Two's complement makes this math pretty easy.
213 const UnsignedT = std.meta.Int(.unsigned, info.bits);
214 const lo: UnsignedT = @bitCast(at_least);
215 const hi: UnsignedT = @bitCast(less_than);
216 const result = lo +% r.uintLessThan(UnsignedT, hi -% lo);
217 return @bitCast(result);
218 } else {
219 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
220 return at_least + r.uintLessThan(T, less_than - at_least);
221 }
222 }
223
224 /// Constant-time implementation off `intRangeAtMostBiased`.
225 /// The results of this function may be biased.
226 pub fn intRangeAtMostBiased(r: Random, comptime T: type, at_least: T, at_most: T) T {
227 assert(at_least <= at_most);
228 const info = @typeInfo(T).Int;
229 if (info.signedness == .signed) {
230 // Two's complement makes this math pretty easy.
231 const UnsignedT = std.meta.Int(.unsigned, info.bits);
232 const lo: UnsignedT = @bitCast(at_least);
233 const hi: UnsignedT = @bitCast(at_most);
234 const result = lo +% r.uintAtMostBiased(UnsignedT, hi -% lo);
235 return @bitCast(result);
236 } else {
237 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
238 return at_least + r.uintAtMostBiased(T, at_most - at_least);
239 }
240 }
241
242 /// Returns an evenly distributed random integer `at_least <= i <= at_most`.
243 /// See `uintLessThan`, which this function uses in most cases,
244 /// for commentary on the runtime of this function.
245 pub fn intRangeAtMost(r: Random, comptime T: type, at_least: T, at_most: T) T {
246 assert(at_least <= at_most);
247 const info = @typeInfo(T).Int;
248 if (info.signedness == .signed) {
249 // Two's complement makes this math pretty easy.
250 const UnsignedT = std.meta.Int(.unsigned, info.bits);
251 const lo: UnsignedT = @bitCast(at_least);
252 const hi: UnsignedT = @bitCast(at_most);
253 const result = lo +% r.uintAtMost(UnsignedT, hi -% lo);
254 return @bitCast(result);
255 } else {
256 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
257 return at_least + r.uintAtMost(T, at_most - at_least);
258 }
259 }
260
261 /// Return a floating point value evenly distributed in the range [0, 1).
262 pub fn float(r: Random, comptime T: type) T {
263 // Generate a uniformly random value for the mantissa.
264 // Then generate an exponentially biased random value for the exponent.
265 // This covers every possible value in the range.
266 switch (T) {
267 f32 => {
268 // Use 23 random bits for the mantissa, and the rest for the exponent.
269 // If all 41 bits are zero, generate additional random bits, until a
270 // set bit is found, or 126 bits have been generated.
271 const rand = r.int(u64);
272 var rand_lz = @clz(rand);
273 if (rand_lz >= 41) {
274 // TODO: when #5177 or #489 is implemented,
275 // tell the compiler it is unlikely (1/2^41) to reach this point.
276 // (Same for the if branch and the f64 calculations below.)
277 rand_lz = 41 + @clz(r.int(u64));
278 if (rand_lz == 41 + 64) {
279 // It is astronomically unlikely to reach this point.
280 rand_lz += @clz(r.int(u32) | 0x7FF);
281 }
282 }
283 const mantissa: u23 = @truncate(rand);
284 const exponent = @as(u32, 126 - rand_lz) << 23;
285 return @bitCast(exponent | mantissa);
286 },
287 f64 => {
288 // Use 52 random bits for the mantissa, and the rest for the exponent.
289 // If all 12 bits are zero, generate additional random bits, until a
290 // set bit is found, or 1022 bits have been generated.
291 const rand = r.int(u64);
292 var rand_lz: u64 = @clz(rand);
293 if (rand_lz >= 12) {
294 rand_lz = 12;
295 while (true) {
296 // It is astronomically unlikely for this loop to execute more than once.
297 const addl_rand_lz = @clz(r.int(u64));
298 rand_lz += addl_rand_lz;
299 if (addl_rand_lz != 64) {
300 break;
301 }
302 if (rand_lz >= 1022) {
303 rand_lz = 1022;
304 break;
305 }
306 }
307 }
308 const mantissa = rand & 0xFFFFFFFFFFFFF;
309 const exponent = (1022 - rand_lz) << 52;
310 return @bitCast(exponent | mantissa);
311 },
312 else => @compileError("unknown floating point type"),
313 }
314 }
315
316 /// Return a floating point value normally distributed with mean = 0, stddev = 1.
317 ///
318 /// To use different parameters, use: floatNorm(...) * desiredStddev + desiredMean.
319 pub fn floatNorm(r: Random, comptime T: type) T {
320 const value = ziggurat.next_f64(r, ziggurat.NormDist);
321 switch (T) {
322 f32 => return @floatCast(value),
323 f64 => return value,
324 else => @compileError("unknown floating point type"),
325 }
326 }
327
328 /// Return an exponentially distributed float with a rate parameter of 1.
329 ///
330 /// To use a different rate parameter, use: floatExp(...) / desiredRate.
331 pub fn floatExp(r: Random, comptime T: type) T {
332 const value = ziggurat.next_f64(r, ziggurat.ExpDist);
333 switch (T) {
334 f32 => return @floatCast(value),
335 f64 => return value,
336 else => @compileError("unknown floating point type"),
337 }
338 }
339
340 /// Shuffle a slice into a random order.
341 ///
342 /// Note that this will not yield consistent results across all targets
343 /// due to dependence on the representation of `usize` as an index.
344 /// See `shuffleWithIndex` for further commentary.
345 pub inline fn shuffle(r: Random, comptime T: type, buf: []T) void {
346 r.shuffleWithIndex(T, buf, usize);
347 }
348
349 /// Shuffle a slice into a random order, using an index of a
350 /// specified type to maintain distribution across targets.
351 /// Asserts the index type can represent `buf.len`.
352 ///
353 /// Indexes into the slice are generated using the specified `Index`
354 /// type, which determines distribution properties. This allows for
355 /// results to be independent of `usize` representation.
356 ///
357 /// Prefer `shuffle` if this isn't important.
358 ///
359 /// See `intRangeLessThan`, which this function uses,
360 /// for commentary on the runtime of this function.
361 pub fn shuffleWithIndex(r: Random, comptime T: type, buf: []T, comptime Index: type) void {
362 const MinInt = MinArrayIndex(Index);
363 if (buf.len < 2) {
364 return;
365 }
366
367 // `i <= j < max <= maxInt(MinInt)`
368 const max: MinInt = @intCast(buf.len);
369 var i: MinInt = 0;
370 while (i < max - 1) : (i += 1) {
371 const j: MinInt = @intCast(r.intRangeLessThan(Index, i, max));
372 mem.swap(T, &buf[i], &buf[j]);
373 }
374 }
375
376 /// Randomly selects an index into `proportions`, where the likelihood of each
377 /// index is weighted by that proportion.
378 /// It is more likely for the index of the last proportion to be returned
379 /// than the index of the first proportion in the slice, and vice versa.
380 ///
381 /// This is useful for selecting an item from a slice where weights are not equal.
382 /// `T` must be a numeric type capable of holding the sum of `proportions`.
383 pub fn weightedIndex(r: std.rand.Random, comptime T: type, proportions: []const T) usize {
384 // This implementation works by summing the proportions and picking a
385 // random point in [0, sum). We then loop over the proportions,
386 // accumulating until our accumulator is greater than the random point.
387
388 const sum = s: {
389 var sum: T = 0;
390 for (proportions) |v| sum += v;
391 break :s sum;
392 };
393
394 const point = switch (@typeInfo(T)) {
395 .Int => |int_info| switch (int_info.signedness) {
396 .signed => r.intRangeLessThan(T, 0, sum),
397 .unsigned => r.uintLessThan(T, sum),
398 },
399 // take care that imprecision doesn't lead to a value slightly greater than sum
400 .Float => @min(r.float(T) * sum, sum - std.math.floatEps(T)),
401 else => @compileError("weightedIndex does not support proportions of type " ++
402 @typeName(T)),
403 };
404
405 assert(point < sum);
406
407 var accumulator: T = 0;
408 for (proportions, 0..) |p, index| {
409 accumulator += p;
410 if (point < accumulator) return index;
411 } else unreachable;
412 }
413
414 /// Returns the smallest of `Index` and `usize`.
415 fn MinArrayIndex(comptime Index: type) type {
416 const index_info = @typeInfo(Index).Int;
417 assert(index_info.signedness == .unsigned);
418 return if (index_info.bits >= @typeInfo(usize).Int.bits) usize else Index;
419 }
420};
421
422/// Convert a random integer 0 <= random_int <= maxValue(T),
423/// into an integer 0 <= result < less_than.
424/// This function introduces a minor bias.
425pub fn limitRangeBiased(comptime T: type, random_int: T, less_than: T) T {
426 comptime assert(@typeInfo(T).Int.signedness == .unsigned);
427 const bits = @typeInfo(T).Int.bits;
428
429 // adapted from:
430 // http://www.pcg-random.org/posts/bounded-rands.html
431 // "Integer Multiplication (Biased)"
432 const m = math.mulWide(T, random_int, less_than);
433 return @intCast(m >> bits);
434}
435
436// Generator to extend 64-bit seed values into longer sequences.
437//
438// The number of cycles is thus limited to 64-bits regardless of the engine, but this
439// is still plenty for practical purposes.
440pub const SplitMix64 = struct {
441 s: u64,
442
443 pub fn init(seed: u64) SplitMix64 {
444 return SplitMix64{ .s = seed };
445 }
446
447 pub fn next(self: *SplitMix64) u64 {
448 self.s +%= 0x9e3779b97f4a7c15;
449
450 var z = self.s;
451 z = (z ^ (z >> 30)) *% 0xbf58476d1ce4e5b9;
452 z = (z ^ (z >> 27)) *% 0x94d049bb133111eb;
453 return z ^ (z >> 31);
454 }
455};
456
457test {
458 std.testing.refAllDecls(@This());
459 _ = @import("rand/test.zig");
460}
lib/std/rand/Ascon.zig deleted-59
......@@ -1,59 +0,0 @@
1//! CSPRNG based on the Reverie construction, a permutation-based PRNG
2//! with forward security, instantiated with the Ascon(128,12,8) permutation.
3//!
4//! Compared to ChaCha, this PRNG has a much smaller state, and can be
5//! a better choice for constrained environments.
6//!
7//! References:
8//! - A Robust and Sponge-Like PRNG with Improved Efficiency https://eprint.iacr.org/2016/886.pdf
9//! - Ascon https://ascon.iaik.tugraz.at/files/asconv12-nist.pdf
10
11const std = @import("std");
12const mem = std.mem;
13const Random = std.rand.Random;
14const Self = @This();
15
16const Ascon = std.crypto.core.Ascon(.little);
17
18state: Ascon,
19
20const rate = 16;
21pub const secret_seed_length = 32;
22
23/// The seed must be uniform, secret and `secret_seed_length` bytes long.
24pub fn init(secret_seed: [secret_seed_length]u8) Self {
25 var self = Self{ .state = Ascon.initXof() };
26 self.addEntropy(&secret_seed);
27 return self;
28}
29
30/// Inserts entropy to refresh the internal state.
31pub fn addEntropy(self: *Self, bytes: []const u8) void {
32 comptime std.debug.assert(secret_seed_length % rate == 0);
33 var i: usize = 0;
34 while (i + rate < bytes.len) : (i += rate) {
35 self.state.addBytes(bytes[i..][0..rate]);
36 self.state.permuteR(8);
37 }
38 if (i != bytes.len) self.state.addBytes(bytes[i..]);
39 self.state.permute();
40}
41
42/// Returns a `std.rand.Random` structure backed by the current RNG.
43pub fn random(self: *Self) Random {
44 return Random.init(self, fill);
45}
46
47/// Fills the buffer with random bytes.
48pub fn fill(self: *Self, buf: []u8) void {
49 var i: usize = 0;
50 while (true) {
51 const left = buf.len - i;
52 const n = @min(left, rate);
53 self.state.extractBytes(buf[i..][0..n]);
54 if (left == 0) break;
55 self.state.permuteR(8);
56 i += n;
57 }
58 self.state.permuteRatchet(6, rate);
59}
lib/std/rand/ChaCha.zig deleted-98
......@@ -1,98 +0,0 @@
1//! CSPRNG based on the ChaCha8 stream cipher, with forward security.
2//!
3//! References:
4//! - Fast-key-erasure random-number generators https://blog.cr.yp.to/20170723-random.html
5
6const std = @import("std");
7const mem = std.mem;
8const Random = std.rand.Random;
9const Self = @This();
10
11const Cipher = std.crypto.stream.chacha.ChaCha8IETF;
12
13const State = [8 * Cipher.block_length]u8;
14
15state: State,
16offset: usize,
17
18const nonce = [_]u8{0} ** Cipher.nonce_length;
19
20pub const secret_seed_length = Cipher.key_length;
21
22/// The seed must be uniform, secret and `secret_seed_length` bytes long.
23pub fn init(secret_seed: [secret_seed_length]u8) Self {
24 var self = Self{ .state = undefined, .offset = 0 };
25 Cipher.stream(&self.state, 0, secret_seed, nonce);
26 return self;
27}
28
29/// Inserts entropy to refresh the internal state.
30pub fn addEntropy(self: *Self, bytes: []const u8) void {
31 var i: usize = 0;
32 while (i + Cipher.key_length <= bytes.len) : (i += Cipher.key_length) {
33 Cipher.xor(
34 self.state[0..Cipher.key_length],
35 self.state[0..Cipher.key_length],
36 0,
37 bytes[i..][0..Cipher.key_length].*,
38 nonce,
39 );
40 }
41 if (i < bytes.len) {
42 var k = [_]u8{0} ** Cipher.key_length;
43 const src = bytes[i..];
44 @memcpy(k[0..src.len], src);
45 Cipher.xor(
46 self.state[0..Cipher.key_length],
47 self.state[0..Cipher.key_length],
48 0,
49 k,
50 nonce,
51 );
52 }
53 self.refill();
54}
55
56/// Returns a `std.rand.Random` structure backed by the current RNG.
57pub fn random(self: *Self) Random {
58 return Random.init(self, fill);
59}
60
61// Refills the buffer with random bytes, overwriting the previous key.
62fn refill(self: *Self) void {
63 Cipher.stream(&self.state, 0, self.state[0..Cipher.key_length].*, nonce);
64 self.offset = 0;
65}
66
67/// Fills the buffer with random bytes.
68pub fn fill(self: *Self, buf_: []u8) void {
69 const bytes = self.state[Cipher.key_length..];
70 var buf = buf_;
71
72 const avail = bytes.len - self.offset;
73 if (avail > 0) {
74 // Bytes from the current block
75 const n = @min(avail, buf.len);
76 @memcpy(buf[0..n], bytes[self.offset..][0..n]);
77 @memset(bytes[self.offset..][0..n], 0);
78 buf = buf[n..];
79 self.offset += n;
80 }
81 if (buf.len == 0) return;
82
83 self.refill();
84
85 // Full blocks
86 while (buf.len >= bytes.len) {
87 @memcpy(buf[0..bytes.len], bytes);
88 buf = buf[bytes.len..];
89 self.refill();
90 }
91
92 // Remaining bytes
93 if (buf.len > 0) {
94 @memcpy(buf, bytes[0..buf.len]);
95 @memset(bytes[0..buf.len], 0);
96 self.offset = buf.len;
97 }
98}
lib/std/rand/Isaac64.zig deleted-235
......@@ -1,235 +0,0 @@
1//! ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html
2//!
3//! Follows the general idea of the implementation from here with a few shortcuts.
4//! https://doc.rust-lang.org/rand/src/rand/prng/isaac64.rs.html
5
6const std = @import("std");
7const Random = std.rand.Random;
8const mem = std.mem;
9const Isaac64 = @This();
10
11r: [256]u64,
12m: [256]u64,
13a: u64,
14b: u64,
15c: u64,
16i: usize,
17
18pub fn init(init_s: u64) Isaac64 {
19 var isaac = Isaac64{
20 .r = undefined,
21 .m = undefined,
22 .a = undefined,
23 .b = undefined,
24 .c = undefined,
25 .i = undefined,
26 };
27
28 // seed == 0 => same result as the unseeded reference implementation
29 isaac.seed(init_s, 1);
30 return isaac;
31}
32
33pub fn random(self: *Isaac64) Random {
34 return Random.init(self, fill);
35}
36
37fn step(self: *Isaac64, mix: u64, base: usize, comptime m1: usize, comptime m2: usize) void {
38 const x = self.m[base + m1];
39 self.a = mix +% self.m[base + m2];
40
41 const y = self.a +% self.b +% self.m[@as(usize, @intCast((x >> 3) % self.m.len))];
42 self.m[base + m1] = y;
43
44 self.b = x +% self.m[@as(usize, @intCast((y >> 11) % self.m.len))];
45 self.r[self.r.len - 1 - base - m1] = self.b;
46}
47
48fn refill(self: *Isaac64) void {
49 const midpoint = self.r.len / 2;
50
51 self.c +%= 1;
52 self.b +%= self.c;
53
54 {
55 var i: usize = 0;
56 while (i < midpoint) : (i += 4) {
57 self.step(~(self.a ^ (self.a << 21)), i + 0, 0, midpoint);
58 self.step(self.a ^ (self.a >> 5), i + 1, 0, midpoint);
59 self.step(self.a ^ (self.a << 12), i + 2, 0, midpoint);
60 self.step(self.a ^ (self.a >> 33), i + 3, 0, midpoint);
61 }
62 }
63
64 {
65 var i: usize = 0;
66 while (i < midpoint) : (i += 4) {
67 self.step(~(self.a ^ (self.a << 21)), i + 0, midpoint, 0);
68 self.step(self.a ^ (self.a >> 5), i + 1, midpoint, 0);
69 self.step(self.a ^ (self.a << 12), i + 2, midpoint, 0);
70 self.step(self.a ^ (self.a >> 33), i + 3, midpoint, 0);
71 }
72 }
73
74 self.i = 0;
75}
76
77fn next(self: *Isaac64) u64 {
78 if (self.i >= self.r.len) {
79 self.refill();
80 }
81
82 const value = self.r[self.i];
83 self.i += 1;
84 return value;
85}
86
87fn seed(self: *Isaac64, init_s: u64, comptime rounds: usize) void {
88 // We ignore the multi-pass requirement since we don't currently expose full access to
89 // seeding the self.m array completely.
90 @memset(self.m[0..], 0);
91 self.m[0] = init_s;
92
93 // prescrambled golden ratio constants
94 var a = [_]u64{
95 0x647c4677a2884b7c,
96 0xb9f8b322c73ac862,
97 0x8c0ea5053d4712a0,
98 0xb29b2e824a595524,
99 0x82f053db8355e0ce,
100 0x48fe4a0fa5a09315,
101 0xae985bf2cbfc89ed,
102 0x98f5704f6c44c0ab,
103 };
104
105 comptime var i: usize = 0;
106 inline while (i < rounds) : (i += 1) {
107 var j: usize = 0;
108 while (j < self.m.len) : (j += 8) {
109 comptime var x1: usize = 0;
110 inline while (x1 < 8) : (x1 += 1) {
111 a[x1] +%= self.m[j + x1];
112 }
113
114 a[0] -%= a[4];
115 a[5] ^= a[7] >> 9;
116 a[7] +%= a[0];
117 a[1] -%= a[5];
118 a[6] ^= a[0] << 9;
119 a[0] +%= a[1];
120 a[2] -%= a[6];
121 a[7] ^= a[1] >> 23;
122 a[1] +%= a[2];
123 a[3] -%= a[7];
124 a[0] ^= a[2] << 15;
125 a[2] +%= a[3];
126 a[4] -%= a[0];
127 a[1] ^= a[3] >> 14;
128 a[3] +%= a[4];
129 a[5] -%= a[1];
130 a[2] ^= a[4] << 20;
131 a[4] +%= a[5];
132 a[6] -%= a[2];
133 a[3] ^= a[5] >> 17;
134 a[5] +%= a[6];
135 a[7] -%= a[3];
136 a[4] ^= a[6] << 14;
137 a[6] +%= a[7];
138
139 comptime var x2: usize = 0;
140 inline while (x2 < 8) : (x2 += 1) {
141 self.m[j + x2] = a[x2];
142 }
143 }
144 }
145
146 @memset(self.r[0..], 0);
147 self.a = 0;
148 self.b = 0;
149 self.c = 0;
150 self.i = self.r.len; // trigger refill on first value
151}
152
153pub fn fill(self: *Isaac64, buf: []u8) void {
154 var i: usize = 0;
155 const aligned_len = buf.len - (buf.len & 7);
156
157 // Fill complete 64-byte segments
158 while (i < aligned_len) : (i += 8) {
159 var n = self.next();
160 comptime var j: usize = 0;
161 inline while (j < 8) : (j += 1) {
162 buf[i + j] = @as(u8, @truncate(n));
163 n >>= 8;
164 }
165 }
166
167 // Fill trailing, ignoring excess (cut the stream).
168 if (i != buf.len) {
169 var n = self.next();
170 while (i < buf.len) : (i += 1) {
171 buf[i] = @as(u8, @truncate(n));
172 n >>= 8;
173 }
174 }
175}
176
177test "isaac64 sequence" {
178 var r = Isaac64.init(0);
179
180 // from reference implementation
181 const seq = [_]u64{
182 0xf67dfba498e4937c,
183 0x84a5066a9204f380,
184 0xfee34bd5f5514dbb,
185 0x4d1664739b8f80d6,
186 0x8607459ab52a14aa,
187 0x0e78bc5a98529e49,
188 0xfe5332822ad13777,
189 0x556c27525e33d01a,
190 0x08643ca615f3149f,
191 0xd0771faf3cb04714,
192 0x30e86f68a37b008d,
193 0x3074ebc0488a3adf,
194 0x270645ea7a2790bc,
195 0x5601a0a8d3763c6a,
196 0x2f83071f53f325dd,
197 0xb9090f3d42d2d2ea,
198 };
199
200 for (seq) |s| {
201 try std.testing.expect(s == r.next());
202 }
203}
204
205test "isaac64 fill" {
206 var r = Isaac64.init(0);
207
208 // from reference implementation
209 const seq = [_]u64{
210 0xf67dfba498e4937c,
211 0x84a5066a9204f380,
212 0xfee34bd5f5514dbb,
213 0x4d1664739b8f80d6,
214 0x8607459ab52a14aa,
215 0x0e78bc5a98529e49,
216 0xfe5332822ad13777,
217 0x556c27525e33d01a,
218 0x08643ca615f3149f,
219 0xd0771faf3cb04714,
220 0x30e86f68a37b008d,
221 0x3074ebc0488a3adf,
222 0x270645ea7a2790bc,
223 0x5601a0a8d3763c6a,
224 0x2f83071f53f325dd,
225 0xb9090f3d42d2d2ea,
226 };
227
228 for (seq) |s| {
229 var buf0: [8]u8 = undefined;
230 var buf1: [7]u8 = undefined;
231 std.mem.writeInt(u64, &buf0, s, .little);
232 r.fill(&buf1);
233 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
234 }
235}
lib/std/rand/Pcg.zig deleted-122
......@@ -1,122 +0,0 @@
1//! PCG32 - http://www.pcg-random.org/
2//!
3//! PRNG
4
5const std = @import("std");
6const Random = std.rand.Random;
7const Pcg = @This();
8
9const default_multiplier = 6364136223846793005;
10
11s: u64,
12i: u64,
13
14pub fn init(init_s: u64) Pcg {
15 var pcg = Pcg{
16 .s = undefined,
17 .i = undefined,
18 };
19
20 pcg.seed(init_s);
21 return pcg;
22}
23
24pub fn random(self: *Pcg) Random {
25 return Random.init(self, fill);
26}
27
28fn next(self: *Pcg) u32 {
29 const l = self.s;
30 self.s = l *% default_multiplier +% (self.i | 1);
31
32 const xor_s: u32 = @truncate(((l >> 18) ^ l) >> 27);
33 const rot: u32 = @intCast(l >> 59);
34
35 return (xor_s >> @as(u5, @intCast(rot))) | (xor_s << @as(u5, @intCast((0 -% rot) & 31)));
36}
37
38fn seed(self: *Pcg, init_s: u64) void {
39 // Pcg requires 128-bits of seed.
40 var gen = std.rand.SplitMix64.init(init_s);
41 self.seedTwo(gen.next(), gen.next());
42}
43
44fn seedTwo(self: *Pcg, init_s: u64, init_i: u64) void {
45 self.s = 0;
46 self.i = (init_s << 1) | 1;
47 self.s = self.s *% default_multiplier +% self.i;
48 self.s +%= init_i;
49 self.s = self.s *% default_multiplier +% self.i;
50}
51
52pub fn fill(self: *Pcg, buf: []u8) void {
53 var i: usize = 0;
54 const aligned_len = buf.len - (buf.len & 3);
55
56 // Complete 4 byte segments.
57 while (i < aligned_len) : (i += 4) {
58 var n = self.next();
59 comptime var j: usize = 0;
60 inline while (j < 4) : (j += 1) {
61 buf[i + j] = @as(u8, @truncate(n));
62 n >>= 8;
63 }
64 }
65
66 // Remaining. (cuts the stream)
67 if (i != buf.len) {
68 var n = self.next();
69 while (i < buf.len) : (i += 1) {
70 buf[i] = @as(u8, @truncate(n));
71 n >>= 8;
72 }
73 }
74}
75
76test "pcg sequence" {
77 var r = Pcg.init(0);
78 const s0: u64 = 0x9394bf54ce5d79de;
79 const s1: u64 = 0x84e9c579ef59bbf7;
80 r.seedTwo(s0, s1);
81
82 const seq = [_]u32{
83 2881561918,
84 3063928540,
85 1199791034,
86 2487695858,
87 1479648952,
88 3247963454,
89 };
90
91 for (seq) |s| {
92 try std.testing.expect(s == r.next());
93 }
94}
95
96test "pcg fill" {
97 var r = Pcg.init(0);
98 const s0: u64 = 0x9394bf54ce5d79de;
99 const s1: u64 = 0x84e9c579ef59bbf7;
100 r.seedTwo(s0, s1);
101
102 const seq = [_]u32{
103 2881561918,
104 3063928540,
105 1199791034,
106 2487695858,
107 1479648952,
108 3247963454,
109 };
110
111 var i: u32 = 0;
112 while (i < seq.len) : (i += 2) {
113 var buf0: [8]u8 = undefined;
114 std.mem.writeInt(u32, buf0[0..4], seq[i], .little);
115 std.mem.writeInt(u32, buf0[4..8], seq[i + 1], .little);
116
117 var buf1: [7]u8 = undefined;
118 r.fill(&buf1);
119
120 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
121 }
122}
lib/std/rand/RomuTrio.zig deleted-132
......@@ -1,132 +0,0 @@
1// Website: romu-random.org
2// Reference paper: http://arxiv.org/abs/2002.11331
3// Beware: this PRNG is trivially predictable. While fast, it should *never* be used for cryptographic purposes.
4
5const std = @import("std");
6const Random = std.rand.Random;
7const math = std.math;
8const RomuTrio = @This();
9
10x_state: u64,
11y_state: u64,
12z_state: u64, // set to nonzero seed
13
14pub fn init(init_s: u64) RomuTrio {
15 var x = RomuTrio{ .x_state = undefined, .y_state = undefined, .z_state = undefined };
16 x.seed(init_s);
17 return x;
18}
19
20pub fn random(self: *RomuTrio) Random {
21 return Random.init(self, fill);
22}
23
24fn next(self: *RomuTrio) u64 {
25 const xp = self.x_state;
26 const yp = self.y_state;
27 const zp = self.z_state;
28 self.x_state = 15241094284759029579 *% zp;
29 self.y_state = yp -% xp;
30 self.y_state = std.math.rotl(u64, self.y_state, 12);
31 self.z_state = zp -% yp;
32 self.z_state = std.math.rotl(u64, self.z_state, 44);
33 return xp;
34}
35
36pub fn seedWithBuf(self: *RomuTrio, buf: [24]u8) void {
37 const seed_buf = @as([3]u64, @bitCast(buf));
38 self.x_state = seed_buf[0];
39 self.y_state = seed_buf[1];
40 self.z_state = seed_buf[2];
41}
42
43pub fn seed(self: *RomuTrio, init_s: u64) void {
44 // RomuTrio requires 192-bits of seed.
45 var gen = std.rand.SplitMix64.init(init_s);
46
47 self.x_state = gen.next();
48 self.y_state = gen.next();
49 self.z_state = gen.next();
50}
51
52pub fn fill(self: *RomuTrio, buf: []u8) void {
53 var i: usize = 0;
54 const aligned_len = buf.len - (buf.len & 7);
55
56 // Complete 8 byte segments.
57 while (i < aligned_len) : (i += 8) {
58 var n = self.next();
59 comptime var j: usize = 0;
60 inline while (j < 8) : (j += 1) {
61 buf[i + j] = @as(u8, @truncate(n));
62 n >>= 8;
63 }
64 }
65
66 // Remaining. (cuts the stream)
67 if (i != buf.len) {
68 var n = self.next();
69 while (i < buf.len) : (i += 1) {
70 buf[i] = @as(u8, @truncate(n));
71 n >>= 8;
72 }
73 }
74}
75
76test "RomuTrio sequence" {
77 // Unfortunately there does not seem to be an official test sequence.
78 var r = RomuTrio.init(0);
79
80 const seq = [_]u64{
81 16294208416658607535,
82 13964609475759908645,
83 4703697494102998476,
84 3425221541186733346,
85 2285772463536419399,
86 9454187757529463048,
87 13695907680080547496,
88 8328236714879408626,
89 12323357569716880909,
90 12375466223337721820,
91 };
92
93 for (seq) |s| {
94 try std.testing.expectEqual(s, r.next());
95 }
96}
97
98test "RomuTrio fill" {
99 // Unfortunately there does not seem to be an official test sequence.
100 var r = RomuTrio.init(0);
101
102 const seq = [_]u64{
103 16294208416658607535,
104 13964609475759908645,
105 4703697494102998476,
106 3425221541186733346,
107 2285772463536419399,
108 9454187757529463048,
109 13695907680080547496,
110 8328236714879408626,
111 12323357569716880909,
112 12375466223337721820,
113 };
114
115 for (seq) |s| {
116 var buf0: [8]u8 = undefined;
117 var buf1: [7]u8 = undefined;
118 std.mem.writeInt(u64, &buf0, s, .little);
119 r.fill(&buf1);
120 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
121 }
122}
123
124test "RomuTrio buf seeding test" {
125 const buf0 = @as([24]u8, @bitCast([3]u64{ 16294208416658607535, 13964609475759908645, 4703697494102998476 }));
126 const resulting_state = .{ .x = 16294208416658607535, .y = 13964609475759908645, .z = 4703697494102998476 };
127 var r = RomuTrio.init(0);
128 r.seedWithBuf(buf0);
129 try std.testing.expect(r.x_state == resulting_state.x);
130 try std.testing.expect(r.y_state == resulting_state.y);
131 try std.testing.expect(r.z_state == resulting_state.z);
132}
lib/std/rand/Sfc64.zig deleted-132
......@@ -1,132 +0,0 @@
1//! Sfc64 pseudo-random number generator from Practically Random.
2//! Fastest engine of pracrand and smallest footprint.
3//! See http://pracrand.sourceforge.net/
4
5const std = @import("std");
6const Random = std.rand.Random;
7const math = std.math;
8const Sfc64 = @This();
9
10a: u64 = undefined,
11b: u64 = undefined,
12c: u64 = undefined,
13counter: u64 = undefined,
14
15const Rotation = 24;
16const RightShift = 11;
17const LeftShift = 3;
18
19pub fn init(init_s: u64) Sfc64 {
20 var x = Sfc64{};
21
22 x.seed(init_s);
23 return x;
24}
25
26pub fn random(self: *Sfc64) Random {
27 return Random.init(self, fill);
28}
29
30fn next(self: *Sfc64) u64 {
31 const tmp = self.a +% self.b +% self.counter;
32 self.counter += 1;
33 self.a = self.b ^ (self.b >> RightShift);
34 self.b = self.c +% (self.c << LeftShift);
35 self.c = math.rotl(u64, self.c, Rotation) +% tmp;
36 return tmp;
37}
38
39fn seed(self: *Sfc64, init_s: u64) void {
40 self.a = init_s;
41 self.b = init_s;
42 self.c = init_s;
43 self.counter = 1;
44 var i: u32 = 0;
45 while (i < 12) : (i += 1) {
46 _ = self.next();
47 }
48}
49
50pub fn fill(self: *Sfc64, buf: []u8) void {
51 var i: usize = 0;
52 const aligned_len = buf.len - (buf.len & 7);
53
54 // Complete 8 byte segments.
55 while (i < aligned_len) : (i += 8) {
56 var n = self.next();
57 comptime var j: usize = 0;
58 inline while (j < 8) : (j += 1) {
59 buf[i + j] = @as(u8, @truncate(n));
60 n >>= 8;
61 }
62 }
63
64 // Remaining. (cuts the stream)
65 if (i != buf.len) {
66 var n = self.next();
67 while (i < buf.len) : (i += 1) {
68 buf[i] = @as(u8, @truncate(n));
69 n >>= 8;
70 }
71 }
72}
73
74test "Sfc64 sequence" {
75 // Unfortunately there does not seem to be an official test sequence.
76 var r = Sfc64.init(0);
77
78 const seq = [_]u64{
79 0x3acfa029e3cc6041,
80 0xf5b6515bf2ee419c,
81 0x1259635894a29b61,
82 0xb6ae75395f8ebd6,
83 0x225622285ce302e2,
84 0x520d28611395cb21,
85 0xdb909c818901599d,
86 0x8ffd195365216f57,
87 0xe8c4ad5e258ac04a,
88 0x8f8ef2c89fdb63ca,
89 0xf9865b01d98d8e2f,
90 0x46555871a65d08ba,
91 0x66868677c6298fcd,
92 0x2ce15a7e6329f57d,
93 0xb2f1833ca91ca79,
94 0x4b0890ac9bf453ca,
95 };
96
97 for (seq) |s| {
98 try std.testing.expectEqual(s, r.next());
99 }
100}
101
102test "Sfc64 fill" {
103 // Unfortunately there does not seem to be an official test sequence.
104 var r = Sfc64.init(0);
105
106 const seq = [_]u64{
107 0x3acfa029e3cc6041,
108 0xf5b6515bf2ee419c,
109 0x1259635894a29b61,
110 0xb6ae75395f8ebd6,
111 0x225622285ce302e2,
112 0x520d28611395cb21,
113 0xdb909c818901599d,
114 0x8ffd195365216f57,
115 0xe8c4ad5e258ac04a,
116 0x8f8ef2c89fdb63ca,
117 0xf9865b01d98d8e2f,
118 0x46555871a65d08ba,
119 0x66868677c6298fcd,
120 0x2ce15a7e6329f57d,
121 0xb2f1833ca91ca79,
122 0x4b0890ac9bf453ca,
123 };
124
125 for (seq) |s| {
126 var buf0: [8]u8 = undefined;
127 var buf1: [7]u8 = undefined;
128 std.mem.writeInt(u64, &buf0, s, .little);
129 r.fill(&buf1);
130 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
131 }
132}
lib/std/rand/Xoroshiro128.zig deleted-147
......@@ -1,147 +0,0 @@
1//! Xoroshiro128+ - http://xoroshiro.di.unimi.it/
2//!
3//! PRNG
4
5const std = @import("std");
6const Random = std.rand.Random;
7const math = std.math;
8const Xoroshiro128 = @This();
9
10s: [2]u64,
11
12pub fn init(init_s: u64) Xoroshiro128 {
13 var x = Xoroshiro128{ .s = undefined };
14
15 x.seed(init_s);
16 return x;
17}
18
19pub fn random(self: *Xoroshiro128) Random {
20 return Random.init(self, fill);
21}
22
23pub fn next(self: *Xoroshiro128) u64 {
24 const s0 = self.s[0];
25 var s1 = self.s[1];
26 const r = s0 +% s1;
27
28 s1 ^= s0;
29 self.s[0] = math.rotl(u64, s0, @as(u8, 55)) ^ s1 ^ (s1 << 14);
30 self.s[1] = math.rotl(u64, s1, @as(u8, 36));
31
32 return r;
33}
34
35// Skip 2^64 places ahead in the sequence
36pub fn jump(self: *Xoroshiro128) void {
37 var s0: u64 = 0;
38 var s1: u64 = 0;
39
40 const table = [_]u64{
41 0xbeac0467eba5facb,
42 0xd86b048b86aa9922,
43 };
44
45 inline for (table) |entry| {
46 var b: usize = 0;
47 while (b < 64) : (b += 1) {
48 if ((entry & (@as(u64, 1) << @as(u6, @intCast(b)))) != 0) {
49 s0 ^= self.s[0];
50 s1 ^= self.s[1];
51 }
52 _ = self.next();
53 }
54 }
55
56 self.s[0] = s0;
57 self.s[1] = s1;
58}
59
60pub fn seed(self: *Xoroshiro128, init_s: u64) void {
61 // Xoroshiro requires 128-bits of seed.
62 var gen = std.rand.SplitMix64.init(init_s);
63
64 self.s[0] = gen.next();
65 self.s[1] = gen.next();
66}
67
68pub fn fill(self: *Xoroshiro128, buf: []u8) void {
69 var i: usize = 0;
70 const aligned_len = buf.len - (buf.len & 7);
71
72 // Complete 8 byte segments.
73 while (i < aligned_len) : (i += 8) {
74 var n = self.next();
75 comptime var j: usize = 0;
76 inline while (j < 8) : (j += 1) {
77 buf[i + j] = @as(u8, @truncate(n));
78 n >>= 8;
79 }
80 }
81
82 // Remaining. (cuts the stream)
83 if (i != buf.len) {
84 var n = self.next();
85 while (i < buf.len) : (i += 1) {
86 buf[i] = @as(u8, @truncate(n));
87 n >>= 8;
88 }
89 }
90}
91
92test "xoroshiro sequence" {
93 var r = Xoroshiro128.init(0);
94 r.s[0] = 0xaeecf86f7878dd75;
95 r.s[1] = 0x01cd153642e72622;
96
97 const seq1 = [_]u64{
98 0xb0ba0da5bb600397,
99 0x18a08afde614dccc,
100 0xa2635b956a31b929,
101 0xabe633c971efa045,
102 0x9ac19f9706ca3cac,
103 0xf62b426578c1e3fb,
104 };
105
106 for (seq1) |s| {
107 try std.testing.expect(s == r.next());
108 }
109
110 r.jump();
111
112 const seq2 = [_]u64{
113 0x95344a13556d3e22,
114 0xb4fb32dafa4d00df,
115 0xb2011d9ccdcfe2dd,
116 0x05679a9b2119b908,
117 0xa860a1da7c9cd8a0,
118 0x658a96efe3f86550,
119 };
120
121 for (seq2) |s| {
122 try std.testing.expect(s == r.next());
123 }
124}
125
126test "xoroshiro fill" {
127 var r = Xoroshiro128.init(0);
128 r.s[0] = 0xaeecf86f7878dd75;
129 r.s[1] = 0x01cd153642e72622;
130
131 const seq = [_]u64{
132 0xb0ba0da5bb600397,
133 0x18a08afde614dccc,
134 0xa2635b956a31b929,
135 0xabe633c971efa045,
136 0x9ac19f9706ca3cac,
137 0xf62b426578c1e3fb,
138 };
139
140 for (seq) |s| {
141 var buf0: [8]u8 = undefined;
142 var buf1: [7]u8 = undefined;
143 std.mem.writeInt(u64, &buf0, s, .little);
144 r.fill(&buf1);
145 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
146 }
147}
lib/std/rand/Xoshiro256.zig deleted-146
......@@ -1,146 +0,0 @@
1//! Xoshiro256++ - http://xoroshiro.di.unimi.it/
2//!
3//! PRNG
4
5const std = @import("std");
6const Random = std.rand.Random;
7const math = std.math;
8const Xoshiro256 = @This();
9
10s: [4]u64,
11
12pub fn init(init_s: u64) Xoshiro256 {
13 var x = Xoshiro256{
14 .s = undefined,
15 };
16
17 x.seed(init_s);
18 return x;
19}
20
21pub fn random(self: *Xoshiro256) Random {
22 return Random.init(self, fill);
23}
24
25pub fn next(self: *Xoshiro256) u64 {
26 const r = math.rotl(u64, self.s[0] +% self.s[3], 23) +% self.s[0];
27
28 const t = self.s[1] << 17;
29
30 self.s[2] ^= self.s[0];
31 self.s[3] ^= self.s[1];
32 self.s[1] ^= self.s[2];
33 self.s[0] ^= self.s[3];
34
35 self.s[2] ^= t;
36
37 self.s[3] = math.rotl(u64, self.s[3], 45);
38
39 return r;
40}
41
42// Skip 2^128 places ahead in the sequence
43pub fn jump(self: *Xoshiro256) void {
44 var s: u256 = 0;
45
46 var table: u256 = 0x39abdc4529b1661ca9582618e03fc9aad5a61266f0c9392c180ec6d33cfd0aba;
47
48 while (table != 0) : (table >>= 1) {
49 if (@as(u1, @truncate(table)) != 0) {
50 s ^= @as(u256, @bitCast(self.s));
51 }
52 _ = self.next();
53 }
54
55 self.s = @as([4]u64, @bitCast(s));
56}
57
58pub fn seed(self: *Xoshiro256, init_s: u64) void {
59 // Xoshiro requires 256-bits of seed.
60 var gen = std.rand.SplitMix64.init(init_s);
61
62 self.s[0] = gen.next();
63 self.s[1] = gen.next();
64 self.s[2] = gen.next();
65 self.s[3] = gen.next();
66}
67
68pub fn fill(self: *Xoshiro256, buf: []u8) void {
69 var i: usize = 0;
70 const aligned_len = buf.len - (buf.len & 7);
71
72 // Complete 8 byte segments.
73 while (i < aligned_len) : (i += 8) {
74 var n = self.next();
75 comptime var j: usize = 0;
76 inline while (j < 8) : (j += 1) {
77 buf[i + j] = @as(u8, @truncate(n));
78 n >>= 8;
79 }
80 }
81
82 // Remaining. (cuts the stream)
83 if (i != buf.len) {
84 var n = self.next();
85 while (i < buf.len) : (i += 1) {
86 buf[i] = @as(u8, @truncate(n));
87 n >>= 8;
88 }
89 }
90}
91
92test "xoroshiro sequence" {
93 if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest;
94 if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest;
95
96 var r = Xoshiro256.init(0);
97
98 const seq1 = [_]u64{
99 0x53175d61490b23df,
100 0x61da6f3dc380d507,
101 0x5c0fdf91ec9a7bfc,
102 0x02eebf8c3bbe5e1a,
103 0x7eca04ebaf4a5eea,
104 0x0543c37757f08d9a,
105 };
106
107 for (seq1) |s| {
108 try std.testing.expect(s == r.next());
109 }
110
111 r.jump();
112
113 const seq2 = [_]u64{
114 0xae1db5c5e27807be,
115 0xb584c6a7fd8709fe,
116 0xc46a0ee9330fb6e,
117 0xdc0c9606f49ed76e,
118 0x1f5bb6540f6651fb,
119 0x72fa2ca734601488,
120 };
121
122 for (seq2) |s| {
123 try std.testing.expect(s == r.next());
124 }
125}
126
127test "xoroshiro fill" {
128 var r = Xoshiro256.init(0);
129
130 const seq = [_]u64{
131 0x53175d61490b23df,
132 0x61da6f3dc380d507,
133 0x5c0fdf91ec9a7bfc,
134 0x02eebf8c3bbe5e1a,
135 0x7eca04ebaf4a5eea,
136 0x0543c37757f08d9a,
137 };
138
139 for (seq) |s| {
140 var buf0: [8]u8 = undefined;
141 var buf1: [7]u8 = undefined;
142 std.mem.writeInt(u64, &buf0, s, .little);
143 r.fill(&buf1);
144 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
145 }
146}
lib/std/rand/benchmark.zig deleted-217
......@@ -1,217 +0,0 @@
1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
2
3const std = @import("std");
4const builtin = @import("builtin");
5const time = std.time;
6const Timer = time.Timer;
7const rand = std.rand;
8
9const KiB = 1024;
10const MiB = 1024 * KiB;
11const GiB = 1024 * MiB;
12
13const Rng = struct {
14 ty: type,
15 name: []const u8,
16 init_u8s: ?[]const u8 = null,
17 init_u64: ?u64 = null,
18};
19
20const prngs = [_]Rng{
21 Rng{
22 .ty = rand.Isaac64,
23 .name = "isaac64",
24 .init_u64 = 0,
25 },
26 Rng{
27 .ty = rand.Pcg,
28 .name = "pcg",
29 .init_u64 = 0,
30 },
31 Rng{
32 .ty = rand.RomuTrio,
33 .name = "romutrio",
34 .init_u64 = 0,
35 },
36 Rng{
37 .ty = std.rand.Sfc64,
38 .name = "sfc64",
39 .init_u64 = 0,
40 },
41 Rng{
42 .ty = std.rand.Xoroshiro128,
43 .name = "xoroshiro128",
44 .init_u64 = 0,
45 },
46 Rng{
47 .ty = std.rand.Xoshiro256,
48 .name = "xoshiro256",
49 .init_u64 = 0,
50 },
51};
52
53const csprngs = [_]Rng{
54 Rng{
55 .ty = rand.Ascon,
56 .name = "ascon",
57 .init_u8s = &[_]u8{0} ** 32,
58 },
59 Rng{
60 .ty = rand.ChaCha,
61 .name = "chacha",
62 .init_u8s = &[_]u8{0} ** 32,
63 },
64};
65
66const Result = struct {
67 throughput: u64,
68};
69
70const long_block_size: usize = 8 * 8192;
71const short_block_size: usize = 8;
72
73pub fn benchmark(comptime H: anytype, bytes: usize, comptime block_size: usize) !Result {
74 var rng = blk: {
75 if (H.init_u8s) |init| {
76 break :blk H.ty.init(init[0..].*);
77 }
78 if (H.init_u64) |init| {
79 break :blk H.ty.init(init);
80 }
81 break :blk H.ty.init();
82 };
83
84 var block: [block_size]u8 = undefined;
85
86 var offset: usize = 0;
87 var timer = try Timer.start();
88 const start = timer.lap();
89 while (offset < bytes) : (offset += block.len) {
90 rng.fill(block[0..]);
91 }
92 const end = timer.read();
93
94 const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
95 const throughput = @as(u64, @intFromFloat(@as(f64, @floatFromInt(bytes)) / elapsed_s));
96
97 std.debug.assert(rng.random().int(u64) != 0);
98
99 return Result{
100 .throughput = throughput,
101 };
102}
103
104fn usage() void {
105 std.debug.print(
106 \\throughput_test [options]
107 \\
108 \\Options:
109 \\ --filter [test-name]
110 \\ --count [int]
111 \\ --prngs-only
112 \\ --csprngs-only
113 \\ --short-only
114 \\ --long-only
115 \\ --help
116 \\
117 , .{});
118}
119
120fn mode(comptime x: comptime_int) comptime_int {
121 return if (builtin.mode == .Debug) x / 64 else x;
122}
123
124pub fn main() !void {
125 const stdout = std.io.getStdOut().writer();
126
127 var buffer: [1024]u8 = undefined;
128 var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
129 const args = try std.process.argsAlloc(fixed.allocator());
130
131 var filter: ?[]u8 = "";
132 var count: usize = mode(128 * MiB);
133 var bench_prngs = true;
134 var bench_csprngs = true;
135 var bench_long = true;
136 var bench_short = true;
137
138 var i: usize = 1;
139 while (i < args.len) : (i += 1) {
140 if (std.mem.eql(u8, args[i], "--mode")) {
141 try stdout.print("{}\n", .{builtin.mode});
142 return;
143 } else if (std.mem.eql(u8, args[i], "--filter")) {
144 i += 1;
145 if (i == args.len) {
146 usage();
147 std.os.exit(1);
148 }
149
150 filter = args[i];
151 } else if (std.mem.eql(u8, args[i], "--count")) {
152 i += 1;
153 if (i == args.len) {
154 usage();
155 std.os.exit(1);
156 }
157
158 const c = try std.fmt.parseUnsigned(usize, args[i], 10);
159 count = c * MiB;
160 } else if (std.mem.eql(u8, args[i], "--csprngs-only")) {
161 bench_prngs = false;
162 } else if (std.mem.eql(u8, args[i], "--prngs-only")) {
163 bench_csprngs = false;
164 } else if (std.mem.eql(u8, args[i], "--short-only")) {
165 bench_long = false;
166 } else if (std.mem.eql(u8, args[i], "--long-only")) {
167 bench_short = false;
168 } else if (std.mem.eql(u8, args[i], "--help")) {
169 usage();
170 return;
171 } else {
172 usage();
173 std.os.exit(1);
174 }
175 }
176
177 if (bench_prngs) {
178 if (bench_long) {
179 inline for (prngs) |R| {
180 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
181 try stdout.print("{s} (long outputs)\n", .{R.name});
182 const result_long = try benchmark(R, count, long_block_size);
183 try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
184 }
185 }
186 }
187 if (bench_short) {
188 inline for (prngs) |R| {
189 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
190 try stdout.print("{s} (short outputs)\n", .{R.name});
191 const result_short = try benchmark(R, count, short_block_size);
192 try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
193 }
194 }
195 }
196 }
197 if (bench_csprngs) {
198 if (bench_long) {
199 inline for (csprngs) |R| {
200 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
201 try stdout.print("{s} (cryptographic, long outputs)\n", .{R.name});
202 const result_long = try benchmark(R, count, long_block_size);
203 try stdout.print(" {:5} MiB/s\n", .{result_long.throughput / (1 * MiB)});
204 }
205 }
206 }
207 if (bench_short) {
208 inline for (csprngs) |R| {
209 if (filter == null or std.mem.indexOf(u8, R.name, filter.?) != null) {
210 try stdout.print("{s} (cryptographic, short outputs)\n", .{R.name});
211 const result_short = try benchmark(R, count, short_block_size);
212 try stdout.print(" {:5} MiB/s\n", .{result_short.throughput / (1 * MiB)});
213 }
214 }
215 }
216 }
217}
lib/std/rand/test.zig deleted-473
......@@ -1,473 +0,0 @@
1const std = @import("../std.zig");
2const math = std.math;
3const DefaultPrng = std.rand.DefaultPrng;
4const Random = std.rand.Random;
5const SplitMix64 = std.rand.SplitMix64;
6const DefaultCsprng = std.rand.DefaultCsprng;
7const expect = std.testing.expect;
8const expectEqual = std.testing.expectEqual;
9
10const SequentialPrng = struct {
11 const Self = @This();
12 next_value: u8,
13
14 pub fn init() Self {
15 return Self{
16 .next_value = 0,
17 };
18 }
19
20 pub fn random(self: *Self) Random {
21 return Random.init(self, fill);
22 }
23
24 pub fn fill(self: *Self, buf: []u8) void {
25 for (buf) |*b| {
26 b.* = self.next_value;
27 }
28 self.next_value +%= 1;
29 }
30};
31
32/// Do not use this PRNG! It is meant to be predictable, for the purposes of test reproducibility and coverage.
33/// Its output is just a repeat of a user-specified byte pattern.
34/// Name is a reference to this comic: https://dilbert.com/strip/2001-10-25
35const Dilbert = struct {
36 pattern: []const u8 = undefined,
37 curr_idx: usize = 0,
38
39 pub fn init(pattern: []const u8) !Dilbert {
40 if (pattern.len == 0)
41 return error.EmptyPattern;
42 var self = Dilbert{};
43 self.pattern = pattern;
44 self.curr_idx = 0;
45 return self;
46 }
47
48 pub fn random(self: *Dilbert) Random {
49 return Random.init(self, fill);
50 }
51
52 pub fn fill(self: *Dilbert, buf: []u8) void {
53 for (buf) |*byte| {
54 byte.* = self.pattern[self.curr_idx];
55 self.curr_idx = (self.curr_idx + 1) % self.pattern.len;
56 }
57 }
58
59 test "Dilbert fill" {
60 var r = try Dilbert.init("9nine");
61
62 const seq = [_]u64{
63 0x396E696E65396E69,
64 0x6E65396E696E6539,
65 0x6E696E65396E696E,
66 0x65396E696E65396E,
67 0x696E65396E696E65,
68 };
69
70 for (seq) |s| {
71 var buf0: [8]u8 = undefined;
72 var buf1: [8]u8 = undefined;
73 std.mem.writeInt(u64, &buf0, s, .big);
74 r.fill(&buf1);
75 try std.testing.expect(std.mem.eql(u8, buf0[0..], buf1[0..]));
76 }
77 }
78};
79
80test "Random int" {
81 try testRandomInt();
82 try comptime testRandomInt();
83}
84fn testRandomInt() !void {
85 var rng = SequentialPrng.init();
86 const random = rng.random();
87
88 try expect(random.int(u0) == 0);
89
90 rng.next_value = 0;
91 try expect(random.int(u1) == 0);
92 try expect(random.int(u1) == 1);
93 try expect(random.int(u2) == 2);
94 try expect(random.int(u2) == 3);
95 try expect(random.int(u2) == 0);
96
97 rng.next_value = 0xff;
98 try expect(random.int(u8) == 0xff);
99 rng.next_value = 0x11;
100 try expect(random.int(u8) == 0x11);
101
102 rng.next_value = 0xff;
103 try expect(random.int(u32) == 0xffffffff);
104 rng.next_value = 0x11;
105 try expect(random.int(u32) == 0x11111111);
106
107 rng.next_value = 0xff;
108 try expect(random.int(i32) == -1);
109 rng.next_value = 0x11;
110 try expect(random.int(i32) == 0x11111111);
111
112 rng.next_value = 0xff;
113 try expect(random.int(i8) == -1);
114 rng.next_value = 0x11;
115 try expect(random.int(i8) == 0x11);
116
117 rng.next_value = 0xff;
118 try expect(random.int(u33) == 0x1ffffffff);
119 rng.next_value = 0xff;
120 try expect(random.int(i1) == -1);
121 rng.next_value = 0xff;
122 try expect(random.int(i2) == -1);
123 rng.next_value = 0xff;
124 try expect(random.int(i33) == -1);
125}
126
127test "Random boolean" {
128 try testRandomBoolean();
129 try comptime testRandomBoolean();
130}
131fn testRandomBoolean() !void {
132 var rng = SequentialPrng.init();
133 const random = rng.random();
134
135 try expect(random.boolean() == false);
136 try expect(random.boolean() == true);
137 try expect(random.boolean() == false);
138 try expect(random.boolean() == true);
139}
140
141test "Random enum" {
142 try testRandomEnumValue();
143 try comptime testRandomEnumValue();
144}
145fn testRandomEnumValue() !void {
146 const TestEnum = enum {
147 First,
148 Second,
149 Third,
150 };
151 var rng = SequentialPrng.init();
152 const random = rng.random();
153 rng.next_value = 0;
154 try expect(random.enumValue(TestEnum) == TestEnum.First);
155 try expect(random.enumValue(TestEnum) == TestEnum.First);
156 try expect(random.enumValue(TestEnum) == TestEnum.First);
157}
158
159test "Random intLessThan" {
160 @setEvalBranchQuota(10000);
161 try testRandomIntLessThan();
162 try comptime testRandomIntLessThan();
163}
164fn testRandomIntLessThan() !void {
165 var rng = SequentialPrng.init();
166 const random = rng.random();
167
168 rng.next_value = 0xff;
169 try expect(random.uintLessThan(u8, 4) == 3);
170 try expect(rng.next_value == 0);
171 try expect(random.uintLessThan(u8, 4) == 0);
172 try expect(rng.next_value == 1);
173
174 rng.next_value = 0;
175 try expect(random.uintLessThan(u64, 32) == 0);
176
177 // trigger the bias rejection code path
178 rng.next_value = 0;
179 try expect(random.uintLessThan(u8, 3) == 0);
180 // verify we incremented twice
181 try expect(rng.next_value == 2);
182
183 rng.next_value = 0xff;
184 try expect(random.intRangeLessThan(u8, 0, 0x80) == 0x7f);
185 rng.next_value = 0xff;
186 try expect(random.intRangeLessThan(u8, 0x7f, 0xff) == 0xfe);
187
188 rng.next_value = 0xff;
189 try expect(random.intRangeLessThan(i8, 0, 0x40) == 0x3f);
190 rng.next_value = 0xff;
191 try expect(random.intRangeLessThan(i8, -0x40, 0x40) == 0x3f);
192 rng.next_value = 0xff;
193 try expect(random.intRangeLessThan(i8, -0x80, 0) == -1);
194
195 rng.next_value = 0xff;
196 try expect(random.intRangeLessThan(i3, -4, 0) == -1);
197 rng.next_value = 0xff;
198 try expect(random.intRangeLessThan(i3, -2, 2) == 1);
199}
200
201test "Random intAtMost" {
202 @setEvalBranchQuota(10000);
203 try testRandomIntAtMost();
204 try comptime testRandomIntAtMost();
205}
206fn testRandomIntAtMost() !void {
207 var rng = SequentialPrng.init();
208 const random = rng.random();
209
210 rng.next_value = 0xff;
211 try expect(random.uintAtMost(u8, 3) == 3);
212 try expect(rng.next_value == 0);
213 try expect(random.uintAtMost(u8, 3) == 0);
214
215 // trigger the bias rejection code path
216 rng.next_value = 0;
217 try expect(random.uintAtMost(u8, 2) == 0);
218 // verify we incremented twice
219 try expect(rng.next_value == 2);
220
221 rng.next_value = 0xff;
222 try expect(random.intRangeAtMost(u8, 0, 0x7f) == 0x7f);
223 rng.next_value = 0xff;
224 try expect(random.intRangeAtMost(u8, 0x7f, 0xfe) == 0xfe);
225
226 rng.next_value = 0xff;
227 try expect(random.intRangeAtMost(i8, 0, 0x3f) == 0x3f);
228 rng.next_value = 0xff;
229 try expect(random.intRangeAtMost(i8, -0x40, 0x3f) == 0x3f);
230 rng.next_value = 0xff;
231 try expect(random.intRangeAtMost(i8, -0x80, -1) == -1);
232
233 rng.next_value = 0xff;
234 try expect(random.intRangeAtMost(i3, -4, -1) == -1);
235 rng.next_value = 0xff;
236 try expect(random.intRangeAtMost(i3, -2, 1) == 1);
237
238 try expect(random.uintAtMost(u0, 0) == 0);
239}
240
241test "Random Biased" {
242 var prng = DefaultPrng.init(0);
243 const random = prng.random();
244 // Not thoroughly checking the logic here.
245 // Just want to execute all the paths with different types.
246
247 try expect(random.uintLessThanBiased(u1, 1) == 0);
248 try expect(random.uintLessThanBiased(u32, 10) < 10);
249 try expect(random.uintLessThanBiased(u64, 20) < 20);
250
251 try expect(random.uintAtMostBiased(u0, 0) == 0);
252 try expect(random.uintAtMostBiased(u1, 0) <= 0);
253 try expect(random.uintAtMostBiased(u32, 10) <= 10);
254 try expect(random.uintAtMostBiased(u64, 20) <= 20);
255
256 try expect(random.intRangeLessThanBiased(u1, 0, 1) == 0);
257 try expect(random.intRangeLessThanBiased(i1, -1, 0) == -1);
258 try expect(random.intRangeLessThanBiased(u32, 10, 20) >= 10);
259 try expect(random.intRangeLessThanBiased(i32, 10, 20) >= 10);
260 try expect(random.intRangeLessThanBiased(u64, 20, 40) >= 20);
261 try expect(random.intRangeLessThanBiased(i64, 20, 40) >= 20);
262
263 // uncomment for broken module error:
264 //expect(random.intRangeAtMostBiased(u0, 0, 0) == 0);
265 try expect(random.intRangeAtMostBiased(u1, 0, 1) >= 0);
266 try expect(random.intRangeAtMostBiased(i1, -1, 0) >= -1);
267 try expect(random.intRangeAtMostBiased(u32, 10, 20) >= 10);
268 try expect(random.intRangeAtMostBiased(i32, 10, 20) >= 10);
269 try expect(random.intRangeAtMostBiased(u64, 20, 40) >= 20);
270 try expect(random.intRangeAtMostBiased(i64, 20, 40) >= 20);
271}
272
273test "splitmix64 sequence" {
274 var r = SplitMix64.init(0xaeecf86f7878dd75);
275
276 const seq = [_]u64{
277 0x5dbd39db0178eb44,
278 0xa9900fb66b397da3,
279 0x5c1a28b1aeebcf5c,
280 0x64a963238f776912,
281 0xc6d4177b21d1c0ab,
282 0xb2cbdbdb5ea35394,
283 };
284
285 for (seq) |s| {
286 try expect(s == r.next());
287 }
288}
289
290// Actual Random helper function tests, pcg engine is assumed correct.
291test "Random float correctness" {
292 var prng = DefaultPrng.init(0);
293 const random = prng.random();
294
295 var i: usize = 0;
296 while (i < 1000) : (i += 1) {
297 const val1 = random.float(f32);
298 try expect(val1 >= 0.0);
299 try expect(val1 < 1.0);
300
301 const val2 = random.float(f64);
302 try expect(val2 >= 0.0);
303 try expect(val2 < 1.0);
304 }
305}
306
307// Check the "astronomically unlikely" code paths.
308test "Random float coverage" {
309 var prng = try Dilbert.init(&[_]u8{0});
310 const random = prng.random();
311
312 const rand_f64 = random.float(f64);
313 const rand_f32 = random.float(f32);
314
315 try expect(rand_f32 == 0.0);
316 try expect(rand_f64 == 0.0);
317}
318
319test "Random float chi-square goodness of fit" {
320 const num_numbers = 100000;
321 const num_buckets = 1000;
322
323 var f32_hist = std.AutoHashMap(u32, u32).init(std.testing.allocator);
324 defer f32_hist.deinit();
325 var f64_hist = std.AutoHashMap(u64, u32).init(std.testing.allocator);
326 defer f64_hist.deinit();
327
328 var prng = DefaultPrng.init(0);
329 const random = prng.random();
330
331 var i: usize = 0;
332 while (i < num_numbers) : (i += 1) {
333 const rand_f32 = random.float(f32);
334 const rand_f64 = random.float(f64);
335 const f32_put = try f32_hist.getOrPut(@as(u32, @intFromFloat(rand_f32 * @as(f32, @floatFromInt(num_buckets)))));
336 if (f32_put.found_existing) {
337 f32_put.value_ptr.* += 1;
338 } else {
339 f32_put.value_ptr.* = 1;
340 }
341 const f64_put = try f64_hist.getOrPut(@as(u32, @intFromFloat(rand_f64 * @as(f64, @floatFromInt(num_buckets)))));
342 if (f64_put.found_existing) {
343 f64_put.value_ptr.* += 1;
344 } else {
345 f64_put.value_ptr.* = 1;
346 }
347 }
348
349 var f32_total_variance: f64 = 0;
350 var f64_total_variance: f64 = 0;
351
352 {
353 var j: u32 = 0;
354 while (j < num_buckets) : (j += 1) {
355 const count = @as(f64, @floatFromInt((if (f32_hist.get(j)) |v| v else 0)));
356 const expected = @as(f64, @floatFromInt(num_numbers)) / @as(f64, @floatFromInt(num_buckets));
357 const delta = count - expected;
358 const variance = (delta * delta) / expected;
359 f32_total_variance += variance;
360 }
361 }
362
363 {
364 var j: u64 = 0;
365 while (j < num_buckets) : (j += 1) {
366 const count = @as(f64, @floatFromInt((if (f64_hist.get(j)) |v| v else 0)));
367 const expected = @as(f64, @floatFromInt(num_numbers)) / @as(f64, @floatFromInt(num_buckets));
368 const delta = count - expected;
369 const variance = (delta * delta) / expected;
370 f64_total_variance += variance;
371 }
372 }
373
374 // Accept p-values >= 0.05.
375 // Critical value is calculated by opening a Python interpreter and running:
376 // scipy.stats.chi2.isf(0.05, num_buckets - 1)
377 const critical_value = 1073.6426506574246;
378 try expect(f32_total_variance < critical_value);
379 try expect(f64_total_variance < critical_value);
380}
381
382test "Random shuffle" {
383 var prng = DefaultPrng.init(0);
384 const random = prng.random();
385
386 var seq = [_]u8{ 0, 1, 2, 3, 4 };
387 var seen = [_]bool{false} ** 5;
388
389 var i: usize = 0;
390 while (i < 1000) : (i += 1) {
391 random.shuffle(u8, seq[0..]);
392 seen[seq[0]] = true;
393 try expect(sumArray(seq[0..]) == 10);
394 }
395
396 // we should see every entry at the head at least once
397 for (seen) |e| {
398 try expect(e == true);
399 }
400}
401
402fn sumArray(s: []const u8) u32 {
403 var r: u32 = 0;
404 for (s) |e|
405 r += e;
406 return r;
407}
408
409test "Random range" {
410 var prng = DefaultPrng.init(0);
411 const random = prng.random();
412
413 try testRange(random, -4, 3);
414 try testRange(random, -4, -1);
415 try testRange(random, 10, 14);
416 try testRange(random, -0x80, 0x7f);
417}
418
419fn testRange(r: Random, start: i8, end: i8) !void {
420 try testRangeBias(r, start, end, true);
421 try testRangeBias(r, start, end, false);
422}
423fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void {
424 const count = @as(usize, @intCast(@as(i32, end) - @as(i32, start)));
425 var values_buffer = [_]bool{false} ** 0x100;
426 const values = values_buffer[0..count];
427 var i: usize = 0;
428 while (i < count) {
429 const value: i32 = if (biased) r.intRangeLessThanBiased(i8, start, end) else r.intRangeLessThan(i8, start, end);
430 const index = @as(usize, @intCast(value - start));
431 if (!values[index]) {
432 i += 1;
433 values[index] = true;
434 }
435 }
436}
437
438test "CSPRNG" {
439 var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined;
440 std.crypto.random.bytes(&secret_seed);
441 var csprng = DefaultCsprng.init(secret_seed);
442 const random = csprng.random();
443 const a = random.int(u64);
444 const b = random.int(u64);
445 const c = random.int(u64);
446 try expect(a ^ b ^ c != 0);
447}
448
449test "Random weightedIndex" {
450 // Make sure weightedIndex works for various integers and floats
451 inline for (.{ u64, i4, f32, f64 }) |T| {
452 var prng = DefaultPrng.init(0);
453 const random = prng.random();
454
455 const proportions = [_]T{ 2, 1, 1, 2 };
456 var counts = [_]f64{ 0, 0, 0, 0 };
457
458 const n_trials: u64 = 10_000;
459 var i: usize = 0;
460 while (i < n_trials) : (i += 1) {
461 const pick = random.weightedIndex(T, &proportions);
462 counts[pick] += 1;
463 }
464
465 // We expect the first and last counts to be roughly 2x the second and third
466 const approxEqRel = std.math.approxEqRel;
467 // Define "roughly" to be within 10%
468 const tolerance = 0.1;
469 try std.testing.expect(approxEqRel(f64, counts[0], counts[1] * 2, tolerance));
470 try std.testing.expect(approxEqRel(f64, counts[1], counts[2], tolerance));
471 try std.testing.expect(approxEqRel(f64, counts[2] * 2, counts[3], tolerance));
472 }
473}
lib/std/rand/ziggurat.zig deleted-170
......@@ -1,170 +0,0 @@
1//! Implements [ZIGNOR][1] (Jurgen A. Doornik, 2005, Nuffield College, Oxford).
2//!
3//! [1]: https://www.doornik.com/research/ziggurat.pdf
4//!
5//! rust/rand used as a reference;
6//!
7//! NOTE: This seems interesting but reference code is a bit hard to grok:
8//! https://sbarral.github.io/etf.
9
10const std = @import("../std.zig");
11const builtin = @import("builtin");
12const math = std.math;
13const Random = std.rand.Random;
14
15pub fn next_f64(random: Random, comptime tables: ZigTable) f64 {
16 while (true) {
17 // We manually construct a float from parts as we can avoid an extra random lookup here by
18 // using the unused exponent for the lookup table entry.
19 const bits = random.int(u64);
20 const i = @as(usize, @as(u8, @truncate(bits)));
21
22 const u = blk: {
23 if (tables.is_symmetric) {
24 // Generate a value in the range [2, 4) and scale into [-1, 1)
25 const repr = ((0x3ff + 1) << 52) | (bits >> 12);
26 break :blk @as(f64, @bitCast(repr)) - 3.0;
27 } else {
28 // Generate a value in the range [1, 2) and scale into (0, 1)
29 const repr = (0x3ff << 52) | (bits >> 12);
30 break :blk @as(f64, @bitCast(repr)) - (1.0 - math.floatEps(f64) / 2.0);
31 }
32 };
33
34 const x = u * tables.x[i];
35 const test_x = if (tables.is_symmetric) @abs(x) else x;
36
37 // equivalent to |u| < tables.x[i+1] / tables.x[i] (or u < tables.x[i+1] / tables.x[i])
38 if (test_x < tables.x[i + 1]) {
39 return x;
40 }
41
42 if (i == 0) {
43 return tables.zero_case(random, u);
44 }
45
46 // equivalent to f1 + DRanU() * (f0 - f1) < 1
47 if (tables.f[i + 1] + (tables.f[i] - tables.f[i + 1]) * random.float(f64) < tables.pdf(x)) {
48 return x;
49 }
50 }
51}
52
53pub const ZigTable = struct {
54 r: f64,
55 x: [257]f64,
56 f: [257]f64,
57
58 // probability density function used as a fallback
59 pdf: fn (f64) f64,
60 // whether the distribution is symmetric
61 is_symmetric: bool,
62 // fallback calculation in the case we are in the 0 block
63 zero_case: fn (Random, f64) f64,
64};
65
66// zigNorInit
67pub fn ZigTableGen(
68 comptime is_symmetric: bool,
69 comptime r: f64,
70 comptime v: f64,
71 comptime f: fn (f64) f64,
72 comptime f_inv: fn (f64) f64,
73 comptime zero_case: fn (Random, f64) f64,
74) ZigTable {
75 var tables: ZigTable = undefined;
76
77 tables.is_symmetric = is_symmetric;
78 tables.r = r;
79 tables.pdf = f;
80 tables.zero_case = zero_case;
81
82 tables.x[0] = v / f(r);
83 tables.x[1] = r;
84
85 for (tables.x[2..256], 0..) |*entry, i| {
86 const last = tables.x[2 + i - 1];
87 entry.* = f_inv(v / last + f(last));
88 }
89 tables.x[256] = 0;
90
91 for (tables.f[0..], 0..) |*entry, i| {
92 entry.* = f(tables.x[i]);
93 }
94
95 return tables;
96}
97
98// N(0, 1)
99pub const NormDist = blk: {
100 @setEvalBranchQuota(30000);
101 break :blk ZigTableGen(true, norm_r, norm_v, norm_f, norm_f_inv, norm_zero_case);
102};
103
104pub const norm_r = 3.6541528853610088;
105pub const norm_v = 0.00492867323399;
106
107pub fn norm_f(x: f64) f64 {
108 return @exp(-x * x / 2.0);
109}
110pub fn norm_f_inv(y: f64) f64 {
111 return @sqrt(-2.0 * @log(y));
112}
113pub fn norm_zero_case(random: Random, u: f64) f64 {
114 var x: f64 = 1;
115 var y: f64 = 0;
116
117 while (-2.0 * y < x * x) {
118 x = @log(random.float(f64)) / norm_r;
119 y = @log(random.float(f64));
120 }
121
122 if (u < 0) {
123 return x - norm_r;
124 } else {
125 return norm_r - x;
126 }
127}
128
129test "normal dist sanity" {
130 var prng = std.rand.DefaultPrng.init(0);
131 const random = prng.random();
132
133 var i: usize = 0;
134 while (i < 1000) : (i += 1) {
135 _ = random.floatNorm(f64);
136 }
137}
138
139// Exp(1)
140pub const ExpDist = blk: {
141 @setEvalBranchQuota(30000);
142 break :blk ZigTableGen(false, exp_r, exp_v, exp_f, exp_f_inv, exp_zero_case);
143};
144
145pub const exp_r = 7.69711747013104972;
146pub const exp_v = 0.0039496598225815571993;
147
148pub fn exp_f(x: f64) f64 {
149 return @exp(-x);
150}
151pub fn exp_f_inv(y: f64) f64 {
152 return -@log(y);
153}
154pub fn exp_zero_case(random: Random, _: f64) f64 {
155 return exp_r - @log(random.float(f64));
156}
157
158test "exp dist smoke test" {
159 var prng = std.rand.DefaultPrng.init(0);
160 const random = prng.random();
161
162 var i: usize = 0;
163 while (i < 1000) : (i += 1) {
164 _ = random.floatExp(f64);
165 }
166}
167
168test {
169 _ = NormDist;
170}
lib/std/sort.zig+1-1
......@@ -379,7 +379,7 @@ test "sort with context in the middle of a slice" {
379379}
380380
381381test "sort fuzz testing" {
382 var prng = std.rand.DefaultPrng.init(0x12345678);
382 var prng = std.Random.DefaultPrng.init(0x12345678);
383383 const random = prng.random();
384384 const test_case_count = 10;
385385
lib/std/std.zig+3-2
......@@ -36,6 +36,7 @@ pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceE
3636pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
3737pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;
3838pub const Progress = @import("Progress.zig");
39pub const Random = @import("Random.zig");
3940pub const RingBuffer = @import("RingBuffer.zig");
4041pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
4142pub const SemanticVersion = @import("SemanticVersion.zig");
......@@ -156,8 +157,8 @@ pub const pdb = @import("pdb.zig");
156157/// and spawning of child processes.
157158pub const process = @import("process.zig");
158159
159/// Fast pseudo-random number generators (i.e. not cryptographically secure).
160pub const rand = @import("rand.zig");
160/// Deprecated: use `Random` instead.
161pub const rand = Random;
161162
162163/// Sorting.
163164pub const sort = @import("sort.zig");
lib/std/treap.zig+4-4
......@@ -18,7 +18,7 @@ pub fn Treap(comptime Key: type, comptime compareFn: anytype) type {
1818
1919 /// A customized pseudo random number generator for the treap.
2020 /// This just helps reducing the memory size of the treap itself
21 /// as std.rand.DefaultPrng requires larger state (while producing better entropy for randomness to be fair).
21 /// as std.Random.DefaultPrng requires larger state (while producing better entropy for randomness to be fair).
2222 const Prng = struct {
2323 xorshift: usize = 0,
2424
......@@ -305,7 +305,7 @@ pub fn Treap(comptime Key: type, comptime compareFn: anytype) type {
305305// https://lemire.me/blog/2017/09/18/visiting-all-values-in-an-array-exactly-once-in-random-order/
306306fn SliceIterRandomOrder(comptime T: type) type {
307307 return struct {
308 rng: std.rand.Random,
308 rng: std.Random,
309309 slice: []T,
310310 index: usize = undefined,
311311 offset: usize = undefined,
......@@ -313,7 +313,7 @@ fn SliceIterRandomOrder(comptime T: type) type {
313313
314314 const Self = @This();
315315
316 pub fn init(slice: []T, rng: std.rand.Random) Self {
316 pub fn init(slice: []T, rng: std.Random) Self {
317317 return Self{
318318 .rng = rng,
319319 .slice = slice,
......@@ -353,7 +353,7 @@ test "std.Treap: insert, find, replace, remove" {
353353 var treap = TestTreap{};
354354 var nodes: [10]TestNode = undefined;
355355
356 var prng = std.rand.DefaultPrng.init(0xdeadbeef);
356 var prng = std.Random.DefaultPrng.init(0xdeadbeef);
357357 var iter = SliceIterRandomOrder(TestNode).init(&nodes, prng.random());
358358
359359 // insert check
src/reduce.zig+2-2
......@@ -136,7 +136,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
136136 var more_fixups: Ast.Fixups = .{};
137137 defer more_fixups.deinit(gpa);
138138
139 var rng = std.rand.DefaultPrng.init(seed);
139 var rng = std.Random.DefaultPrng.init(seed);
140140
141141 // 1. Walk the AST of the source file looking for independent
142142 // reductions and collecting them all into an array list.
......@@ -274,7 +274,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
274274 return std.process.cleanExit();
275275}
276276
277fn sortTransformations(transformations: []Walk.Transformation, rng: std.rand.Random) void {
277fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {
278278 rng.shuffle(Walk.Transformation, transformations);
279279 // Stable sort based on priority to keep randomness as the secondary sort.
280280 // TODO: introduce transformation priorities
src/resinator/compile.zig+1-1
......@@ -3356,7 +3356,7 @@ test "StringTable" {
33563356 }
33573357 break :ids buf;
33583358 };
3359 var prng = std.rand.DefaultPrng.init(0);
3359 var prng = std.Random.DefaultPrng.init(0);
33603360 var random = prng.random();
33613361 random.shuffle(u16, &ids);
33623362