C/C++ 随机数

C/C++ 随机数

在 C/C++ 里,生成随机数是一项很常见的任务,常用在模拟、游戏、密码学和统计等场景。生成时一般要指定它服从均匀分布还是正态分布。下面我们就来看看,怎么在 C 和 C++ 里生成满足均匀分布和正态分布的随机数。要做到这件事,通常分三步:

  1. 设置随机数种子
  2. 根据种子值生成随机数
  3. 将随机数映射到某个分布内

1. C++ 随机数

C++ 里用 random_device 作为随机数种子生成器,它是一个硬件随机数设备,通常用来生成高质量的随机数种子。

C++ 里的随机数生成器有好几种,比如 Mersenne Twister,还有线性同余生成器(Linear Congruential Generator,简称 LCG),我们一般选随机性更好的前者。Mersenne Twister 有 32 位和 64 位两个版本,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 << "number5 = " << number5 << 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
number5 = 0.618274
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;
}