为什么人们说在使用随机数发生器时存在模数偏差?
我曾经看到过很多这个问题,但从来没有见过这个问题的具体答案。 所以我打算在这里发布一个,希望能够帮助人们理解为什么在使用随机数生成器时为什么会出现“模偏差”,如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?