c - 为什么 rand() 在 Linux 上重复数字的频率远高于 Mac?
问题描述
作为我正在处理的项目的一部分,我在 C 中实现了一个 hashmap,并使用随机插入来测试它。我注意到rand()
在 Linux 上似乎比在 Mac 上更频繁地重复数字。RAND_MAX
在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);
}
Linux 持续生成大约 7.9 亿个重复项。Mac 始终只生成一个,因此它循环遍历它可以生成的每个随机数,几乎不重复。谁能向我解释这是如何工作的?我无法从页面中分辨出任何不同的man
内容,无法分辨每个人使用的是哪个 RNG,也无法在网上找到任何内容。谢谢!
解决方案
虽然起初听起来 macOSrand()
在不重复任何数字方面更好,但应该注意的是,由于生成了这么多数字,预计会看到大量重复(实际上,大约 7.9 亿,或(2 31 -1 )/ e )。同样,按顺序遍历数字也不会产生重复,但不会被认为是非常随机的。因此,在这个测试中,Linuxrand()
实现与真正的随机源无法区分,而 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));
在序列再次重复之前,这确实会导致 1 和RAND_MAX
(含)之间的所有数字恰好一次。由于下一个状态基于乘法,因此状态永远不会为零(或所有未来状态也将为零)。因此,您看到的重复数字是第一个数字,而零是永远不会返回的数字。
至少只要 macOS(或 OS X)存在,Apple 就一直在其文档和示例中推广使用更好的随机数生成器,因此质量rand()
可能并不重要,他们只是坚持使用其中之一可用的最简单的伪随机生成器。(正如您所指出的,他们rand()
甚至评论了使用建议arc4random()
。)
在相关的说明中,我能找到的最简单的伪随机数生成器在这个(和许多其他)随机性测试中产生不错的结果是xorshift*:
uint64_t x = *ctx;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*ctx = x;
return (x * 0x2545F4914F6CDD1DUL) >> 33;
此实现导致您的测试中几乎正好有 7.9 亿次重复。
推荐阅读
- c# - SSIS - 无法加载 DLL 'clrcompression.dll':找不到指定的模块
- amazon-web-services - 如何使用 Cognito 保护对 API Gateway 的访问并使其只能通过 CloudFront 访问?
- reactjs - 使用 Cypress 移动滑块
- nginx - Nginx启动和重启时如何使用Lua进行http请求
- mysql - 错误:在 MySQL、Heroku 和 Node.js 上连接 ECONNREFUSED 127.0.0.1:3306
- airflow - 气流 - 没有日志的任务失败
- discord.js - 查看谁邀请了不和谐机器人 (js)
- json - 同一个服务器上同一个dotnet core程序的不同appsettings.json配置
- java - 在 Swing 文本区域中更改(某些文本的)颜色
- python - Docker / Flask - 客户端向 HTTPS 服务器发送 HTTP 请求