authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2018-11-21 17:33:37-05:00
committergravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2018-11-24 22:25:21-05:00
log1924ffa67d90f21852680b7c9a09df07fc218ebe
tree28606b05dffdd654ee48e3e04608ba1a10c2e854
parent4d747d452fba0987332244c777dfd3a29d5a20bd

better debiased random range implementation


1 files changed, 46 insertions(+), 32 deletions(-)

std/rand/index.zig+46-32
......@@ -69,22 +69,36 @@ pub const Random = struct {
6969 pub fn uintLessThan(r: *Random, comptime T: type, less_than: T) T {
7070 assert(T.is_signed == false);
7171 assert(0 < less_than);
72
73 const last_group_size_minus_one: T = maxInt(T) % less_than;
74 if (last_group_size_minus_one == less_than - 1) {
75 // less_than is a power of two.
76 assert(math.floorPowerOfTwo(T, less_than) == less_than);
77 // There is no retry zone. The optimal retry_zone_start would be maxInt(T) + 1.
78 return r.int(T) % less_than;
79 }
80 const retry_zone_start = maxInt(T) - last_group_size_minus_one;
81
82 while (true) {
83 const rand_val = r.int(T);
84 if (rand_val < retry_zone_start) {
85 return rand_val % less_than;
72 // Small is typically u32
73 const Small = @IntType(false, @divTrunc(T.bit_count + 31, 32) * 32);
74 // Large is typically u64
75 const Large = @IntType(false, Small.bit_count * 2);
76
77 // adapted from:
78 // http://www.pcg-random.org/posts/bounded-rands.html
79 // "Lemire's (with an extra tweak from me)"
80 var x: Small = r.int(Small);
81 var m: Large = Large(x) * Large(less_than);
82 var l: Small = @truncate(Small, m);
83 if (l < less_than) {
84 // TODO: workaround for https://github.com/ziglang/zig/issues/1770
85 // should be:
86 // var t: Small = -%less_than;
87 var t: Small = @bitCast(Small, -%@bitCast(@IntType(true, Small.bit_count), Small(less_than)));
88
89 if (t >= less_than) {
90 t -= less_than;
91 if (t >= less_than) {
92 t %= less_than;
93 }
94 }
95 while (l < t) {
96 x = r.int(Small);
97 m = Large(x) * Large(less_than);
98 l = @truncate(Small, m);
8699 }
87100 }
101 return @intCast(T, m >> Small.bit_count);
88102 }
89103
90104 /// Returns an evenly distributed random unsigned integer `0 <= i <= at_most`.
......@@ -294,10 +308,19 @@ fn testRandomIntLessThan() void {
294308 var r = SequentialPrng.init();
295309 r.next_value = 0xff;
296310 assert(r.random.uintLessThan(u8, 4) == 3);
297 r.next_value = 0xff;
298 assert(r.random.uintLessThan(u8, 3) == 0);
311 assert(r.next_value == 0);
312 assert(r.random.uintLessThan(u8, 4) == 0);
299313 assert(r.next_value == 1);
300314
315 r.next_value = 0;
316 assert(r.random.uintLessThan(u64, 32) == 0);
317
318 // trigger the bias rejection code path
319 r.next_value = 0;
320 assert(r.random.uintLessThan(u8, 3) == 0);
321 // verify we incremented twice
322 assert(r.next_value == 2);
323
301324 r.next_value = 0xff;
302325 assert(r.random.intRangeLessThan(u8, 0, 0x80) == 0x7f);
303326 r.next_value = 0xff;
......@@ -310,17 +333,10 @@ fn testRandomIntLessThan() void {
310333 r.next_value = 0xff;
311334 assert(r.random.intRangeLessThan(i8, -0x80, 0) == -1);
312335
313 r.next_value = 0xff;
314 assert(r.random.intRangeLessThan(i64, -0x8000000000000000, 0) == -1);
315336 r.next_value = 0xff;
316337 assert(r.random.intRangeLessThan(i3, -4, 0) == -1);
317338 r.next_value = 0xff;
318339 assert(r.random.intRangeLessThan(i3, -2, 2) == 1);
319
320 // test retrying and eventually getting a good value
321 // start just out of bounds
322 r.next_value = 0x81;
323 assert(r.random.uintLessThan(u8, 0x81) == 0);
324340}
325341
326342test "Random intAtMost" {
......@@ -332,9 +348,14 @@ fn testRandomIntAtMost() void {
332348 var r = SequentialPrng.init();
333349 r.next_value = 0xff;
334350 assert(r.random.uintAtMost(u8, 3) == 3);
335 r.next_value = 0xff;
351 assert(r.next_value == 0);
352 assert(r.random.uintAtMost(u8, 3) == 0);
353
354 // trigger the bias rejection code path
355 r.next_value = 0;
336356 assert(r.random.uintAtMost(u8, 2) == 0);
337 assert(r.next_value == 1);
357 // verify we incremented twice
358 assert(r.next_value == 2);
338359
339360 r.next_value = 0xff;
340361 assert(r.random.intRangeAtMost(u8, 0, 0x7f) == 0x7f);
......@@ -348,17 +369,10 @@ fn testRandomIntAtMost() void {
348369 r.next_value = 0xff;
349370 assert(r.random.intRangeAtMost(i8, -0x80, -1) == -1);
350371
351 r.next_value = 0xff;
352 assert(r.random.intRangeAtMost(i64, -0x8000000000000000, -1) == -1);
353372 r.next_value = 0xff;
354373 assert(r.random.intRangeAtMost(i3, -4, -1) == -1);
355374 r.next_value = 0xff;
356375 assert(r.random.intRangeAtMost(i3, -2, 1) == 1);
357
358 // test retrying and eventually getting a good value
359 // start just out of bounds
360 r.next_value = 0x81;
361 assert(r.random.uintAtMost(u8, 0x80) == 0);
362376}
363377
364378// Generator to extend 64-bit seed values into longer sequences.