从各种其他SO问题中读取,当使用rand()%N时,您可能会修改您获得的伪数的偏差,因此您通常需要引入一些范围处理。
但是在所有情况下总是提到rand(),而不是较新的random()或arcrandom4()函数或本机C ++ 11方法。当你在一组上运行这些例程时会发生什么?你有像rand()那样的偏见吗?
感谢。
答案 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
。让我们分解下一步可能发生的可能性:
这意味着两种方式最终得到的数字从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)引擎,您可以找到适合您应用的最佳引擎。
中新的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)
是模运算中的除数。下图显示了我们的观点:
我们可以看到范围[ 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]
结果:
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]
结果:
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
下图有助于了解这一结果。