rand():
计算机编程很需要随机功能,但计算机能够提供真实的随机概率吗?通过C语言的函数rand(),可以方便地获得“伪随机”数,比如:
int a = rand();
在调用rand()之前,还需要先调用srand(int seed),作用是“种下一颗随机种子”,“种子”就是入参seed,也是一个整数。播种之后,C运行库就会依据该种子的指,生成一系列的数值,而后程序第一次调用rand(),就得到该系列中的第一个数,第二次调用就得到系列中的第二个数……想象一下,如果程序存在并发,自然容易发生分不清谁是第几次的情况,也就会存在某两个并发几乎“同时”调用了rand(),于是得到了相同的两个随机数的情况。
更“伪”的事情时:如果两次调用srand(rand)中下的种子是一样的,则所产生两个系列数,将一一对应,完全相同。
连续运行连词,可以看到种子相同,得到随机数完全相同,这很不随机呀。种子不同,得到的随机数系列也不同,原来一开始,这一切变化来自最开始的那个种子。
能不能让“种子数”本身也随机呢?这除非有个硬件设备,比如可以采集周边光线、噪声、WIFI强度、微博头条等等随机数据,以便在每次运行时,生成一个真正的随机数,否则软件模拟的结果,“伪随机”就是“伪随机”。常见的做法是,取当前时间作为作为随机数种子,毕竟时间一直在变,前例的代码改变如下:
、
再运行两次,两次的结果不一样了。看起来很完美,但相当多的C/C++程序员被这个方法坑过,因为在并发时,很可能会有两个或更多线程在同一秒内调用time()函数得到的时间是一样的,可机器的CPU的计算能力随便就死3GHz,还多核,理论值上,一毫秒之内调用一百万次time()得到的时间全相同。我们用单一线程就可以模拟出类似情况,比如循环100次调用time():
得到的时间是距离1970年1月1日零点零分零秒的秒数。
random_device:
C++标准委员会在新标准里提供了一个名为“random_device”的类。
注意这次有两层循环,内层循环输出了5个随机数,外层循环负责同样的事情做两遍,所以测试时不需要运行程序两次了。
外层循环中,定义了random_device类的一个变量r,内层循环就用它产生随机数,一个变量直接挂一对括号,这是“函数对象”,random_device应该是针对“()”做了操作符重载,连续调5次,产生5个随机数。在Windows下,两次调用果真产生了相同的系列,
在Ubuntu Linux下,效果大不一样,
速度比较快,不是重点,重点是在Linux下,两个随机系列完美的不相同!Linux提供了良好的随机数设备模拟,其原理是利用系统当前的“熵”进行模拟。“熵”指系统整体熵的信息混乱度,比如内存剩余多少,当前有几个进程,磁盘上最后一次读出的内容是什么,键盘刚刚哪个键被按下等等的变化数据,以某种算法计算出一个随机种子。
Windows其实也提供类似功能,却没有绑定到当前使用的C/C++运行库。结果是“random_device”仍然在取时间。
在Linux下,每次都需要直接访问操作系统计算熵值,会比较慢(甚至可能堵塞),为此我们可以只向“random_device”要一次数据,然后以此为种子,再使用各种数学算法计算出随机数系列:
注意,random_device变量更名为“rd”,用它所产生的随机数,被用于mt19937对象“r”的构造入参,“r”将被作为种子,以代号“mt19937”为代表的算法,生成一系列随机数。
【小提示】:mt19937随机算法
mt19937伪随机数产生算法,具有实现简易、占用内存少、速度高、所产生的随机数分布均衡等优点,因此被广泛使用。更多解释请上网查阅“MersenseTwister”百科
以上例子中产生的随机数都或大或小,如果希望所产生的随机数值被控制再一定范围内,可以使用“求余”操作。比如一个“摇骰子”的游戏,每个骰子所产生的数必须在1到6之间:
int v = (rand() % 6) + 1;
【课堂作业】:使用C++11标准,写一段猜随机数