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

我曾经看到过很多这个问题,但从来没有见过这个问题的具体答案。 所以我打算在这里发布一个,希望能够帮助人们理解为什么在使用随机数生成器时为什么会出现“模偏差”,如C ++中的rand()


所以rand()是一个伪随机数发生器,它选择一个介于0和RAND_MAX之间的自然数, RAND_MAX是一个在cstdlib定义的常量(请参阅本文以获取关于rand()的一般概述)。

现在如果你想在0和2之间生成一个随机数,会发生什么? 为了说明起见,假设RAND_MAX是10,我决定通过调用rand()%3来生成一个介于0和2之间的随机数。 但是, rand()%3不会以相等的概率产生0到2之间的数字!

rand()返回rand()或9时, rand()%3 == 0 。 因此,P(0)= 4/11

rand()返回rand()或10时, rand()%3 == 1 。 因此,P(1)= 4/11

rand()返回2,5或8时, rand()%3 == 2 。 因此,P(2)= 3/11

这不会以相等的概率产生0到2之间的数字。 当然,对于小范围来说,这可能不是最大的问题,但是对于更大的范围来说,这可能会扭曲分布,偏向更小的数字。

那么rand()%n何时以相等的概率返回从0到n-1的数字范围? 当RAND_MAX%n == n - 1 。 在这种情况下,与我们之前的假设一样, rand()确实以相等的概率返回0到RAND_MAX之间的数字,n的模数类也将是平均分布的。

那么我们如何解决这个问题呢? 粗略的方法是继续生成随机数字,直到获得所需范围内的数字:

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

但对于n低值,这样做效率不高,因为您只有n/RAND_MAX在您的范围内获得值的机会,因此您需要平均对rand()执行RAND_MAX/n调用。

一个更有效的公式方法是采用一个长度可被n整除的大范围,比如RAND_MAX - RAND_MAX % n ,继续生成随机数,直到得到一个位于范围内的随机数,然后取模:

int x;

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

x %= n;

对于n小值,这很少需要对rand()进行多次调用。


作品引用和进一步阅读:

  • CPlusPlus参考

  • 永远Confuzzled



  • 保持选择随机是消除偏见的好方法。

    更新

    如果我们搜索一个可被n整除的x,我们可以使代码更快。

    // 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;
    

    上面的循环应该非常快,平均说1次迭代。


    @ user1413793对于这个问题是正确的。 我不打算进一步讨论,除了要说一点:是的,对于小的n值和RAND_MAX大值,模数偏差可能非常小。 但是使用偏差诱导模式意味着每次计算随机数时必须考虑偏差,并针对不同情况选择不同的模式。 如果你做出错误的选择,它引入的错误是微妙的,单元测试几乎是不可能的。 与只使用适当的工具(比如arc4random_uniform )相比,这是额外的工作,而不是更少的工作。 做更多的工作并获得更糟的解决方案是糟糕的工程,特别是在大多数平台上,每次都很容易。

    不幸的是,解决方案的实现都不正确或效率低于他们应该。 (每个解决方案都有各种解释问题的注释,但没有一个解决方案已经解决了这些问题。)这可能会让偶然的答案搜索者感到困惑,所以我在这里提供了一个已知好的实现。

    同样,最好的解决方案就是在提供它的平台上使用arc4random_uniform ,或者为您的平台使用类似的范围解决方案(例如Java上的Random.nextInt )。 它将为您做任何正确的事情,而无需代码费用。 这几乎总是正确的要求。

    如果你没有arc4random_uniform ,那么你可以使用opensource的力量来查看它是如何在更宽范围的RNG之上实现的(在这种情况下是ar4random ,但类似的方法也可以在其他RNG上运行) 。

    这是OpenBSD的实现:

    /*
     * 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;
    }
    

    值得注意的是,对于那些需要实现类似事情的人来说,这个代码的最新提交评论是:

    更改arc4random_uniform()以计算2**32 % upper_bound'' as -upper_bound%upper_bound''。 简化代码并使其在ILP32和LP64体系结构上保持不变,并且在LP64体系结构上使用32位余数而不是64位余数稍快一些。

    由Jorden Verwer在tech @ ok deraadt指出; 没有djm或otto的反对意见

    Java实现也很容易找到(请参阅上一个链接):

    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/58285.html

    上一篇: Why do people say there is modulo bias when using a random number generator?

    下一篇: Calculate modulo in sh script