从一个具体的例子说起:如何均匀生成 1 亿以内的随机数? 所谓“均匀”,意味着生成概率相等。
从 rand() 函数开始
生成随机数,第一反应是使用 rand() 函数。rand() 函数是 C 语言中用来生成随机数的函数:
#include <stdlib.h> void srand(unsigned int seed); int rand(void); int rand_r(unsigned int *seedp);
rand() 函数可以生成 [0, RAND_MAX] 之间的数字,其中 RAND_MAX 一般是 2147483647。
传统的 rand() 函数在使用前需要使用 srand() 函数设置随机种子。由于 rand() 函数内部使用了静态变量保存状态,调用 rand() 函数时会进行加锁,并且是不可重入的。为了解决这个问题,可以使用 rand_r() 函数,它是 rand() 的可重入版本,使用参数 seedp 来保存相应的状态。
为了生成 1 亿以内的随机数,最简单的方式是使用取模运算:rand() % 100000000。但是这种方法并不是均匀的,因为对于 [0, 99999999] 这 1 亿个数字来说,概率是不相等的。例如,随机生成数字 0 的情况有 22 种可能,而随机生成数字 99999999 的情况只有 21 种可能。为了得到均匀分布的随机数,需要使用其他方法来处理。
C++ 的 uniform_int_distribution
C++ 的 uniform_int_distribution 是 C++ 标准库中的一个随机数分布类,用于生成均匀分布的整数随机数。它可以用于生成指定范围内的整数随机数,确保生成的随机数在指定范围内是均匀分布的。
uniform_int_distribution 类通常与 C++ 标准库中的随机数引擎配合使用,例如 std::default_random_engine。通过使用 uniform_int_distribution,可以避免使用取模运算来生成随机数,从而更加方便地生成均匀分布的整数随机数。
int main() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<int32_t> distrib(0, 99999999); for (uint32_t i = 0; i < 10; i++) { std::cout << distrib(gen) << std::endl; } }
关于 std::random_device 和 std::mt19937
std::random_device 是 C++ 标准库中用于生成真正随机数的设备。它通常用作生成随机数种子,以便初始化伪随机数生成器。由于 std::random_device 生成的随机数是基于真正的随机事件(例如硬件噪声)而不是伪随机算法,因此它通常用于初始化伪随机数生成器的种子,以提供更好的随机性。
std::mt19937 是 C++ 标准库中的 Mersenne Twister 伪随机数生成器。它是一个高质量的伪随机数生成器,能够生成均匀分布的整数随机数。通常,std::random_device 用于生成种子,然后将该种子传递给 std::mt19937,以初始化 Mersenne Twister 伪随机数生成器。
综合使用 std::random_device 和 std::mt19937 可以提供更好的随机性,并且是 C++ 中生成随机数的常用做法。
当使用 std::random_device 和 std::mt19937 时,可以通过以下代码示例来生成均匀分布的整数随机数:
#include <iostream> #include <random> int main() { // 使用 random_device 生成种子 std::random_device rd; // 使用 mt19937 作为伪随机数生成器 std::mt19937 gen(rd()); // 定义均匀分布的整数随机数分布 std::uniform_int_distribution<int> dis(1, 100); // 生成 1 到 100 之间的均匀分布的整数随机数 // 生成随机数 for (int i = 0; i < 10; ++i) { int random_num = dis(gen); // 使用 mt19937 生成随机数 std::cout << random_num << " "; } return 0; }
总的来说,无论是性能还是随机数的质量,std::mt19937 / std::mt19937_64 都是其中出类拔萃的伪随机数生成器。
小结:
1.使用 rand() 取模的方式造成的随机数不均匀概率不算特别大,但具体影响因应用而异。建议尽量避免使用这种方式来生成随机数。
2.使用 time(nullptr) 作为 rand() 的随机种子虽然方便,但存在一些问题:种子变化频率低且不够随机,容易被预测。
3.内部会加锁的 rand() 可以使用 rand_r() 避免,但总体来说,建议不要在 C++ 代码中使用 rand() 系列的函数。
4.使用 std::random_device 生成随机种子,std::mt19937 / std::mt19937_64 生成随机数,以及 std::uniform_int_distribution 生成指定范围的随机数是一个方便又安全的随机数生成组合。
参考资料
1.漫谈随机数