为什么兰德()在Linux上比在Mac上更频繁地重复数字?

为什么兰德()在Linux上比在Mac上更频繁地重复数字?,c,linux,macos,random,C,Linux,Macos,Random,我在C中实现了一个hashmap,作为我正在进行的一个项目的一部分,并使用随机插入来测试它。我注意到Linux上的rand()似乎比Mac上重复数字的频率要高得多随机最大值在两种平台上均为2147483647/0x7FFFFFFF。我把它简化为这个测试程序,它使一个字节数组RAND_MAX+1-长,生成RAND_MAX随机数,记录每个数是否重复,并从列表中检查它,如图所示 #include <stdio.h> #include <stdlib.h> #include &l

我在C中实现了一个hashmap,作为我正在进行的一个项目的一部分,并使用随机插入来测试它。我注意到Linux上的
rand()
似乎比Mac上重复数字的频率要高得多<代码>随机最大值在两种平台上均为
2147483647/0x7FFFFFFF
。我把它简化为这个测试程序,它使一个字节数组
RAND_MAX+1
-长,生成
RAND_MAX
随机数,记录每个数是否重复,并从列表中检查它,如图所示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main() {
    size_t size = ((size_t)RAND_MAX) + 1;
    char *randoms = calloc(size, sizeof(char));
    int dups = 0;
    srand(time(0));
    for (int i = 0; i < RAND_MAX; i++) {
        int r = rand();
        if (randoms[r]) {
            // printf("duplicate at %d\n", r);
            dups++;
        }
        randoms[r] = 1;
    }
    printf("duplicates: %d\n", dups);
}
#包括
#包括
#包括
#包括
int main(){
大小大小=((大小)最大值)+1;
char*randoms=calloc(size,sizeof(char));
int-dups=0;
srand(时间(0));
对于(int i=0;i
Linux始终生成大约7.9亿个副本。Mac始终只生成一个随机数,因此它几乎不重复地遍历它可以生成的每个随机数。有人能给我解释一下这是怎么回事吗?我无法分辨与
man
页面不同的内容,无法分辨每个页面使用的是哪个RNG,也无法在网上找到任何内容。谢谢

rand()
是由C标准定义的,C标准没有指定使用哪种算法。显然,苹果使用的算法不如你的GNU/Linux实现:在你的测试中,Linux和真正的随机源是无法区分的,而苹果的实现只是在乱排数字


如果您想要任何质量的随机数,可以使用更好的PRNG,至少对返回的数字的质量提供一些保证,或者只需从
/dev/uradom
或类似文件中读取。后者提供加密质量的数字,但速度较慢。即使它本身太慢,
/dev/uradom
也可以为其他更快的PRNG提供一些优秀的种子。

MacOS在stdlib中提供了一个未记录的rand()函数。如果未设定种子,则它输出的第一个值是16807、282475249、1622650073、984943658和1144108930。A将显示该序列对应于一个非常基本的LCG随机数生成器,该生成器迭代以下公式:

xn+1=75·xn(模块231− (一)

由于此RNG的状态完全由单个32位整数的值描述,因此其周期不是很长。准确地说,它每231次重复一次− 2次迭代,输出从1到231的每个值− 二,

我不认为所有版本的Linux都有一个标准的rand()实现,但是有一个常用的。它不使用单个32位状态变量,而是使用超过1000位的池,这在所有意图和目的下都不会产生完全重复的序列。同样,您也可以通过打印此RNG的前几个输出而不首先设定种子来了解您的版本。(glibc rand()函数生成数字1804289383、846930886、1681692777、1714636915和1957747793。)


因此,在Linux中出现更多冲突(在MacOS中几乎没有)的原因是,Linux版本的rand()基本上更随机。

一般来说,rand/srand对被认为是不推荐使用的,因为低阶位在结果中显示的随机性比高阶位小。这可能与您的结果有关,也可能与您的结果无关,但我认为这仍然是一个很好的机会,让您记住,尽管一些rand/srand实现现在更加更新,但旧的实现仍然存在,最好使用random(3)。在我的Arch Linux机器上,以下注释仍在rand(3)的手册页中:

就在下面,手册页实际上给出了非常简短、非常简单的rand和srand的示例实现,它们是您见过的最简单的LC RNG,并且有一个小的rand_MAX。我认为它们与C标准库中的不匹配,如果它们曾经匹配的话。至少我希望不会


一般来说,如果要使用标准库中的内容,如果可以,可以使用random(手册页将其列为POSIX标准,返回到POSIX.1-2001,但rand是标准的,早在C标准化之前)。或者更好的办法是打开数字配方(或者在线查找)或Knuth并实现一个。它们非常简单,你只需要做一次,就可以拥有一个通用的RNG,它具有你最经常需要的属性,并且是已知质量的。

虽然一开始听起来像macOS
rand()
不重复任何数字会更好,应该注意的是,生成的数量如此之多,因此可以看到大量的重复数据(事实上,大约7.9亿,或(231-1)/e)。同样,在序列中迭代数字也不会产生重复,但不会被认为是非常随机的。因此,在本测试中,Linux
rand()
实现与真正的随机源代码无法区分,而macOS
rand()
则无法区分

另一件乍一看似乎令人惊讶的事情是macOS
rand()
如何能够很好地避免重复。综上所述,我们发现实施情况如下:

/*
 * Compute x = (7^5 * x) mod (2^31 - 1)
 * without overflowing 31 bits:
 *      (2^31 - 1) = 127773 * (7^5) + 2836
 * From "Random number generators: good ones are hard to find",
 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
 * October 1988, p. 1195.
 */
    long hi, lo, x;

    /* Can't be initialized with 0, so use another value. */
    if (*ctx == 0)
        *ctx = 123459876;
    hi = *ctx / 127773;
    lo = *ctx % 127773;
    x = 16807 * lo - 2836 * hi;
    if (x < 0)
        x += 0x7fffffff;
    return ((*ctx = x) % ((unsigned long) RAND_MAX + 1));

此实现在您的测试中几乎会产生7.9亿个副本。

由于rand()返回0..rand_MAX(含0..rand_MAX)的值,因此您的数组需要调整为rand_MAX+1您可能已经注意到rand_MAX/e~=7.9亿。另外,当n接近无穷大时,(1-1/n)^n的极限是1/e。@DavidSchwartz如果我理解正确的话,这也许可以解释为什么Linux上的数字一直在7.9亿左右。我猜接下来的问题是:为什么/如何Mac不重复那么多次?运行库中没有PRNG的质量要求。唯一真正的需求是可重复性
/*
 * Compute x = (7^5 * x) mod (2^31 - 1)
 * without overflowing 31 bits:
 *      (2^31 - 1) = 127773 * (7^5) + 2836
 * From "Random number generators: good ones are hard to find",
 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
 * October 1988, p. 1195.
 */
    long hi, lo, x;

    /* Can't be initialized with 0, so use another value. */
    if (*ctx == 0)
        *ctx = 123459876;
    hi = *ctx / 127773;
    lo = *ctx % 127773;
    x = 16807 * lo - 2836 * hi;
    if (x < 0)
        x += 0x7fffffff;
    return ((*ctx = x) % ((unsigned long) RAND_MAX + 1));
uint64_t x = *ctx;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*ctx = x;
return (x * 0x2545F4914F6CDD1DUL) >> 33;