在一个范围内随机,是否为新的rand()版本存在数字偏差?

时间:2014-06-05 18:42:27

标签: c++ math random distribution modulo

从各种其他SO问题中读取,当使用rand()%N时,您可能会修改您获得的伪数的偏差,因此您通常需要引入一些范围处理。

但是在所有情况下总是提到rand(),而不是较新的random()或arcrandom4()函数或本机C ++ 11方法。当你在一组上运行这些例程时会发生什么?你有像rand()那样的偏见吗?

感谢。

3 个答案:

答案 0 :(得分:6)

以下答案与Eric Lippert's blog post on the same topic的详细内容不同。此外,this question and its answers处理相同的主题。

来自rand() % N部分的大部分偏见都来自rand()部分 - 来自% N部分。

让我们考虑好的' rand()的实现,生成0到100之间的所有数字(为简单起见),概率相等 - 均匀分布。接下来让我们说我们想使用rand()的这个实现来生成0到80之间的随机数,所以我们做rand() % 80。让我们分解下一步可能发生的可能性:

  1. rand()生成一个0到79之间的数字。0到79%之间的任何数字80保持相同的数字
  2. rand()生成一个从80到100的数字.80到100%的任何数字80都会转换为0到20
  3. 这意味着两种方式最终得到的数字从0到20,但只有单向才能得到21到79之间的数字。从0到20的数字更可能比从21到79的数字。这通常不是理想的属性。

    将N均值划分为rand()的最大值的任何N值都不会出现此问题,因为生成任何值的方法都相同。此外,对于小的N值,偏差要小于接近rand()的最大值的N的值。

    那么,rand()以外的函数呢?如果它们从某个固定范围返回值并且你进行mod操作,它们将遭受相同的偏差。如果您正在调用以范围作为参数的随机函数,那么您不需要执行mod操作。该函数可能会在内部处理任何偏差。

答案 1 :(得分:3)

C ++ 11通过添加替代随机生成器引擎解决了这个问题。

使用%(modulo)将随机数约束到某个范围的原因很糟糕,与偏差无关,更多地与rand()(线性同余生成器(LCG))的典型实现有关。大多数语言运行时使用LCG作为随机函数;只有最近设计的语言往往不同。

LCG只是乘法和加法(模数通常通过整数的最大大小来实现)。显而易见的是,这种序列的低位遵循规则模式 - 乘法不会将较高位混合到较低位中,并且add会在每次迭代时以恒定方式改变低位。

通过了解不同的随机生成器(linear_congruential_engine,mersenne_twister_engine,subtract_with_carry_engine)引擎,您可以找到适合您应用的最佳引擎。

Random Engines in c++11

中新的c ++实现有很好的参考

正如@dpy所述std :: uniform_int_distribution是c ++给出的随机分布选项。它即使是随机发电机引擎也会处理偏差问题。但是如果你设置一个范围从1到19并使用%操作将它存储在一个15大小的数组中,则会重新引入偏差问题,如此处的许多帖子所述。

答案 2 :(得分:3)

  

当您在一组上运行这些例程时会发生什么?你有偏见吗?   喜欢rand()?

答案是:这取决于生成器返回的范围大小与模运算中的除数之间的关系。如果除数不能均匀地划分范围,则分布将会发生偏差。偏差比在[1,2]范围内,其中1表示无偏差(均匀分布),偏差随着除数增加。关于arcrandom4(),当模数除数不是2 ^ 32的偶数除数时,这转化为在所有情况下获得的偏态分布。其背后的基本原理解释如下。


简介。偏见

想象一下,我们正在尝试使用

模拟区间[0,99]上的均匀int分布
int x = rand() % 100;

运算符%使X的概率分布偏斜,因为作为rand()的最大值的RAND_MAX可能不等于k * 100 + 99.这导致如果您想象0-RAND_MAX范围的所有100个长度部分然后你可以看到最后一部分可能不会产生0-99的全范围。因此,您有更多的数字生成0,1,2 ......,p但不是必需的p + 1,...,98,99(0,1,2,...,p中每个数字多出1次) )。这种方法的不准确性随着除数的增加而增加,即不均匀划分范围,与均匀分布相比,最大偏差等于2.

在下面的以下部分中,我们显示偏差测量为从[0,p]得到的概率与从[p + 1,n]得到的概率的概率之比等于(k + 1 )/ k 我们用2个例子证实了这一点。


我们将展示操作模数引入的偏差究竟是什么(应用于均匀分布的发生器以调整输出范围的操作)。我们将按公式进行操作

x = rand() % ( n + 1)

其中rand()是某个生成器,( n + 1)是模运算中的除数。下图显示了我们的观点:

enter image description here

我们可以看到范围[ 0, n]中的数字如何分为重复k + 1次(数字[ 0, p])的数字以及重复k次的数字{{1}在单个试验中,“从[ p + 1, n]获得的分布中获取数字”。当将生成器给出的最大数量(即Rand_MAX)除以期望范围的大小(n + 1)时, p 被定义为余数:

p =(N-1)%(n + 1)

N - 1 = k *(n + 1)+ p

k 是商

k =(N-1-p)/(n + 1)

在一次试验中有

(p + 1)*(k + 1)+(n-p)* k =

= p + 1 + k(n + 1)= N

可能的结果。因此,接收重复k次的元素的概率是k / N.让我们表示

f_0 =(k + 1)/ N,来自[0,p]

的每个元素的概率

f_1 = k / N,来自[p + 1,n]的每个元素的概率

假设我们将表示采样的偏差,在均匀分布上的变换分布,作为属于x = rand() % (n+1)的元素概率与元素概率的比率。范围[ 0, p]

偏见= f_0 / f_1 =(k + 1)/ k

那么,数字是两倍吗?

没有。事实上,当我们查看图片数字重复时并不意味着比率为2.这个比率只是一个特例,如果发生器的范围被分成恰好2个子范围。一般来说,偏差比为(k + 1)/ k并且渐近地减小,当除数n + 1趋于1时,(并且k倾向于N)。


实施例

我们现在考虑两个简单的例子(如@dyp所示)。首先,我们将从

给出的分布中生成1000 * 1000个样本

x = rand()%m

生成器为[ p + 1, n],除数m = n + 1等于15,下一个等于6。

示例1

std::uniform_int_distribution<> dist(0, 19)

测试程序是:

int x = rand() % 15; // n + 1 = 15, rand is uniform distribution over [0,19]

code

结果:

0:100500 1:100016 2:99724 3:99871 4:99936 5:50008 6:49762 7:50023 8:50123 9:49963 10:50117 11:50049 12:49885 13:49760 14:50263

我们可以看到,在这种情况下,范围[0,p] = [0,4]中的数字大约是其余数字的两倍。这符合我们的偏见公式

bias = f_0 / f_1 =(k + 1)/ k = 2/1

示例2

#include <iostream>
#include <random>
#include <vector>

int main()
{
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<> dist(0, 19);
    std::vector<int> v(15);
    const int runs = 1000 * 1000;
    for (int i = 0; i < runs; ++i)
    {
        ++v[dist(mt) % v.size()];
    }

    for (int i = 0; i < v.size(); ++i)
    {
        std::cout << i << ": " << v[i] << "\n";
    }
}

测试程序是:

int x = rand() % 6; // n + 1 = 6, rand is uniform distribution over [0,19]

code

结果:

0:199875 1:199642 2:149852 3:149789 4:150237 5:150605

在这种情况下,我们观察到范围[0,p] = [0,1]中的数字看起来不是其余数字的两倍,但比例约为20/15。实际上这是4/3,因为我们的偏差公式在这种情况下是

bias = f_0 / f_1 =(k + 1)/ k = 4/3

下图有助于了解这一结果。

enter image description here

full code