libc随机数生成器有缺陷?

时间:2013-02-04 00:25:07

标签: c algorithm math random glibc

考虑一种算法来测试在特定次数的尝试之后从一组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)。

分发:enter image description here

差异:enter image description here

3 个答案:

答案 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=0xi-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.)

statei = statei-31 ⊕ statei-3

rndi = (statei - (statei mod 2)) / 2

Now, note that:

(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

If i and j are uniformly distributed, the first case will occur 75% of the time, and the second case 25%.

So, by substitution in the generation formula:

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

The two cases can be further reduced to:

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

相关问题