C++ 中使用伪随机数
使用过 C语言标准库中随机数相关功能的同学肯定还记得 C 库中提供的两个函数:
- rand()
- srand()
为了使用这两个函数,需要包含头文件 stdlib.h。这么多年来C语言标准库中随机数生成的相关的函数就只有这两个。并且 rand() 函数只能生成 [0, RAND_MAX] 之间的均匀分布的随机整数,用起来非常不方便。许多 C++ 的程序员在使用到随机数功能时,也只会用 C 标准库中这两个函数。实际上从 C++11 标准起,C++ 标准库中就增加了不少新的随机数生成器。本文就来介绍这些新加进来的随机数函数。
C++ 中新增的随机数功能的声明都放到了头文件 random 中。因此,在使用这些新功能时,需要包含这个头文件。
#include<random>
为了输出随机数,我们需要两个类:
- 随机数引擎:用来产生均匀分布随机数
- 随机数分布:用均匀分布随机数构造我们需要的特点分布的随机数
随机数引擎
在 C++ 11中引入了三类随机数引擎,分别是:
- linear_congruential_engine
- mersenne_twister_engine
- subtract_with_carry_engine
linear_congruential_engine 使用的就是最常见的线性同余法,运算速度较快,是使用最广泛的随机数引擎。因此我们多介绍些。熟悉了这个引擎的使用,其他引擎也就自然的会用了。
线性同余法的公式如下:
类声明如下:
template<
class UIntType,
UIntType a,
UIntType c,
UIntType m
> class linear_congruential_engine;
a,c,m 这三个参数的选择非常的重要。如果选的不好,生成的随机数统计特性就会很差。好在 C++ 11 标准给我们推荐了几组比较好的值。
typedef linear_congruential_engine<unsigned int, 16807, 0, 2147483647>
minstd_rand0;
typedef linear_congruential_engine<unsigned int, 48271, 0, 2147483647>
minstd_rand;
因此,大多数时候,我们只要直接使用 minstd_rand0 或 minstd_rand 就可以了。
这两个预定义的引擎生产的随机数的范围都是 [1, 2147483646]。下面的代码先是输出这个引擎可以输出的最大、最小值,然后用这个引擎输出 10 个随机数。这里随机数引擎的种子设为了 123。 真实代码中,我们通常会选用 random_device 作为种子,后面会专门讲讲 random_device。
minstd_rand engine(123);
cout << engine.min() << endl;
cout << engine.max() << endl;
for(int i = 0; i < 10; i++)
cout << engine() << endl;
上面的代码只是示意性的,我们通常不会这么用的。
mersenne_twister_engine 是马特赛特旋转演算法。此算法是 Makoto Matsumoto (松本)和 Takuji Nishimura (西村)于1997年开发的,基于有限二进制字段上的矩阵线性再生。可以快速产生高质量的伪随机数,修正了古老随机数产生算法的很多缺陷。但是计算速度较慢。这个算法的参数很多,C++11 标准中替我们选好了两组参数,多数情况下我们直接用就好了。
typedef std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31,
0x9908b0df, 11,
0xffffffff, 7,
0x9d2c5680, 15,
0xefc60000, 18, 1812433253> mt19937;
typedef std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
0xb5026f5aa96619e9, 29,
0x5555555555555555, 17,
0x71d67fffeda60000, 37,
0xfff7eee000000000, 43, 6364136223846793005> mt19937_64;
subtract_with_carry_engine 使用的是 lagged Fibonacci 法,这种方法运算速度最快,但是生成的随机数的统计特性比另外两种要差一些。通常只是在我们需要快速生成大量随机数,而对随机数的统计特性要求较低是才使用。下面是两组预先定义好的参数。
typedef std::subtract_with_carry_engine<std::uint_fast32_t, 24, 10, 24> ranlux24_base;
typedef std::subtract_with_carry_engine<std::uint_fast64_t, 48, 5, 12> ranlux48_base;
random_device
这里把 random_device 单独拿出来讲讲。random_device 其实也是个随机数引擎,只不过这个引擎与其他的引擎有些区别。其他的引擎实际上都是伪随机数引擎,这个 random_device 可以是真随机数,当然前提是所在的系统支持生产真随机数。(对于新一点 Intel CPU,有个汇编指令叫做 RDRAND,这个指令通过读取CPU内部一个特殊的电阻热噪声取得硬件真随机数。) random_device 也并不是强制必须生成真随机数,如果硬件不支持,或者有其他什么考虑,也可以生成伪随机数。但是即使生产的是伪随机数,也要比其他的引擎生成的伪随机数有更好的统计特性,也就是说更随机。但是这也是有代价的,通常 random_device 生成随机数的速度会较慢。因此,常用的做法是用 random_device 生成一个随机数作为其他引擎的随机数种子。代码类似下面这样:
std::random_device rd; //Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
随机数分布器
随机数分布器的作用是将均匀分布随机数转化为我们需要的特定分布的随机数。下面先以均匀分布随机数分布器为例。均匀分布随机数分布器有两种,一个输出整数、一个输出浮点数:
template< class IntType = int > class uniform_int_distribution;
template< class RealType = double > class uniform_real_distribution;
均匀分布随机数分布器有两个参数 a 和 b,用来确定输出的随机数的范围,这个区间是前闭后开的 [a,b) ,这点需要特别注意。下面是个例子:
void test19937()
{
std::mt19937 gen(1); //Standard mersenne_twister_engine seeded with 1
std::uniform_real_distribution<> dis(1, 2);
for (int n = 0; n < 10; ++n)
{
//Use dis to transform the random unsigned int generated by gen into a double in [1, 2)
std::cout << dis(gen) << ' '; //Each call to dis(gen) generates a new random double
}
}
除了均匀分布之外, C++11 标准中还提供了多种其他分布的随机数。一一介绍的话篇幅就太长了,我列了个表格,大家可以参考。
名称 | 说明 |
---|---|
uniform_int_distribution | 均匀分布 |
uniform_real_distribution | 均匀分布 |
bernoulli_distribution | bernoulli分布 |
binomial_distribution | 二项分布 |
negative_binomial_distribution | https://en.wikipedia.org/wiki/Negative_binomial_distribution |
geometric_distribution | geometric distribution |
poisson_distribution | poisson distribution |
exponential_distribution | exponential distribution |
gamma_distribution | gamma distribution |
weibull_distribution | weibull distribution |
extreme_value_distribution | https://en.wikipedia.org/wiki/Generalized_extreme_value_distribution |
normal_distribution | normal distribution |
lognormal_distribution | lognormal distribution |
chi_squared_distribution | chi squared distribution |
cauchy_distribution | cauchy distribution |
fisher_f_distribution | Fisher’s F-distribution |
student_t_distribution | Student’s t-distribution |
piecewise_constant_distribution | real values distributed on constant subintervals |
piecewise_linear_distribution | real values distributed on defined subintervals |
随机数引擎适配器
所谓随机数引擎适配器也是个随机数引擎。但是这个引擎必须以另一个随机数引擎作为数据源才能工作。 C++11 标准中给出了三种随机数引擎适配器:
- discard_block_engine
- independent_bits_engine
- shuffle_order_engine
这三种适配器的具体原理没必要仔细研究。我们只需要知道用这三种适配器再配合其他的引擎,可以构造一些经典的随机数算法,下面这三个是标准里给我们预备好的。
typedef discard_block_engine<ranlux24_base, 223, 23> ranlux24;
typedef discard_block_engine<ranlux48_base, 389, 11> ranlux48;
typedef shuffle_order_engine<minstd_rand0, 256> knuth_b;
辅助功能
除了上面介绍的这些,还有两个辅助的类。
- generate_canonical
- seed_seq
这两个类通常用不到。这里也就不介绍了。需要用到时可以查看下面的文档: