authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-10 19:02:03-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-10 19:02:03-07:00
log7bedeb9659af84daab33a4804c54b4c8811c0c5d
tree1fc0bfeae8cbb443d7ab416b0308a0db1149ab8a
parent550888e2ac0dca25b5d736676bfe3ee9d4c8a8ec

std.rand: move tests to a separate test file


3 files changed, 448 insertions(+), 447 deletions(-)

lib/std/rand.zig+1-395
...@@ -9,15 +9,11 @@...@@ -9,15 +9,11 @@
9const std = @import("std.zig");9const std = @import("std.zig");
10const builtin = @import("builtin");10const builtin = @import("builtin");
11const assert = std.debug.assert;11const assert = std.debug.assert;
12const expect = std.testing.expect;
13const expectEqual = std.testing.expectEqual;
14const mem = std.mem;12const mem = std.mem;
15const math = std.math;13const math = std.math;
16const ziggurat = @import("rand/ziggurat.zig");14const ziggurat = @import("rand/ziggurat.zig");
17const maxInt = std.math.maxInt;15const maxInt = std.math.maxInt;
1816
19const Dilbert = @import("rand/Dilbert.zig");
20
21/// Fast unbiased random numbers.17/// Fast unbiased random numbers.
22pub const DefaultPrng = Xoshiro256;18pub const DefaultPrng = Xoshiro256;
2319
...@@ -354,221 +350,6 @@ pub fn limitRangeBiased(comptime T: type, random_int: T, less_than: T) T {...@@ -354,221 +350,6 @@ pub fn limitRangeBiased(comptime T: type, random_int: T, less_than: T) T {
354 return @intCast(T, m >> bits);350 return @intCast(T, m >> bits);
355}351}
356352
357const SequentialPrng = struct {
358 const Self = @This();
359 next_value: u8,
360
361 pub fn init() Self {
362 return Self{
363 .next_value = 0,
364 };
365 }
366
367 pub fn random(self: *Self) Random {
368 return Random.init(self, fill);
369 }
370
371 pub fn fill(self: *Self, buf: []u8) void {
372 for (buf) |*b| {
373 b.* = self.next_value;
374 }
375 self.next_value +%= 1;
376 }
377};
378
379test "Random int" {
380 try testRandomInt();
381 comptime try testRandomInt();
382}
383fn testRandomInt() !void {
384 var rng = SequentialPrng.init();
385 const random = rng.random();
386
387 try expect(random.int(u0) == 0);
388
389 rng.next_value = 0;
390 try expect(random.int(u1) == 0);
391 try expect(random.int(u1) == 1);
392 try expect(random.int(u2) == 2);
393 try expect(random.int(u2) == 3);
394 try expect(random.int(u2) == 0);
395
396 rng.next_value = 0xff;
397 try expect(random.int(u8) == 0xff);
398 rng.next_value = 0x11;
399 try expect(random.int(u8) == 0x11);
400
401 rng.next_value = 0xff;
402 try expect(random.int(u32) == 0xffffffff);
403 rng.next_value = 0x11;
404 try expect(random.int(u32) == 0x11111111);
405
406 rng.next_value = 0xff;
407 try expect(random.int(i32) == -1);
408 rng.next_value = 0x11;
409 try expect(random.int(i32) == 0x11111111);
410
411 rng.next_value = 0xff;
412 try expect(random.int(i8) == -1);
413 rng.next_value = 0x11;
414 try expect(random.int(i8) == 0x11);
415
416 rng.next_value = 0xff;
417 try expect(random.int(u33) == 0x1ffffffff);
418 rng.next_value = 0xff;
419 try expect(random.int(i1) == -1);
420 rng.next_value = 0xff;
421 try expect(random.int(i2) == -1);
422 rng.next_value = 0xff;
423 try expect(random.int(i33) == -1);
424}
425
426test "Random boolean" {
427 try testRandomBoolean();
428 comptime try testRandomBoolean();
429}
430fn testRandomBoolean() !void {
431 var rng = SequentialPrng.init();
432 const random = rng.random();
433
434 try expect(random.boolean() == false);
435 try expect(random.boolean() == true);
436 try expect(random.boolean() == false);
437 try expect(random.boolean() == true);
438}
439
440test "Random enum" {
441 try testRandomEnumValue();
442 comptime try testRandomEnumValue();
443}
444fn testRandomEnumValue() !void {
445 const TestEnum = enum {
446 First,
447 Second,
448 Third,
449 };
450 var rng = SequentialPrng.init();
451 const random = rng.random();
452 rng.next_value = 0;
453 try expect(random.enumValue(TestEnum) == TestEnum.First);
454 try expect(random.enumValue(TestEnum) == TestEnum.First);
455 try expect(random.enumValue(TestEnum) == TestEnum.First);
456}
457
458test "Random intLessThan" {
459 @setEvalBranchQuota(10000);
460 try testRandomIntLessThan();
461 comptime try testRandomIntLessThan();
462}
463fn testRandomIntLessThan() !void {
464 var rng = SequentialPrng.init();
465 const random = rng.random();
466
467 rng.next_value = 0xff;
468 try expect(random.uintLessThan(u8, 4) == 3);
469 try expect(rng.next_value == 0);
470 try expect(random.uintLessThan(u8, 4) == 0);
471 try expect(rng.next_value == 1);
472
473 rng.next_value = 0;
474 try expect(random.uintLessThan(u64, 32) == 0);
475
476 // trigger the bias rejection code path
477 rng.next_value = 0;
478 try expect(random.uintLessThan(u8, 3) == 0);
479 // verify we incremented twice
480 try expect(rng.next_value == 2);
481
482 rng.next_value = 0xff;
483 try expect(random.intRangeLessThan(u8, 0, 0x80) == 0x7f);
484 rng.next_value = 0xff;
485 try expect(random.intRangeLessThan(u8, 0x7f, 0xff) == 0xfe);
486
487 rng.next_value = 0xff;
488 try expect(random.intRangeLessThan(i8, 0, 0x40) == 0x3f);
489 rng.next_value = 0xff;
490 try expect(random.intRangeLessThan(i8, -0x40, 0x40) == 0x3f);
491 rng.next_value = 0xff;
492 try expect(random.intRangeLessThan(i8, -0x80, 0) == -1);
493
494 rng.next_value = 0xff;
495 try expect(random.intRangeLessThan(i3, -4, 0) == -1);
496 rng.next_value = 0xff;
497 try expect(random.intRangeLessThan(i3, -2, 2) == 1);
498}
499
500test "Random intAtMost" {
501 @setEvalBranchQuota(10000);
502 try testRandomIntAtMost();
503 comptime try testRandomIntAtMost();
504}
505fn testRandomIntAtMost() !void {
506 var rng = SequentialPrng.init();
507 const random = rng.random();
508
509 rng.next_value = 0xff;
510 try expect(random.uintAtMost(u8, 3) == 3);
511 try expect(rng.next_value == 0);
512 try expect(random.uintAtMost(u8, 3) == 0);
513
514 // trigger the bias rejection code path
515 rng.next_value = 0;
516 try expect(random.uintAtMost(u8, 2) == 0);
517 // verify we incremented twice
518 try expect(rng.next_value == 2);
519
520 rng.next_value = 0xff;
521 try expect(random.intRangeAtMost(u8, 0, 0x7f) == 0x7f);
522 rng.next_value = 0xff;
523 try expect(random.intRangeAtMost(u8, 0x7f, 0xfe) == 0xfe);
524
525 rng.next_value = 0xff;
526 try expect(random.intRangeAtMost(i8, 0, 0x3f) == 0x3f);
527 rng.next_value = 0xff;
528 try expect(random.intRangeAtMost(i8, -0x40, 0x3f) == 0x3f);
529 rng.next_value = 0xff;
530 try expect(random.intRangeAtMost(i8, -0x80, -1) == -1);
531
532 rng.next_value = 0xff;
533 try expect(random.intRangeAtMost(i3, -4, -1) == -1);
534 rng.next_value = 0xff;
535 try expect(random.intRangeAtMost(i3, -2, 1) == 1);
536
537 try expect(random.uintAtMost(u0, 0) == 0);
538}
539
540test "Random Biased" {
541 var prng = DefaultPrng.init(0);
542 const random = prng.random();
543 // Not thoroughly checking the logic here.
544 // Just want to execute all the paths with different types.
545
546 try expect(random.uintLessThanBiased(u1, 1) == 0);
547 try expect(random.uintLessThanBiased(u32, 10) < 10);
548 try expect(random.uintLessThanBiased(u64, 20) < 20);
549
550 try expect(random.uintAtMostBiased(u0, 0) == 0);
551 try expect(random.uintAtMostBiased(u1, 0) <= 0);
552 try expect(random.uintAtMostBiased(u32, 10) <= 10);
553 try expect(random.uintAtMostBiased(u64, 20) <= 20);
554
555 try expect(random.intRangeLessThanBiased(u1, 0, 1) == 0);
556 try expect(random.intRangeLessThanBiased(i1, -1, 0) == -1);
557 try expect(random.intRangeLessThanBiased(u32, 10, 20) >= 10);
558 try expect(random.intRangeLessThanBiased(i32, 10, 20) >= 10);
559 try expect(random.intRangeLessThanBiased(u64, 20, 40) >= 20);
560 try expect(random.intRangeLessThanBiased(i64, 20, 40) >= 20);
561
562 // uncomment for broken module error:
563 //expect(random.intRangeAtMostBiased(u0, 0, 0) == 0);
564 try expect(random.intRangeAtMostBiased(u1, 0, 1) >= 0);
565 try expect(random.intRangeAtMostBiased(i1, -1, 0) >= -1);
566 try expect(random.intRangeAtMostBiased(u32, 10, 20) >= 10);
567 try expect(random.intRangeAtMostBiased(i32, 10, 20) >= 10);
568 try expect(random.intRangeAtMostBiased(u64, 20, 40) >= 20);
569 try expect(random.intRangeAtMostBiased(i64, 20, 40) >= 20);
570}
571
572// Generator to extend 64-bit seed values into longer sequences.353// Generator to extend 64-bit seed values into longer sequences.
573//354//
574// The number of cycles is thus limited to 64-bits regardless of the engine, but this355// The number of cycles is thus limited to 64-bits regardless of the engine, but this
...@@ -590,182 +371,7 @@ pub const SplitMix64 = struct {...@@ -590,182 +371,7 @@ pub const SplitMix64 = struct {
590 }371 }
591};372};
592373
593test "splitmix64 sequence" {
594 var r = SplitMix64.init(0xaeecf86f7878dd75);
595
596 const seq = [_]u64{
597 0x5dbd39db0178eb44,
598 0xa9900fb66b397da3,
599 0x5c1a28b1aeebcf5c,
600 0x64a963238f776912,
601 0xc6d4177b21d1c0ab,
602 0xb2cbdbdb5ea35394,
603 };
604
605 for (seq) |s| {
606 try expect(s == r.next());
607 }
608}
609
610// Actual Random helper function tests, pcg engine is assumed correct.
611test "Random float correctness" {
612 var prng = DefaultPrng.init(0);
613 const random = prng.random();
614
615 var i: usize = 0;
616 while (i < 1000) : (i += 1) {
617 const val1 = random.float(f32);
618 try expect(val1 >= 0.0);
619 try expect(val1 < 1.0);
620
621 const val2 = random.float(f64);
622 try expect(val2 >= 0.0);
623 try expect(val2 < 1.0);
624 }
625}
626
627// Check the "astronomically unlikely" code paths.
628test "Random float coverage" {
629 var prng = try Dilbert.init(&[_]u8{0});
630 const random = prng.random();
631
632 const rand_f64 = random.float(f64);
633 const rand_f32 = random.float(f32);
634
635 try expect(rand_f32 == 0.0);
636 try expect(rand_f64 == 0.0);
637}
638
639test "Random float chi-square goodness of fit" {
640 const num_numbers = 100000;
641 const num_buckets = 1000;
642
643 var f32_hist = std.AutoHashMap(u32, u32).init(std.testing.allocator);
644 defer f32_hist.deinit();
645 var f64_hist = std.AutoHashMap(u64, u32).init(std.testing.allocator);
646 defer f64_hist.deinit();
647
648 var prng = DefaultPrng.init(0);
649 const random = prng.random();
650
651 var i: usize = 0;
652 while (i < num_numbers) : (i += 1) {
653 const rand_f32 = random.float(f32);
654 const rand_f64 = random.float(f64);
655 var f32_put = try f32_hist.getOrPut(@floatToInt(u32, rand_f32 * @intToFloat(f32, num_buckets)));
656 if (f32_put.found_existing) {
657 f32_put.value_ptr.* += 1;
658 } else {
659 f32_put.value_ptr.* = 0;
660 }
661 var f64_put = try f64_hist.getOrPut(@floatToInt(u32, rand_f64 * @intToFloat(f64, num_buckets)));
662 if (f64_put.found_existing) {
663 f64_put.value_ptr.* += 1;
664 } else {
665 f64_put.value_ptr.* = 0;
666 }
667 }
668
669 var f32_total_variance: f64 = 0;
670 var f64_total_variance: f64 = 0;
671
672 {
673 var j: u32 = 0;
674 while (j < num_buckets) : (j += 1) {
675 const count = @intToFloat(f64, (if (f32_hist.get(j)) |v| v else 0));
676 const expected = @intToFloat(f64, num_numbers) / @intToFloat(f64, num_buckets);
677 const delta = count - expected;
678 const variance = (delta * delta) / expected;
679 f32_total_variance += variance;
680 }
681 }
682
683 {
684 var j: u64 = 0;
685 while (j < num_buckets) : (j += 1) {
686 const count = @intToFloat(f64, (if (f64_hist.get(j)) |v| v else 0));
687 const expected = @intToFloat(f64, num_numbers) / @intToFloat(f64, num_buckets);
688 const delta = count - expected;
689 const variance = (delta * delta) / expected;
690 f64_total_variance += variance;
691 }
692 }
693
694 // Corresponds to a p-value > 0.05.
695 // Critical value is calculated by opening a Python interpreter and running:
696 // scipy.stats.chi2.isf(0.05, num_buckets - 1)
697 const critical_value = 1073.6426506574246;
698 try expect(f32_total_variance < critical_value);
699 try expect(f64_total_variance < critical_value);
700}
701
702test "Random shuffle" {
703 var prng = DefaultPrng.init(0);
704 const random = prng.random();
705
706 var seq = [_]u8{ 0, 1, 2, 3, 4 };
707 var seen = [_]bool{false} ** 5;
708
709 var i: usize = 0;
710 while (i < 1000) : (i += 1) {
711 random.shuffle(u8, seq[0..]);
712 seen[seq[0]] = true;
713 try expect(sumArray(seq[0..]) == 10);
714 }
715
716 // we should see every entry at the head at least once
717 for (seen) |e| {
718 try expect(e == true);
719 }
720}
721
722fn sumArray(s: []const u8) u32 {
723 var r: u32 = 0;
724 for (s) |e|
725 r += e;
726 return r;
727}
728
729test "Random range" {
730 var prng = DefaultPrng.init(0);
731 const random = prng.random();
732
733 try testRange(random, -4, 3);
734 try testRange(random, -4, -1);
735 try testRange(random, 10, 14);
736 try testRange(random, -0x80, 0x7f);
737}
738
739fn testRange(r: Random, start: i8, end: i8) !void {
740 try testRangeBias(r, start, end, true);
741 try testRangeBias(r, start, end, false);
742}
743fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void {
744 const count = @intCast(usize, @as(i32, end) - @as(i32, start));
745 var values_buffer = [_]bool{false} ** 0x100;
746 const values = values_buffer[0..count];
747 var i: usize = 0;
748 while (i < count) {
749 const value: i32 = if (biased) r.intRangeLessThanBiased(i8, start, end) else r.intRangeLessThan(i8, start, end);
750 const index = @intCast(usize, value - start);
751 if (!values[index]) {
752 i += 1;
753 values[index] = true;
754 }
755 }
756}
757
758test "CSPRNG" {
759 var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined;
760 std.crypto.random.bytes(&secret_seed);
761 var csprng = DefaultCsprng.init(secret_seed);
762 const random = csprng.random();
763 const a = random.int(u64);
764 const b = random.int(u64);
765 const c = random.int(u64);
766 try expect(a ^ b ^ c != 0);
767}
768
769test {374test {
770 std.testing.refAllDecls(@This());375 std.testing.refAllDecls(@This());
376 _ = @import("rand/test.zig");
771}377}
lib/std/rand/Dilbert.zig deleted-52
...@@ -1,52 +0,0 @@
1//! Dilbert PRNG
2//! Do not use this PRNG! It is meant to be predictable, for the purposes of test reproducibility and coverage.
3//! Its output is just a repeat of a user-specified byte pattern.
4//! Name is a reference to this comic: https://dilbert.com/strip/2001-10-25
5
6const std = @import("std");
7const Random = std.rand.Random;
8const math = std.math;
9const Dilbert = @This();
10
11pattern: []const u8 = undefined,
12curr_idx: usize = 0,
13
14pub fn init(pattern: []const u8) !Dilbert {
15 if (pattern.len == 0)
16 return error.EmptyPattern;
17 var self = Dilbert{};
18 self.pattern = pattern;
19 self.curr_idx = 0;
20 return self;
21}
22
23pub fn random(self: *Dilbert) Random {
24 return Random.init(self, fill);
25}
26
27pub fn fill(self: *Dilbert, buf: []u8) void {
28 for (buf) |*byte| {
29 byte.* = self.pattern[self.curr_idx];
30 self.curr_idx = (self.curr_idx + 1) % self.pattern.len;
31 }
32}
33
34test "Dilbert fill" {
35 var r = try Dilbert.init("9nine");
36
37 const seq = [_]u64{
38 0x396E696E65396E69,
39 0x6E65396E696E6539,
40 0x6E696E65396E696E,
41 0x65396E696E65396E,
42 0x696E65396E696E65,
43 };
44
45 for (seq) |s| {
46 var buf0: [8]u8 = undefined;
47 var buf1: [8]u8 = undefined;
48 std.mem.writeIntBig(u64, &buf0, s);
49 r.fill(&buf1);
50 try std.testing.expect(std.mem.eql(u8, buf0[0..], buf1[0..]));
51 }
52}
lib/std/rand/test.zig created+447
...@@ -0,0 +1,447 @@
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.writeIntBig(u64, &buf0, s);
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 comptime try 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 comptime try 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 comptime try 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 comptime try 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 comptime try 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 var f32_put = try f32_hist.getOrPut(@floatToInt(u32, rand_f32 * @intToFloat(f32, num_buckets)));
336 if (f32_put.found_existing) {
337 f32_put.value_ptr.* += 1;
338 } else {
339 f32_put.value_ptr.* = 0;
340 }
341 var f64_put = try f64_hist.getOrPut(@floatToInt(u32, rand_f64 * @intToFloat(f64, num_buckets)));
342 if (f64_put.found_existing) {
343 f64_put.value_ptr.* += 1;
344 } else {
345 f64_put.value_ptr.* = 0;
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 = @intToFloat(f64, (if (f32_hist.get(j)) |v| v else 0));
356 const expected = @intToFloat(f64, num_numbers) / @intToFloat(f64, 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 = @intToFloat(f64, (if (f64_hist.get(j)) |v| v else 0));
367 const expected = @intToFloat(f64, num_numbers) / @intToFloat(f64, num_buckets);
368 const delta = count - expected;
369 const variance = (delta * delta) / expected;
370 f64_total_variance += variance;
371 }
372 }
373
374 // Corresponds to a p-value > 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 = @intCast(usize, @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 = @intCast(usize, 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}