1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// [What follows is another outstanding comment from Jim Blandy explaining why
// this technique works.]
//
// This comment should just read, "Generate skip counts with a geometric
// distribution", and leave everyone to go look that up and see why it's the
// right thing to do, if they don't know already.
//
// BUT IF YOU'RE CURIOUS, COMMENTS ARE FREE...
//
// Instead of generating a fresh random number for every trial, we can
// randomly generate a count of how many times we should return false before
// the next time we return true. We call this a "skip count". Once we've
// returned true, we generate a fresh skip count, and begin counting down
// again.
//
// Here's an awesome fact: by exercising a little care in the way we generate
// skip counts, we can produce results indistinguishable from those we would
// get "rolling the dice" afresh for every trial.
//
// In short, skip counts in Bernoulli trials of probability `P` obey a geometric
// distribution. If a random variable `X` is uniformly distributed from
// `[0..1)`, then `floor(log(X) / log(1-P))` has the appropriate geometric
// distribution for the skip counts.
//
// Why that formula?
//
// Suppose we're to return `true` with some probability `P`, say, `0.3`. Spread
// all possible futures along a line segment of length `1`. In portion `P` of
// those cases, we'll return true on the next call to `trial`; the skip count is
// 0. For the remaining portion `1-P` of cases, the skip count is `1` or more.
//
// ```
// skip: 0 1 or more
// |------------------^-----------------------------------------|
// portion: 0.3 0.7
// P 1-P
// ```
//
// But the "1 or more" section of the line is subdivided the same way: *within
// that section*, in portion `P` the second call to `trial()` returns `true`, and
// in portion `1-P` it returns `false` a second time; the skip count is two or
// more. So we return `true` on the second call in proportion `0.7 * 0.3`, and
// skip at least the first two in proportion `0.7 * 0.7`.
//
// ```
// skip: 0 1 2 or more
// |------------------^------------^----------------------------|
// portion: 0.3 0.7 * 0.3 0.7 * 0.7
// P (1-P)*P (1-P)^2
// ```
//
// We can continue to subdivide:
//
// ```
// skip >= 0: |------------------------------------------------- (1-P)^0 --|
// skip >= 1: | ------------------------------- (1-P)^1 --|
// skip >= 2: | ------------------ (1-P)^2 --|
// skip >= 3: | ^ ---------- (1-P)^3 --|
// skip >= 4: | . --- (1-P)^4 --|
// .
// ^X, see below
// ```
//
// In other words, the likelihood of the next `n` calls to `trial` returning
// `false` is `(1-P)^n`. The longer a run we require, the more the likelihood
// drops. Further calls may return `false` too, but this is the probability
// we'll skip at least `n`.
//
// This is interesting, because we can pick a point along this line segment and
// see which skip count's range it falls within; the point `X` above, for
// example, is within the ">= 2" range, but not within the ">= 3" range, so it
// designates a skip count of `2`. So if we pick points on the line at random
// and use the skip counts they fall under, that will be indistinguishable from
// generating a fresh random number between `0` and `1` for each trial and
// comparing it to `P`.
//
// So to find the skip count for a point `X`, we must ask: To what whole power
// must we raise `1-P` such that we include `X`, but the next power would
// exclude it? This is exactly `floor(log(X) / log(1-P))`.
//
// Our algorithm is then, simply: When constructed, compute an initial skip
// count. Return `false` from `trial` that many times, and then compute a new
// skip count.
//
// For a call to `multi_trial(n)`, if the skip count is greater than `n`, return
// `false` and subtract `n` from the skip count. If the skip count is less than
// `n`, return true and compute a new skip count. Since each trial is
// independent, it doesn't matter by how much `n` overshoots the skip count; we
// can actually compute a new skip count at *any* time without affecting the
// distribution. This is really beautiful.
use Rng;
/// Fast Bernoulli sampling: each event has equal probability of being sampled.
///
/// See the [crate-level documentation][crate] for more general
/// information.
///
/// # Example
///
/// ```
/// use fast_bernoulli::FastBernoulli;
/// use rand::Rng;
///
/// // Get the thread-local random number generator.
/// let mut rng = rand::thread_rng();
///
/// // Create a `FastBernoulli` instance that samples events with probability 1/20.
/// let mut bernoulli = FastBernoulli::new(0.05, &mut rng);
///
/// // Each time your event occurs, perform a Bernoulli trail to determine whether
/// // you should sample the event or not.
/// let on_my_event = || {
/// if bernoulli.trial(&mut rng) {
/// // Record the sample...
/// }
/// };
/// ```