众所周知,计算机产生的是伪随机数。所谓伪随机,就是:当知道种子和随机产生算法之后,就可以完全确定出随机数序列了。并且这个随机数序列是循环重复的。不同的随机产生算法的循环周期不同;好的随机产生算法的循环周期会很长。
有的文章提到,可以通过引入系统以外的变量来达到真随机的目的,比如“Unix 维护了一个熵池,不断收集非确定性的设备事件,即机器运行环境中产生的硬件噪音,来作为种子”。个人觉得,这本质上仍然是伪随机,只是利用硬件噪声生成种子而已,这和使用时间戳作为种子没有本质区别。只是别人可能没法知道种子是啥,因此也无法推测出随机序列。而该文中提到的 /dev/random (其实还有一个 /dev/urandom)是一个字符文件,如果读取它,会取得随机字符,而不是随机数字。当然我们可以想办法把随机字符转换成随机数字。关于如何使用 /dev/random 和 /dev/urandom, 可以参见这篇文章,本文不再赘述。
有的文章中提到,C++中的随机数序列的循环周期是65536或2^32. 本文接下来的部分将验证一下计算机产生的是否是伪随机,以及循环周期是多少。
先看代码:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
void gen_rand_int(unsigned int seed)
{
// srand((unsigned)time(NULL));
srand(seed);
for(int i = 0; i < 10;i++ )
cout << rand() << ' ';
cout << endl;
}
void gen_2_rand_int(unsigned int seed)
{
srand(seed);
long max_unsigned_int = 4294967295;
int i_1, i_65536, i_65537, i_max_ui, i_max_ui_1;
long yi = 100000000;
long tmp;
for (long i=0; i<=max_unsigned_int; ++i) {
tmp = rand();
if (i == 0)
i_1 = tmp;
else if (i == 65535)
i_65536 = tmp;
else if (i == 65536)
i_65537 == tmp;
else if (i == max_unsigned_int)
i_max_ui = tmp;
if (i % yi == 0)
cout << i/yi << " Yi have been generated" << endl;
}
cout << "the first number is: " << i_1 << endl;
cout << "the 65536" << " number is " << i_65536 << endl;
cout << "the 65537" << " number is " << i_65537 << endl;
cout << "the " << max_unsigned_int+1 << " number is " << i_max_ui << endl;
cout << "the " << max_unsigned_int+2 << " number is " << rand() << endl;
}
int main()
{
gen_rand_int(10);
gen_rand_int(10); // print the same as above
gen_2_rand_int(10);
return 0;
}
然后,给出观察结论:
1. 在某一台机器上,由main函数中连续2次调用 gen_rand_int(10) 的打印结果完全一致。
仍然在这一台机器上,多次运行该二进制可执行文件,gen_rand_int(10)的打印结果完全一致。
由此可以看出,伪随机的含义就是,一旦知道了种子和算法,随机数序列就是确定的了。这一点在下面还可以继续看出。
2. 笔者将以上程序拿到5台机器上跑,在相同的种子下,结果很有趣:
前10个数 | 第65536个数 | 第65537个数 | 第2^32个数 | 第2^32+1个数 | |
A - Ubuntu 18.04 | ABCD相同 | 1174608988 | 每次都变 | 1843318998 | 1902554484 |
B - Ubuntu 16.04 | ABCD相同 | 1174608988 | 每次都变 | 1843318998 | 1902554484 |
C - CentOS 7.3 | ABCD相同 | 1174608988 | 0 | 1843318998 | 1902554484 |
D - RHEL 7.5 | ABCD相同 | 1174608988 | 0 | 1843318998 | 1902554484 |
E - Cygwin on Windows | 与ABCD不同 | 与ABCD不同 | 0 | 与ABCD不同 | 与ABCD不同 |
由上可以看出,
a. 一般来说,在给定种子时,在各Linux系统上C++程序取得的随机数序列基本都是一致的;
b. 第65537个随机数似乎比较特别,Ubuntu每次跑都会是一个不同的值,似乎有点“随机”,而Red Hat系列的Linux此处总为0;
c. Cygwin系统的随机数取得与真实Linux系统的随机数差别较大,可能是取随机数的库使用的是Windows上的库。
d. 没有看出来循环周期是多少,但基本可以确定不是65536或2^32;根据网上有些文章,可能采用的是一种较好的随机生成算法,其循环周期远大于2^32.
(完)