小编典典

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

all

我看到这个问题被问了很多,但从未见过真正具体的答案。所以我将在这里发布一篇文章,希望能帮助人们理解为什么在使用随机数生成器时会出现“模偏差”,比如rand()
C++ 中。


阅读 105

收藏
2022-04-15

共1个答案

小编典典

rand()一个伪随机数生成器也是如此,它在 0 和 之间选择一个自然数RAND_MAX,这是一个定义在
中的常数cstdlib(请参阅这篇文章以获得关于
的一般概述rand())。

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

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

rand()返回 1、4、7 或 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_MAX/n调用。rand()

一种更有效的公式方法是取一些长度可被 整除的大范围n,例如RAND_MAX - RAND_MAX % n,不断生成随机数,直到得到一个位于该范围内的随机数,然后取模:

int x;

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

x %= n;

对于较小的值n,这很少需要多次调用rand().

2022-04-15