|
EXCURSION 1 Effective Java Programming Language Guide by Joshua Bloch |
|
Effective Java Item 30 ("Know and use the libraries") begins with a broken random number generation idiom (page 145):
private static Random rnd = new Random();
// Common but flawed!
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
The item goes on to note that if this method is invoked repeatedly
with
Suppose you you like three flavors of ice cream equally well, say vanilla, chocolate, and strawberry. You want to choose a flavor at random by flipping coins. Unfortunately, there's no way to do this in any finite number of coin flips. If you flip a coin n times, there are 2n possible outcomes, which isn't divisible by three. To make this concrete, consider the following naive algorithm:
Of course this doesn't work. You'll end up choosing vanilla twice as often as either of the other flavors, as two of the four possible outcomes (TT and HH) result in vanilla.
It turns out that the broken idiom above uses
essentially the same algorithm! It flips 31 "coins" (the bits of
The number of results (or modulus),
To make this concrete, here's how it looks with a 4-bit random number and a modulus of
| Entire Range | Half of Range | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Outcomes | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 |
| Result | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 | 4 | 5 |
The picture for a 31-bit word and a modulus of
So how do you fix the problem? Let's go back to the ice cream example. While you can't choose fairly from among three flavors in any finite number of coin tosses, you can choose fairly if you're willing to tolerate the possibility of arbitrarily many coin tosses. Here is a simple algorithm that works:
This is a probabilistic, potentially nonterminating algorithm that terminates with probability 1. The expected number of times that you'll have to do step 1 is 1 + 1/4 + 1/42 + 1/43 ... = 1.33, which isn't too bad.
Believe it or not, this is essentially the algorithm that Random.nextInt(int) implements. Here's the actual code, which is a bit tricky:
public int nextInt(int n) {
if (n <= 0)
throw new IllegalArgumentException(n + " is not positive");
if ((n & -n) == n) // i.e., n is a power of 2
return (int)((n * (long)next(31)) >> 31);
int bits, val;
do {
bits = next(31);
val = bits % n;
} while(bits - val + (n - 1) < 0);
return val;
}
The if statement after the validity check solves an unrelated
problem with the idiom: The low order
bits of numbers returned by linear congruential pseudorandom number
generators such as the one implemented by
The do-while loop implements exactly the "revised ice cream choosing algorithm" shown above. It generates a random outcome between 0 and
231 - 1 and then checks to see if the outcome falls in a "partial copy" of the range implied by the modulus. If it does, the value is
rejected and the procedure repeated until an acceptable value is generated.
The probability of a value being rejected depends on n. The worst
case occurs when
Another bit of cleverness in this code is the loop termination test,
while (bits - val + (n - 1) < 0);
which determines whether the outcome fell in a partial range copy. The naive
test requires a multiply, which is relatively expensive; this test,
suggested by John Rose, does not. It subtracts the result
(val) from the outcome (bits) to calculate the start of
the "range copy" in which the result falls. Then, by adding
n - 1, it generates the last number in this range copy.
If the outcome fell in a "partial range copy", the addition will overflow,
resulting in a negative number. This causes the program to reject the
outcome and try again.
Finally, note that the
To reiterate the main point of Item 30, the code for