【问题标题】:libc random number generator flawed?libc 随机数生成器有缺陷?
【发布时间】:2013-01-18 16:38:09
【问题描述】:

考虑一个算法来测试在特定次数的尝试后从一组 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 机器上发现了这一点。

我还使用 Unity3D/Mono 在 Windows 7 上对此进行了测试(但不确定是哪个 Mono 版本),这里使用 System.Random 时,沟渠发生在 X=55,而 Unity 的内置 Unity.Random 没有可见沟渠(至少对于 X

分布:

区别:

【问题讨论】:

  • 我认为没有人声称 glibc 中的随机函数特别“高质量”。如果您想要更好的东西,请使用 Mersenne Twister 或其他一些“专业级”RNG。 C 库 [和其他类似库] 提供的那个往往是为了简单而不是“完美”而编写的。
  • 1) main 应该返回 int 2) 模 36 是可疑的,我建议你先尝试模 32,或者另一个 2 的幂。
  • 我很确定 pgp/gpg [或任何其他不是由“奶酪”制成的加密机制] 不使用 libc 的算法,尽管我不得不承认我不了解这些特定工​​具的用途。
  • 除非你是developing for Plan 9,否则最好改掉写void main的习惯。
  • rand % N 有缺陷。我的建议是先使用适当的基于拒绝的方法,然后重新评估。

标签: c algorithm math random glibc


【解决方案1】:

这是由于 glibc 的 random() 函数不够随机。根据this page,对于random()返回的随机数,我们有:

o<sub>i</sub> = (o<sub>i-3</sub> + o<sub>i-31</sub>) % 2^31

或:

o<sub>i</sub> = (o<sub>i-3</sub> + o<sub>i-31</sub> + 1) % 2^31.

现在取x<sub>i</sub> = o<sub>i</sub> % 36,并假设上面的第一个等式是使用的那个(每个数字都有 50% 的机会发生这种情况)。现在如果x<sub>i-31</sub>=0x<sub>i-3</sub>!=0,那么x<sub>i</sub>=0 的机会小于1/36。这是因为 50% 的时间 o<sub>i-31</sub> + o<sub>i-3</sub> 会小于 2^31,而当这种情况发生时,

x<sub>i</sub> = o<sub>i</sub> % 36 = (o<sub>i-3</sub> + o<sub>i-31</sub>) % 36 = o<sub>i-3</sub> % 36 = x<sub>i-3</sub>,

这是非零。这会导致您在 0 样本之后看到 31 个样本的沟渠。

【讨论】:

  • 但这只是 31 岁的沟壑,而不是尖峰。另外,如果我通过使用例如使它们相对优质%49,沟还在。
  • @Wolfram:是的,我在帖子的结尾没有正确地思考,现在已修复。
【解决方案2】:

在此实验中测量的是伯努利实验的成功试验之间的间隔,其中成功定义为 random() mod k == 0 对于某些 k(OP 中的 36)。不幸的是,random() 的实现意味着伯努利试验在统计上不是独立的,这一事实破坏了这一事实。

我们将为 `random()' 的 i<sup>th</sup> 输出写入 rnd<sub>i</sub>,我们注意到:

rnd<sub>i</sub> = rnd<sub>i-31</sub> + rnd<sub>i-3</sub>     概率为 0.75

rnd<sub>i</sub> = rnd<sub>i-31</sub> + rnd<sub>i-3</sub> + 1 概率为 0.25

(参见下面的证明大纲。)

假设rnd<sub>i-31</sub> mod k == 0,我们目前正在查看rnd<sub>i</sub>。那么一定是rnd<sub>i-3</sub> mod k ≠ 0的情况,否则我们会把循环算作长度k-3

但是(大部分时间)(mod k): rnd<sub>i</sub> = rnd<sub>i-31</sub> + rnd<sub>i-3</sub> = rnd<sub>i-3</sub> ≠ 0

因此,目前的试验在统计上并不独立于之前的试验,成功后的第 31st 试验成功的可能性远低于伯努利试验的无偏见系列。 p>

使用线性同余生成器(实际上不适用于random() 算法)的通常建议是使用高位而不是低位,因为高位是“更多随机”(即与连续值的相关性较低)。但这在这种情况下也不起作用,因为上述恒等式同样适用于函数 high log k bits 和函数 mod k == low log k bits

事实上,我们可能期望线性同余生成器工作得更好,特别是如果我们使用输出的高阶位,因为虽然 LCG 在蒙特卡罗模拟方面不是特别好,但它不会受到random() 的线性反馈。


random算法,默认情况下:

state 是一个无符号长向量。使用种子、一些固定值和混合算法初始化state<sub>0</sub>...state<sub>30</sub>。为简单起见,我们可以认为状态向量是无限的,尽管只使用了最后 31 个值,因此它实际上是作为环形缓冲区实现的。

生成rnd<sub>i</sub>: (Note: is addition mod 232.)

state<sub>i</sub> = state<sub>i-31</sub> ⊕ state<sub>i-3</sub>

rnd<sub>i</sub> = (state<sub>i</sub> - (state<sub>i</sub> mod 2)) / 2

现在,请注意:

(i + j) mod 2 = i mod 2 + j mod 2    如果i mod 2 == 0j mod 2 == 0

(i + j) mod 2 = i mod 2 + j mod 2 - 2 如果i mod 2 == 1j mod 2 == 1

如果ij 是均匀分布的,则第一种情况发生的概率为 75%,第二种情况为 25%。

所以,通过代入公式:

rnd<sub>i</sub> = (state<sub>i-31</sub> ⊕ state<sub>i-3</sub> - ((state<sub>i-31</sub> + state<sub>i-3</sub>) mod 2)) / 2

     = ((state<sub>i-31</sub> - (state<sub>i-31</sub> mod 2)) ⊕ (state<sub>i-3</sub> - (state<sub>i-3</sub> mod 2))) / 2

     = ((state<sub>i-31</sub> - (state<sub>i-31</sub> mod 2)) ⊕ (state<sub>i-3</sub> - (state<sub>i-3</sub> mod 2)) + 2) / 2

这两种情况可以进一步简化为:

rnd<sub>i</sub> = rnd<sub>i-31</sub> ⊕ rnd<sub>i-3</sub>

rndi = rndi-31 ⊕ rndi-3 + 1

如上所述,假设 rndi-31 和 rndi-3 是从均匀分布中独立得出的(其中它们不是,但它是一个合理的第一近似值)。

【讨论】:

    【解决方案3】:

    正如其他人指出的那样,random() 不够随机。

    在这种情况下,使用高位而不是低位没有帮助。根据手册 (man 3 rand),rand()old 实现在低位存在问题。这就是为什么推荐使用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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-12
      • 1970-01-01
      • 2023-01-03
      • 2014-02-20
      • 2021-03-18
      • 1970-01-01
      • 1970-01-01
      • 2015-03-14
      相关资源
      最近更新 更多