C++生成随机数

之前写过一篇C语言生成随机数,其中生成随机数的方法自然也可用于C++中,不过C++ 11扩展了生成随机数的方法,我们可以有更多更好的选择了。

随机数的相关类声明都位于<random>头文件中,完整说明可参考:random,此处选择一些常用的总结一下。文末参考文献中那两篇知乎讨论很有趣,介绍了各种关于随机数的知识,很值得一读。

随机数生成器

一共提供了3种算法的生成器:线性同余法(LCG)、梅森旋转法、Lagged Fibonacci generator(LFG),其中梅森旋转法据说是最好的伪随机数生成算法,在C++中的具体实现类为std::mt19937std::mt19937_64,前者是32位的,后者是64位的。

非确定性随机数生成设备

上面几个随机数生成器的本质都是从一个种子开始,使用某个递推方法生成伪随机数序列,其实还有种随机数生成策略就是利用物理世界中的各种噪声,以此产生所谓的真随机数。std::random_device就是这样一种机制,具体实现方法Linux和Windows不相同,以Linux为例就是读取/dev/urandom设备的输出得到的。这个设备中有一个容纳来自各种设备驱动噪声数据的熵池,具体可参考:

/dev/random

/dev/urandom/dev/random的非阻塞版本,会重复利用熵池,保证在熵池为空的时候也会有输出,这以降低随机数可靠性为代价提高了其生成速度。

生成特定分布的随机数

在C语言中,要生成特定分布的随机数并不容易,不过在C++中这就很简单了,C++中提供了各种常用分布的实现,直接调用就可以了。比如最常用的均匀分布的整数:std::uniform_int_distribution;均匀分布的小数:std::uniform_real_distribution;正态分布:std::normal_distribution等。

使用示例

最后举几个实际的例子来说明具体用法。

直接使用random_device的输出作为随机数:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <random>

int main() {
std::random_device rd;

for (int i = 0; i < 100; i++) {
std::cout << rd() << std::endl;
}

return 0;
}

此方法速度较慢,所以一般并不直接使用。


random_device的输出为初始种子,使用梅森旋转法生成[0,99]范围内的整数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <random>
#include <functional>

int main() {
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist(0, 99);
auto dice = std::bind(dist, mt);

for (int i = 0; i < 100; i++) {
std::cout << dice() << std::endl;
}

return 0;
}

使用std::bind()是为了让之后调用起来更方便些。此方法就是生成某个区间内均匀分布的整数随机数最常用的方法了,注意区间两端都是闭区间。


random_device的输出为初始种子,使用梅森旋转法生成[0.0, 1.0)范围内的小数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <random>
#include <functional>

int main() {
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_real_distribution<double> dist(0.0, 1.0);
auto dice = std::bind(dist, mt);

for (int i = 0; i < 100; i++) {
std::cout << dice() << std::endl;
}

return 0;
}

生成[0, 1)区间内任意小数最常用的方法,注意这里是左闭右开区间,不包含1.0的。


参考资料:
C++11带来的随机数生成器
C++ 如何生成大随机数?
电脑取随机数是什么原理,是真正的随机数吗?