我看到这个问题被问了很多,但从未见过真正具体的答案。所以我将在这里发布一篇文章,希望能帮助人们理解为什么在使用随机数生成器时会出现“模偏差”,比如rand()在 C++ 中。
rand()
rand()一个伪随机数生成器也是如此,它在 0 和 之间选择一个自然数RAND_MAX,这是一个定义在 中的常数cstdlib(请参阅这篇文章以获得关于 的一般概述rand())。
RAND_MAX
cstdlib
现在如果你想生成一个介于 0 和 2 之间的随机数会发生什么?为了解释起见,假设RAND_MAX是 10,我决定通过调用来生成 0 到 2 之间的随机数rand()%3。但是,rand()%3不会以相等的概率产生 0 和 2 之间的数字!
rand()%3
当rand()返回 0、3、6 或 9 时, rand()%3 == 0 . 因此,P(0) = 4/11
rand()%3 == 0
当rand()返回 1、4、7 或 10 时, rand()%3 == 1 . 因此,P(1) = 4/11
rand()%3 == 1
当rand()返回 2、5 或 8 时, rand()%3 == 2 . 因此,P(2) = 3/11
rand()%3 == 2
这不会以相等的概率生成 0 和 2 之间的数字。当然,对于较小的范围,这可能不是最大的问题,但对于较大的范围,这可能会扭曲分布,使较小的数字产生偏差。
那么什么时候rand()%n以相等的概率返回从 0 到 n-1 的数字范围呢?当RAND_MAX%n == n - 1. 在这种情况下,除了我们之前的假设rand()确实返回了一个介于 0 之间且RAND_MAX概率相等的数字,n 的模类也将均匀分布。
rand()%n
RAND_MAX%n == n - 1
那么我们如何解决这个问题呢?一种粗略的方法是不断生成随机数,直到获得所需范围内的数字:
int x; do { x = rand(); } while (x >= n);
但这对于 的低值是低效的n,因为您只有n/RAND_MAX机会获得范围内的值,因此您需要平均执行对 的RAND_MAX/n调用。rand()
n
n/RAND_MAX
RAND_MAX/n
一种更有效的公式方法是取一些长度可被 整除的大范围n,例如RAND_MAX - RAND_MAX % n,不断生成随机数,直到得到一个位于该范围内的随机数,然后取模:
RAND_MAX - RAND_MAX % n
int x; do { x = rand(); } while (x >= (RAND_MAX - RAND_MAX % n)); x %= n;
对于较小的值n,这很少需要多次调用rand().