authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2018-11-21 18:24:14-05:00
committergravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2018-11-24 22:25:21-05:00
log49b49618d2db657b22c24a4c78d6516df0fd7e45
tree15ec821c6fec6f09bfe93a52a6bee92e031c926e
parent1924ffa67d90f21852680b7c9a09df07fc218ebe

add biased random range api


1 files changed, 68 insertions(+), 8 deletions(-)

std/rand/index.zig+68-8
......@@ -57,6 +57,23 @@ pub const Random = struct {
5757 return @bitCast(T, unsigned_result);
5858 }
5959
60 /// Constant-time implementation off ::uintLessThan.
61 /// The results of this function may be biased.
62 pub fn uintLessThanBiased(r: *Random, comptime T: type, less_than: T) T {
63 assert(T.is_signed == false);
64 assert(0 < less_than);
65 // Small is typically u32
66 const Small = @IntType(false, @divTrunc(T.bit_count + 31, 32) * 32);
67 // Large is typically u64
68 const Large = @IntType(false, Small.bit_count * 2);
69
70 // adapted from:
71 // http://www.pcg-random.org/posts/bounded-rands.html
72 // "Integer Multiplication (Biased)"
73 var x: Small = r.int(Small);
74 var m: Large = Large(x) * Large(less_than);
75 return @intCast(T, m >> Small.bit_count);
76 }
6077 /// Returns an evenly distributed random unsigned integer `0 <= i < less_than`.
6178 /// This function assumes that the underlying ::fillFn produces evenly distributed values.
6279 /// Within this assumption, the runtime of this function is exponentially distributed.
......@@ -64,8 +81,7 @@ pub const Random = struct {
6481 /// the runtime of this function would technically be unbounded.
6582 /// However, if ::fillFn is backed by any evenly distributed pseudo random number generator,
6683 /// this function is guaranteed to return.
67 /// If you need deterministic runtime bounds, consider instead using `r.int(T) % less_than`,
68 /// which will usually be biased toward smaller values.
84 /// If you need deterministic runtime bounds, use `::uintLessThanBiased`.
6985 pub fn uintLessThan(r: *Random, comptime T: type, less_than: T) T {
7086 assert(T.is_signed == false);
7187 assert(0 < less_than);
......@@ -101,6 +117,16 @@ pub const Random = struct {
101117 return @intCast(T, m >> Small.bit_count);
102118 }
103119
120 /// Constant-time implementation off ::uintAtMost.
121 /// The results of this function may be biased.
122 pub fn uintAtMostBiased(r: *Random, comptime T: type, at_most: T) T {
123 assert(T.is_signed == false);
124 if (at_most == maxInt(T)) {
125 // have the full range
126 return r.int(T);
127 }
128 return r.uintLessThanBiased(T, at_most + 1);
129 }
104130 /// Returns an evenly distributed random unsigned integer `0 <= i <= at_most`.
105131 /// See ::uintLessThan, which this function uses in most cases,
106132 /// for commentary on the runtime of this function.
......@@ -113,6 +139,22 @@ pub const Random = struct {
113139 return r.uintLessThan(T, at_most + 1);
114140 }
115141
142 /// Constant-time implementation off ::intRangeLessThan.
143 /// The results of this function may be biased.
144 pub fn intRangeLessThanBiased(r: *Random, comptime T: type, at_least: T, less_than: T) T {
145 assert(at_least < less_than);
146 if (T.is_signed) {
147 // Two's complement makes this math pretty easy.
148 const UnsignedT = @IntType(false, T.bit_count);
149 const lo = @bitCast(UnsignedT, at_least);
150 const hi = @bitCast(UnsignedT, less_than);
151 const result = lo +% r.uintLessThanBiased(UnsignedT, hi -% lo);
152 return @bitCast(T, result);
153 } else {
154 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
155 return at_least + r.uintLessThanBiased(T, less_than - at_least);
156 }
157 }
116158 /// Returns an evenly distributed random integer `at_least <= i < less_than`.
117159 /// See ::uintLessThan, which this function uses in most cases,
118160 /// for commentary on the runtime of this function.
......@@ -131,6 +173,22 @@ pub const Random = struct {
131173 }
132174 }
133175
176 /// Constant-time implementation off ::intRangeAtMostBiased.
177 /// The results of this function may be biased.
178 pub fn intRangeAtMostBiased(r: *Random, comptime T: type, at_least: T, at_most: T) T {
179 assert(at_least <= at_most);
180 if (T.is_signed) {
181 // Two's complement makes this math pretty easy.
182 const UnsignedT = @IntType(false, T.bit_count);
183 const lo = @bitCast(UnsignedT, at_least);
184 const hi = @bitCast(UnsignedT, at_most);
185 const result = lo +% r.uintAtMostBiased(UnsignedT, hi -% lo);
186 return @bitCast(T, result);
187 } else {
188 // The signed implementation would work fine, but we can use stricter arithmetic operators here.
189 return at_least + r.uintAtMostBiased(T, at_most - at_least);
190 }
191 }
134192 /// Returns an evenly distributed random integer `at_least <= i <= at_most`.
135193 /// See ::uintLessThan, which this function uses in most cases,
136194 /// for commentary on the runtime of this function.
......@@ -149,15 +207,11 @@ pub const Random = struct {
149207 }
150208 }
151209
152 /// Return a random integer/boolean type.
153210 /// TODO: deprecated. use ::boolean or ::int instead.
154211 pub fn scalar(r: *Random, comptime T: type) T {
155 if (T == bool) return r.boolean();
156 return r.int(T);
212 return if (T == bool) r.boolean() else r.int(T);
157213 }
158214
159 /// Return a random integer with even distribution between `start`
160 /// inclusive and `end` exclusive. `start` must be less than `end`.
161215 /// TODO: deprecated. renamed to ::intRangeLessThan
162216 pub fn range(r: *Random, comptime T: type, start: T, end: T) T {
163217 return r.intRangeLessThan(T, start, end);
......@@ -373,6 +427,8 @@ fn testRandomIntAtMost() void {
373427 assert(r.random.intRangeAtMost(i3, -4, -1) == -1);
374428 r.next_value = 0xff;
375429 assert(r.random.intRangeAtMost(i3, -2, 1) == 1);
430
431 assert(r.random.uintAtMost(u0, 0) == 0);
376432}
377433
378434// Generator to extend 64-bit seed values into longer sequences.
......@@ -884,12 +940,16 @@ test "Random range" {
884940}
885941
886942fn testRange(r: *Random, start: i8, end: i8) void {
943 testRangeBias(r, start, end, true);
944 testRangeBias(r, start, end, false);
945}
946fn testRangeBias(r: *Random, start: i8, end: i8, biased: bool) void {
887947 const count = @intCast(usize, i32(end) - i32(start));
888948 var values_buffer = []bool{false} ** 0x100;
889949 const values = values_buffer[0..count];
890950 var i: usize = 0;
891951 while (i < count) {
892 const value: i32 = r.intRangeLessThan(i8, start, end);
952 const value: i32 = if (biased) r.intRangeLessThanBiased(i8, start, end) else r.intRangeLessThan(i8, start, end);
893953 const index = @intCast(usize, value - start);
894954 if (!values[index]) {
895955 i += 1;