在 C/C++中,随机数生成是一项常见的任务,通常用于模拟、游戏、密码学和统计等应用中。生成时一般会指定满足均匀分布、或者正态分布的随机数。下面,介绍下如何在 C 和 C++ 中生成满足均匀分布和正态分布的随机数。实现这一点,一般要有三个步骤:
- 设置随机数种子
- 根据种子值生成随机数
- 将随机数映射到某个分布内
1. C++ 随机数
C++ 中使用 random_device 作为随机数种子生成器,是一个硬件随机数生成设备,通常用于生成高质量的随机数种子。
在 C++ 中随机数生成器有 Mersenne Twister 生成器,也有线性同余生成器(Linear Congruential Generator,简称LCG),使用使用随机性更好的前者作为生成器。Mersenne Twister 生成器有两个版本 32 位和 64 位,后者生成的随机数范围更广泛,一般情况下,32 位生成器已经能够满足大部分场景下的随机数需求。
在 C++ 中提供两个类uniform_int_distribution、uniform_real_distribution 用于将生成的随机数映射到均匀分布,前者用于映射到均匀分布的整数,后者用于映射到均匀分布的浮点数。两个类都需要设置映射的数值范围。normal_distribution 用于将随机数映射到满足均值和标准差的某个正态分布中。
#if 1 #include <random> #include <iostream> using namespace std; void test() { // 1. 生成随机数种子 random_device rd; int seed = rd(); cout << "seed = " << seed << endl; // 2. 根据种子生成随机数 // Mersenne Twister 32 版本,提供更大的周期和更广的范围。 mt19937 generator1(rd()); int number1 = generator1(); cout << "number1 = " << number1 << endl; // Mersenne Twister 64位版本,提供更大的周期和更广的范围。 mt19937_64 generator2(rd()); int number2 = generator2(); cout << "number2 = " << number2 << endl; // 默认随机数生成器,默认使用的是 mt19937 std::default_random_engine generator3(rd()); int number3 = generator3(); cout << "number3 = " << number3 << endl; // 3. 映射到分布 // 整数均匀分布:将生成器生成的数字映射到整数均匀分布 uniform_int_distribution<int> distribution1(100000, 200000); int number4 = distribution1(generator1); cout << "number4 = " << number4 << endl; // 小数均匀分布:将生成器生成的数字映射到小数均匀分布 uniform_real_distribution<double> distribution2(0.0, 1.0); double number5 = distribution2(generator1); cout << "number2 = " << number2 << endl; // 正态分布:将生成器生成的数字映射到正态分布 normal_distribution<double> distribution3(0.0, 1.0); double number6 = distribution3(generator1); cout << "number6 = " << number6 << endl; } int main() { test(); return 0; } #endif
seed = 1306554885 number1 = -2089015614 number2 = 1517304759 number3 = -1872929550 number4 = 168310 number2 = 1517304759 number6 = -0.425118
2. C 随机数
C 使用 rand 函数根据随机数种子计算出 0-32767 之间的随机数,并且该随机数满足均匀分布。
#include <stdlib.h> #include <time.h> #include <stdio.h> #include <memory.h> void test01() { // 1. 获得时间戳作为随机数种子 time_t seed = time(NULL); printf("seed = %lld\n", seed); srand((unsigned int)seed); // 2. 产生随机数, 函数能够返回的最大随机数32767 int number = rand(); printf("number = %d\n", number); // 3. 计算指定范围 int lower = 0; int upper = RAND_MAX; // 4. 查看随机数分布:默认是均匀分布 int *record = (int *)malloc(sizeof(int) * (RAND_MAX + 100)); memset(record, 0, sizeof(int) * (RAND_MAX + 100)); for (int i =0; i < 100000000; ++i) { int random_number = rand() % (upper - lower + 1) + lower; record[random_number] += 1; } for (int i = RAND_MAX - 10; i < RAND_MAX + 5; ++i) { printf("%d %d\n", i, record[i]); } }
seed = 1698314607 32757 3012 32758 3073 32759 2966 32760 3004 32761 3041 32762 2993 32763 3018 32764 3080 32765 3069 32766 3001 32767 3040 32768 0 32769 0 32770 0 32771 0
如果产生超出 0-32767 范围的随机数,rand 函数是力不从心的。究其原因就是随机产生的数字范围太小,我们可以在 rand 函数的基础上,构造一个较大的随机数,再将其映射到一个较小的区间内来实现。
#include <stdlib.h> #include <time.h> #include <stdio.h> #include <memory.h> #include <math.h> long long random_int_number(long long lower, long long upper) { // 将负数便宜到正数区间 long long offset = 0; if (lower < 0) { offset = 0 - lower; lower = lower + offset; upper = upper + offset; } // 构造一个随机较大的整数 long double number = 0.0; for (int i = 0; i < 8; ++i) { number += ((rand() % 10) * powl((long double)10, (long double)i)); } // 将较大随机数映射到指定的区间 [lower, upper] 区间 long long result = (long long)number % (upper - lower + 1) + lower; // 再将随机数映射到用户指定的区间 result -= offset; return result; } void test02() { srand((unsigned int)time(NULL)); int arr[21] = { 0 }; for (int i = 0; i < 1000000; ++i) { long long number = random_int_number(-10, 10); arr[number + 10] += 1; } for (int i = 0; i < 20; ++i) { printf("%d %d\n", i-10, arr[i]); } } int main() { test02(); return 0; }
-10 47405 -9 47574 -8 47770 -7 47740 -6 47976 -5 47464 -4 47650 -3 47539 -2 47582 -1 47435 0 47696 1 47647 2 47575 3 47931 4 47458 5 47300 6 47667 7 47983 8 47470 9 47271
如果要生成满足正态分布的随机数,可以使用 Box-Muller 变换,示例代码如下:
double normal_random(double mean, double stddev) { double u1 = (double)rand() / RAND_MAX; double u2 = (double)rand() / RAND_MAX; // Box-Muller 变换是一种用于生成满足标准正态分布(均值为0,标准差为1)的随机数的统计方法 double z = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.1415926 * u2); return mean + stddev * z; }