考虑一种算法来测试在特定次数的尝试之后从一组N个唯一数字中挑选某个数字的概率(例如,N = 2,轮盘中的概率是多少(没有0) X试图让布莱克获胜?)。
正确的分布是pow(1-1 / N,X-1)*(1 / N)。
但是,当我使用以下代码测试时,X = 31处始终存在深沟,独立于N,并且独立于种子。
这是一个由于PRNG的实施细节而无法防止的内在缺陷,这是一个真正的错误,还是我忽略了一些明显的东西?
// C
#include <sys/times.h>
#include <math.h>
#include <stdio.h>
int array[101];
void main(){
int nsamples=10000000;
double breakVal,diffVal;
int i,cnt;
// seed, but doesn't change anything
struct tms time;
srandom(times(&time));
// sample
for(i=0;i<nsamples;i++){
cnt=1;
do{
if((random()%36)==0) // break if 0 is chosen
break;
cnt++;
}while(cnt<100);
array[cnt]++;
}
// show distribution
for(i=1;i<100;i++){
breakVal=array[i]/(double)nsamples; // normalize
diffVal=breakVal-pow(1-1/36.,i-1)*1/36.; // difference to expected value
printf("%d %.12g %.12g\n",i,breakVal,diffVal);
}
}
使用libc6软件包2.15-0ubuntu20和Intel Core i5-2500 SandyBridge测试了最新的Xubuntu 12.10,但几年前我在一台旧的Ubuntu机器上发现了这一点。
我也在Windows 7上使用Unity3D / Mono进行了测试(虽然不确定哪个Mono版本),这里使用System.Random时,X = 55时沟渠发生,而Unity内置的Unity.Random没有可见的沟渠(至少不是X&lt; 100)。
分发:
差异:
答案 0 :(得分:10)
这是由于glibc的random()
函数不够随机。根据{{3}},对于random()
返回的随机数,我们有:
oi = (oi-3 + oi-31) % 2^31
或:
oi = (oi-3 + oi-31 + 1) % 2^31
。
现在取xi = oi % 36
,并假设上面的第一个等式是使用的等式(每个数字的概率为50%)。现在,如果xi-31=0
和xi-3!=0
,则xi=0
小于1/36的可能性。这是因为50%的时间oi-31 + oi-3
将小于2 ^ 31,当发生这种情况时,
xi = oi % 36 = (oi-3 + oi-31) % 36 = oi-3 % 36 = xi-3
,
非零。这样就可以在0样本后看到31个样本。
答案 1 :(得分:7)
在这个实验中测量的是伯努利实验成功试验之间的间隔,其中成功被定义为random() mod k == 0
k
(OP中为36)。不幸的是,random()
的实施意味着伯努利试验在统计上并不独立。
我们会为`random()'的rndi
输出写ith
,我们注意到:
rndi = rndi-31 + rndi-3
,概率为0.75
rndi = rndi-31 + rndi-3 + 1
,概率为0.25
(见下面的校对大纲。)
我们假设rndi-31 mod k == 0
,我们目前正在关注rndi
。然后必须是rndi-3 mod k ≠ 0
的情况,因为否则我们会将周期计为长度k-3
。
但(大部分时间)(mod k): rndi = rndi-31 + rndi-3 = rndi-3 ≠ 0
。
因此,目前的试验在统计学上并不依赖于以前的试验,成功后的31 st 试验成功的可能性要小于无偏见的伯努利试验系列。
使用线性同余生成器的常用建议(实际上并不适用于random()
算法)是使用高阶位而不是低阶位,因为高阶位是“更随机”(即与连续值的相关性更小)。但在这种情况下,这也不会起作用,因为上述身份同样适用于函数high log k bits
和函数mod k == low log k bits
。
事实上,我们可能期望线性同余生成器更好地工作,特别是如果我们使用输出的高阶位,因为尽管LCG在蒙特卡罗模拟中不是特别好,但它不会受到影响。 random()
的线性反馈。
random
算法,默认情况下:
让state
成为无符号长整数的向量。使用种子,一些固定值和混合算法初始化state0...state30
。为简单起见,我们可以认为状态向量是无限的,尽管只使用了最后的31个值,所以它实际上是作为环形缓冲区实现的。
生成rndi: (Note:
⊕
is addition mod 232.)
⊕
Now, note that: If So, by substitution in the generation formula: The two cases can be further reduced to:
statei = statei-31 ⊕ statei-3
rndi = (statei - (statei mod 2)) / 2
(i + j) mod 2 = i mod 2 + j mod 2
if i mod 2 == 0
or j mod 2 == 0
(i + j) mod 2 = i mod 2 + j mod 2 - 2
if i mod 2 == 1
and j mod 2 == 1
i
and j
are uniformly distributed, the first case will occur 75% of the time, and the second case 25%.rndi = (statei-31 ⊕ statei-3 - ((statei-31 + statei-3) mod 2)) / 2
= ((statei-31 - (statei-31 mod 2)) ⊕ (statei-3 - (statei-3 mod 2))) / 2
or = ((statei-31 - (statei-31 mod 2)) ⊕ (statei-3 - (statei-3 mod 2)) + 2) / 2
rndi = rndi-31 ⊕ rndi-3
如上所述,第一种情况发生在75%的时间,假设rnd i-31 和rnd i-3 是从均匀分布中独立绘制的(他们不是,但这是一个合理的第一近似值。)
答案 2 :(得分:1)
正如其他人指出的那样,random()
不够随意。
在这种情况下,使用较高位而不是较低位无效。根据手册(man 3 rand
),rand()
的旧实现在较低位中存在问题。这就是为什么推荐random()
的原因。但是,rand()
的当前实现使用与random()
相同的生成器。
我尝试了旧rand()
:
if ((int)(rand()/(RAND_MAX+1.0)*36)==0)
......在X = 31
时得到了同样的深沟如果我将rand()
的数字与另一个序列混合,我会摆脱沟渠:
unsigned x=0;
//...
x = (179*x + 79) % 997;
if(((rand()+x)%36)==0)
我正在使用旧Linear Congruential Generator。我从素数表中随机选择了79,179和997。这应该生成长度为997的重复序列。
那就是说,这个技巧可能会引入一些非随机性,一些足迹......由此产生的混合序列肯定会失败其他统计测试。 x
在连续迭代中从不采用相同的值。实际上,重复每个值需要997次迭代。
''[..]不应使用随机选择的方法生成随机数。应该使用一些理论。“(D.E.Knuth,”计算机程序设计的艺术“,第2卷)
对于模拟,如果您想确定,请使用Mersenne Twister