C++11随机数的正确打开方式

在C++11之前,现有的随机数函数都存在一个问题:在利用循环多次获取随机数时,如果程序运行过快或者使用了多线程等方法,srand((unsigned)time(null))这样的设置当前系统时间为种子的方法每次返回的随机数都是一样的。而C++11中提供了真随机数做种子的方法来解决这一问题。

By the way,2019年了,我见过的编译器都不需要特殊指定使用的是C++11的新特征了

random_device

标准库提供了一个非确定性随机数生成设备.在Linux的实现中,是读取/dev/urandom设备;Windows的实现是用rand_s,使用的是操作系统来生成加密安全的伪随机数

注意,urandom实际上也是一种伪随机数,具体见下一节

random_device提供()操作符,用来返回一个min()到max()之间的一个数字.

注意这里的min()和max()都是只能看不能改的。

random_device一般只用来作为其他伪随机数算法的种子,原因有三:

  1. random_device的最大值和最小值不能修改,当然可以通过取模的方式获得想要的范围的数,但是毕竟不太优雅
  2. 当熵池用尽后,许多random_device的具体实现的性能会急速下降(原话是:"the performance of many implementations of random_device degrades sharply once the entropy pool is exhausted. For practical use random_device is generally only used to seed a PRNG such as mt19937"(来源:https://stackoverflow.com/questions/39288595/why-not-just-use-random-device)。但是这一点也有争议,在linux下random_device的实现其实是std::fopen("/dev/urandom"),有人说urandom在熵池耗尽之后输出的随机数是低质量的,但也有人说不是(https://blog.csdn.net/F8qG7f9YD02Pe/article/details/89880266),我没有对它做过多研究,但是下一点是毋庸置疑的
  3. 多次调用random_device要花费比其他伪随机数算法更多的时间。在Linux中,正如上文所说,每次调用random_device都需要读urandom这个文件再关闭,而在WIndom中我们需要调用操作系统的API,再销毁实例化对象,这个时间花费显然比设置好种子就能一直产生的其他伪随机数算法要慢得多。

所以,我们一般将random_device只用作种子。

有关于熵池的内容见下一节。划重点:“产生真随机数依赖于熵池中的噪声资源。如果熵池资源耗尽,就需要等到收集足够多的环境噪声时,才能继续产生新的随机数。”

[转载]Linux 内核熵池与 /dev/urandom

原文地址:http://www.codebelief.com/article/2017/10/linux-entropy-pool-and-dev-urandom/

从计算机随机数谈起

我们知道,计算机是一个可预测的系统,因此不可能通过算法来产生真正的随机数。在计算机中,所谓的随机数通常都是伪随机数,就是通过随机算法计算出来的,可以被近似看作随机数的数值。常见的随机数算法有线性同余法(Linear Congruential Generator)、梅森旋转法(Mersenne twister)等,前者是大部分编译器采用的算法,随机性相对差一些;而后者是更为优秀的随机算法,随机性好,被 Python、Ruby 等语言用作默认的随机算法。

但是,随机算法的缺陷也是很明显的。一方面,随机性越好的算法计算复杂度越大;另一方面,即使随机性再好,也无法与真正的随机数相媲美。因此,产生真正的随机数是最理想的方法。

Linux 内核熵池

Linux 内核采用熵来描述数据的随机性。在物理学中,熵(entropy)是一个描述系统混乱程度的物理量,熵越大说明系统越无序、越混乱,不确定性越大。

虽然计算机本身可预测,但计算机的运行环境中充满了各种不可预知的噪声,例如来自设备驱动的噪声、随机的鼠标点击间隔、硬件设备发生中断的时间等等。

Linux 系统维护了一个专门用于收集上述噪声的熵池(entropy pool),这些噪声将被用于产生真正的随机数。

需要注意的是,产生真随机数依赖于熵池中的噪声资源。如果熵池资源耗尽,就需要等到收集足够多的环境噪声时,才能继续产生新的随机数。

/dev/urandom

Linux 提供了内核随机数生成器的接口,即字符设备/dev/random,该字符设备用于生成高质量的随机数,它会确保熵池资源足够时才生成随机数。如上面所说,当熵池为空时,对/dev/random 的读取操作将会阻塞,直到收集足够的噪声为止。

使用/dev/random 来生成随机数,很可能导致应用被阻塞。幸运的是,Linux 中还有另一个随机数生成器/dev/urandom,该字符设备是/dev/random 的非阻塞版本,准确说它是一个伪随机数生成器,它的随机数种子来自于熵池,不过即使熵池为空,/dev/urandom 仍然能产生随机数。

Linux 的 man(4)手册中这么写道:

/dev/random 是一个遗留下来的接口,在所有使用场景中,/dev/urandom 更受欢迎并且能满足要求。

/dev/random 接口遗留下来的原因主要是早期/dev/urandom 所采用的密码算法未被大家信任,但现在,/dev/urandom 已经被广泛的采用了。

解决由/dev/random 引起的阻塞

目前,仍有一些应用使用/dev/random 来生成随机数,这些应用在运行时,可能由于熵池耗尽而阻塞。例如 tomcat7.0 以上的版本,依赖于该生成器来生成随机数,可能启动时便没有反应,实际上是等待熵池收集噪声。此外,strongSwan 生成 CA 时使用的 ipsec pki 命令也可能因此而阻塞。

解决该问题可以通过安装 havged 程序来解决。haveged 是一个简单易用的不可预测随机数生成器,基于 HAVEGE 算法。haveged 可以解决在某些情况下,系统熵过低的问题。

参考 man 手册

random(4) - Linux manual page

常用的随机数算法

上面我们说了,random_device一般只用来做种子,C++11中常用来做为伪随机数算法的有以下几种:

  • linear_congruential_engine线性同余法,这种速度最快、最常用
  • mersenne_twister_engine梅森旋转法,这种生成的随机数质量比较高
  • substract_with_carry_engine滞后Fibonacci

但是这些类可以直接使用吗?这是不行的,这三个都是模板类,只是定义了接口,需要我们自己将它实例化。但是显然每个人实例化一次是不现实的,所以C++11中预先实现了一些给我们,我们只需要直接创建这些类的对象就好了。

还有一个default_random_engine的类。它是一个实例化的类。之所以不归入那三种算法,是因为它的实现是由编译器厂家决定的,有的可能用linear_congruential_engine实现,有的可能用mersenne_twister_engine实现。这种现象在C/C++中见多了。不过,对于其他的类,C++11是有明确规定用哪种算法和参数实现的。

对于default_random_engine来说,其产生的随机数范围是在[min(), max()]之间,其中min()和max()为它的两个成员函数,是闭区间。

来源:https://blog.csdn.net/luotuo44/article/details/33690179

在C++11里面,把这些随机数生成器叫做引擎(engines)

注意,random_device也是一种随机数引擎

虽然和每次都直接使用random_device相比,使用这些算法大大减少了多次生成随机数时的平均时间和空间花费,但是这些算法也存在问题,就是他们产生的范围还是太大了,如果我们需要特定范围下的随机数,依然需要取模。为了解决这一问题,我们要引出随机分布模板类。

随机分布模板类

常见的随机分布模板类

均匀分布:

​ uniform_int_distribution 整数均匀分布

​ uniform_real_distribution 浮点数均匀分布

注意,uniform_int_distribution的随机数的范围不是半开范围[ ),而是[ ],对于uniform_real_distribution却是半开范围[ )。

伯努利类型分布:(仅有yes/no两种结果,概率一个p,一个1-p)

​ bernoulli_distribution 伯努利分布

​ binomial_distribution 二项分布

​ geometry_distribution 几何分布

​ negative_biomial_distribution 负二项分布

Rate-based distributions:

​ poisson_distribution 泊松分布

​ exponential_distribution指数分布

​ gamma_distribution 伽马分布

​ weibull_distribution 威布尔分布

​ extreme_value_distribution 极值分布

正态分布相关:

​ normal_distribution 正态分布

​ chi_squared_distribution卡方分布

​ cauchy_distribution 柯西分布

​ fisher_f_distribution 费歇尔F分布

​ student_t_distribution t分布

分段分布相关:

​ discrete_distribution离散分布

​ piecewise_constant_distribution分段常数分布

​ piecewise_linear_distribution分段线性分布

这些模板类都是定义好了的、可以直接使用的。

这些概率分布函数都是有参数的,在类的构造函数中把参数传进去即可。我们最常用的还是均匀分布,这里以 uniform_int_distribution为例介绍以下如何使用这些算法:

#include <random>
#include<iostream>
using namespace std;//要是不使用std名字空间,下面的就都需要加std::
void formData(){
random_device sd;//生成random_device对象sd做种子
minstd_rand linearRan(sd());//使用种子初始化linear_congruential_engine对象,为的是使用它来做我们下面随机分布的种子以输出多个随机分布.注意这里要使用()操作符,因为minst_rand()接受的是一个值(你用srand也是给出这样的一个值)
uniform_int_distribution<int>dis1(0,1);//生成01序列
for(int i=0;i<100;i++){
cout<<dis1(linearRan)<<endl;//使用linear engine做种子,注意这里传入的不是一个值而是一个引擎:【随机分布函数需要传入一个随机数引擎作为参数,其实random_device也是一个引擎,这里把sd传入也不会出错】
}
}
int main(){
formData();
}

总结

还是有点绕的。总之,要得到不止一个一个我们最常需要的、符合一定分布规律的且随机质量较高的随机数,我们要做的是:

  1. 定义random_device对象
  2. 选择随机引擎(默认、线性、梅森、斐波那契)的实现类,将random_device的随机结果传入作为种子
  3. 选择要的分布,创建分布对象,将引擎传入作为种子,让分布对象输出随机数。

C++11随机数的正确打开方式的更多相关文章

  1. C#语法——泛型的多种应用 C#语法——await与async的正确打开方式 C#线程安全使用(五) C#语法——元组类型 好好耕耘 redis和memcached的区别

    C#语法——泛型的多种应用   本篇文章主要介绍泛型的应用. 泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了 ...

  2. 【分享】WeX5的正确打开方式(6)——数据组件初探

    本文是[WeX5的正确打开方式]系列的第6篇文章,简单介绍一下WeX5中数据组件的特性和结构形式. 数据组件的由来 上一篇 WeX5绑定机制我们实现了一个简单的记账本应用,当时所有数据都用 JSON ...

  3. iOS开发小技巧--相机相册的正确打开方式

    iOS相机相册的正确打开方式- UIImagePickerController 通过指定sourceType来实现打开相册还是相机 UIImagePickerControllerSourceTypeP ...

  4. Xcode 的正确打开方式——Debugging(转载)

    Xcode 的正确打开方式——Debugging   程序员日常开发中有大量时间都会花费在 debug 上,从事 iOS 开发不可避免地需要使用 Xcode.这篇博客就主要介绍了 Xcode 中几种能 ...

  5. InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式

    InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式 https://mp.weixin.qq.com/s/HGa_90XvC22anabiBF8AbQ 在这篇文章里,我将讨论在MySQL 5 ...

  6. Console控制台的正确打开方式

    Console控制台的正确打开方式 console对象提供了访问浏览器调试模式的信息到控制台 -- Console对象 |-- assert() 如果第一个参数断言为false,则在控制台输出错误信息 ...

  7. 任务队列和异步接口的正确打开方式(.NET Core版本)

    任务队列和异步接口的正确打开方式 什么是异步接口? Asynchronous Operations Certain types of operations might require processi ...

  8. (一)Redis for Windows正确打开方式

    目录 (一)Redis for Windows正确打开方式 (二)Redis for 阿里云公网连接 (三)Redis for StackExchange.Redis 下载地址 官网.中文网1 及 中 ...

  9. List的remove()方法的三种正确打开方式

    转: java编程:List的remove()方法的三种正确打开方式! 2018年08月12日 16:26:13 Aries9986 阅读数 2728更多 分类专栏: leetcode刷题   版权声 ...

随机推荐

  1. R_Studio(学生成绩)对两个班级学生成绩进行集合,重新计算学生综合测评成绩并对学生按综合测评成绩进行排名

    对成绩表"11_1_1.csv" "11_2_1.csv"进行集成,并重新计算4门课程的平均分为综合测评,增加“排名”属性,并按排名排序 "11_1_ ...

  2. 用node批量压缩html页面

    最近在写一个用了layui的后台管理系统.因为某些原因,html,css,js都写在.html里,并且没有用到别的打包工具.所以写了一个用node命令批量压缩页面并且混淆js的小工具.node安装ht ...

  3. jquery 四舍五入小数处理总结

    一.jquery中对小数进行取整.四舍五入的方法 1.丢弃小数部分,保留整数部分 parseInt(5/2) =2 2.四舍五入. Math.round(5/2) =3 3.向下取整 Math.flo ...

  4. C# 强命名程序集,防止dll被修改,混淆下发布

    未能加载文件或程序集“Jonckers.Service.RedisCacheEngineExtend, Version=1.0.0.0, Culture=neutral, PublicKeyToken ...

  5. Java反序列化与远程代码执行

    https://mp.weixin.qq.com/s/asQIIF8NI_wvur0U0jNvGw 原创: feng 唯品会安全应急响应中心 2017-09-19 https://mp.weixin. ...

  6. Android动画View Animation与Drawable Animation

    Animations 一.Animations介绍 Animations是一个实现android UI界面动画效果的API,Animations提供了一系列的动画效果,可以进行旋转.缩放.淡入淡出等, ...

  7. 1.3 Junit4简介

    1.Junit4框架 可用于单元测试,直接测试类中的方法 2.简单实用 a.导入Junit的jar包 b.熟悉Junit的执行顺序 c.写测试用例 d.利用断言,找bug 3.demo public ...

  8. NSIS打包electron程序为exe安装包

    在我的上一篇博客已经介绍了将electron程序生成一个exe可执行文件,但是这并不是最终能够发给用户用来安装的最终安装包,下面我们就介绍如何使用NISI将我们的应用程序打包成安装包: 上一篇博客我们 ...

  9. 四十二:数据库之SQLAlchemy之数据查询懒加载技术

    懒加载在一对多,或者多对多的时候,如果要获取多的这一部分的数据的时候,通过一个relationship定义好对应关系就可以全部获取,此时获取到的数据是list,但是有时候不想获取全部数据,如果要进行数 ...

  10. 【图形学手记】law of the unconscious statistician

    以扔色子为例,结果集为{1,2,3,4,5,6},每个数字出现的概率为1/6 以色子结果为随机变量X,如果我们定义函数F(X) = (X-3)2,我们来计算F(X)的概率分布: X=1,F(1)=(1 ...