Why do people say there is modulo bias when using a random number generator?

I have seen this question asked a lot but never seen a true concrete answer to it. So I am going to post one here which will hopefully help people understand why exactly there is "modulo bias" when using a random number generator, like rand() in C++.


So rand() is a pseudo-random number generator which chooses a natural number between 0 and RAND_MAX , which is a constant defined in cstdlib (see this article for a general overview on rand() ).

Now what happens if you want to generate a random number between say 0 and 2? For the sake of explanation, let's say RAND_MAX is 10 and I decide to generate a random number between 0 and 2 by calling rand()%3 . However, rand()%3 does not produce the numbers between 0 and 2 with equal probability!

When rand() returns 0, 3, 6, or 9, rand()%3 == 0 . Therefore, P(0) = 4/11

When rand() returns 1, 4, 7, or 10, rand()%3 == 1 . Therefore, P(1) = 4/11

When rand() returns 2, 5, or 8, rand()%3 == 2 . Therefore, P(2) = 3/11

This does not generate the numbers between 0 and 2 with equal probability. Of course for small ranges this might not be the biggest issue but for a larger range this could skew the distribution, biasing the smaller numbers.

So when does rand()%n return a range of numbers from 0 to n-1 with equal probability? When RAND_MAX%n == n - 1 . In this case, along with our earlier assumption rand() does return a number between 0 and RAND_MAX with equal probability, the modulo classes of n would also be equally distributed.

So how do we solve this problem? A crude way is to keep generating random numbers until you get a number in your desired range:

int x; 
do {
    x = rand();
} while (x >= n);

but that's inefficient for low values of n , since you only have a n/RAND_MAX chance of getting a value in your range, and so you'll need to perform RAND_MAX/n calls to rand() on average.

A more efficient formula approach would be to take some large range with a length divisible by n , like RAND_MAX - RAND_MAX % n , keep generating random numbers until you get one that lies in the range, and then take the modulus:

int x;

do {
    x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));

x %= n;

For small values of n , this will rarely require more than one call to rand() .


Works cited and further reading:

  • CPlusPlus Reference

  • Eternally Confuzzled



  • Keep selecting a random is a good way to remove the bias.

    Update

    We could make the code fast if we search for an x in range divisible by n .

    // Assumptions
    // rand() in [0, RAND_MAX]
    // n in (0, RAND_MAX]
    
    int x = rand();
    
    // Keep searching for an x in a range divisible by n 
    while (x >= RAND_MAX - (RAND_MAX % n)) {
      x = rand();
    }
    
    x %= n;
    

    The above loop should be very fast, say 1 iteration on average.


    @user1413793 is correct about the problem. I'm not going to discuss that further, except to make one point: yes, for small values of n and large values of RAND_MAX , the modulo bias can be very small. But using a bias-inducing pattern means that you must consider the bias every time you calculate a random number and choose different patterns for different cases. And if you make the wrong choice, the bugs it introduces are subtle and almost impossible to unit test. Compared to just using the proper tool (such as arc4random_uniform ), that's extra work, not less work. Doing more work and getting a worse solution is terrible engineering, especially when doing it right every time is easy on most platforms.

    Unfortunately, the implementations of the solution are all incorrect or less efficient than they should be. (Each solution has various comments explaining the problems, but none of the solutions have been fixed to address them.) This is likely to confuse the casual answer-seeker, so I'm providing a known-good implementation here.

    Again, the best solution is just to use arc4random_uniform on platforms that provide it, or a similar ranged solution for your platform (such as Random.nextInt on Java). It will do the right thing at no code cost to you. This is almost always the correct call to make.

    If you don't have arc4random_uniform , then you can use the power of opensource to see exactly how it is implemented on top of a wider-range RNG ( ar4random in this case, but a similar approach could also work on top of other RNGs).

    Here is the OpenBSD implementation:

    /*
     * Calculate a uniformly distributed random number less than upper_bound
     * avoiding "modulo bias".
     *
     * Uniformity is achieved by generating new random numbers until the one
     * returned is outside the range [0, 2**32 % upper_bound).  This
     * guarantees the selected random number will be inside
     * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
     * after reduction modulo upper_bound.
     */
    u_int32_t
    arc4random_uniform(u_int32_t upper_bound)
    {
        u_int32_t r, min;
    
        if (upper_bound < 2)
            return 0;
    
        /* 2**32 % x == (2**32 - x) % x */
        min = -upper_bound % upper_bound;
    
        /*
         * This could theoretically loop forever but each retry has
         * p > 0.5 (worst case, usually far better) of selecting a
         * number inside the range we need, so it should rarely need
         * to re-roll.
         */
        for (;;) {
            r = arc4random();
            if (r >= min)
                break;
        }
    
        return r % upper_bound;
    }
    

    It is worth noting the latest commit comment on this code for those who need to implement similar things:

    Change arc4random_uniform() to calculate 2**32 % upper_bound'' as -upper_bound % upper_bound''. Simplifies the code and makes it the same on both ILP32 and LP64 architectures, and also slightly faster on LP64 architectures by using a 32-bit remainder instead of a 64-bit remainder.

    Pointed out by Jorden Verwer on tech@ ok deraadt; no objections from djm or otto

    The Java implementation is also easily findable (see previous link):

    public int nextInt(int n) {
       if (n <= 0)
         throw new IllegalArgumentException("n must be 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;
     }
    
    链接地址: http://www.djcxy.com/p/58286.html

    上一篇: 模数运算与负数

    下一篇: 为什么人们说在使用随机数发生器时存在模数偏差?