rand()
和 srand()
<cstdlib>
中提供的 rand()
是比较常用的生成随机数的方法。它可以通过线性同余法提供一个 中的伪随机数,类型为 int
。
1 |
根据定义,RAND_MAX
大小为 ,也就是说 rand()
生成的数字将会落在 这个区间内。
rand()
本身规则是以 1 为种子,如果在同一个程序中多次运行 rand()
,可以得到不同的结果。不过,下一次运行程序时将会重复一模一样的序列,某种程度上,这样的随机数失去了意义。
解决方法一般是用 srand()
为 rand()
进行初始化。srand()
接受一个无符号整型作为输入,这个值将会作为种子供 rand()
进行迭代。srand()
可以多次使用,每次使用都会让 rand()
使用新种子开启新一轮迭代。
显然,如果 srand()
每次都接收到一个相同的值,那么这个初始化也就没有意义了。因此,一般使用一些会流变的特殊值进行初始化,其中最为方便的就是时间。
<ctime>
中提供了 time()
,它可以获取自 1970 年 1 月 1 日以来经过的秒数,类型为 time_t
,是一个整型。这个函数可以返回一个值,允许直接把返回值赋给 int
类型。同时,函数也允许提供一个类型为 time_t
的指针作为参数,返回值会写入指针地址。这个参数允许空指针。需要注意的是,由于 srand()
接受的是一个无符号整型,所以在使用时应该进行类型转换。
另一种方法借助于 <windows.h>
中提供的 GetTickCount()
函数,它返回自系统启动以来经过的毫秒数,类型为 DWORD
。该类型本身就是一个无符号整型,可以直接传递给 srand()
。这种方法的好处是精度更高(毫秒级),可以一定程度上避免并行时获取到相同种子。
1 | // 使用例 |
<random>
从 C++11 开始,<random>
被引入。这个库允许定义随机数生成工具,并且允许对随机数的分布进行定义。
均匀随机数生成器
<random>
库中定义了一些均匀随机数生成器(Uniform Random Number Generator, URNG),他们被称为引擎。这些引擎可以生成范围内的均匀随机数。比较常见的有以下几个:
mt19937
:应用梅森旋转算法,32 位字长。mt19937_64
:同样是梅森旋转算法,不过字长为 64 位。knuth_b
:应用高德纳随机算法。minstd_rand
:应用线性同余法。ranlux24
:应用 Ranlux 算法,生成字长为 24 位的随机数。ranlux24_base
:ranlux24
的基础引擎。ranlux48
/ranlux48_base
:同上,但生成字长为 48 位的随机数。default_random_engine
:默认引擎,不过不清楚实际调用了哪个引擎。(不推荐使用)
以上这些都可以在范围内生成均匀的随机数。在使用时可以按照以下方法:
1 | mt19937 E((unsigned)time(NULL)); // 定义引擎类型,此时可以传入种子 |
分布类型
<random>
还允许对分布类型进行定义。常见的分布类型有:
- 均匀分布
uniform_int_distribution
:给定闭区间内生成整数的均匀分布。uniform_real_distribution
:给定左闭右开区间内生成浮点数的均匀分布。generate_canonical
:生成 内均匀的浮点数分布,可以指定精度。
- 伯努利分布
bernoulli_distribution
:生成布尔值的伯努利分布。binomial_distribution
:生成整数值的二项式分布。geometric_distribution
:生成整数值的几何分布。negative_binomial_distribution
:生成整数值的负二项式分布。
- 正态分布
cauchy_distribution
:生成浮点值的柯西分布。chi_squared_distribution
:生成浮点值的卡方分布。fisher_f_distribution
:生成浮点值的 F 分布(Fisher-Snedecor 分布)。lognormal_distribution
:生成浮点值的对数正态分布。normal_distribution
:生成浮点值的正态分布(高斯分布)。student_t_distribution
:生成浮点值的学生 t 分布。
- 泊松分布
exponential_distribution
:生成浮点值的指数分布。extreme_value_distribution
:生成浮点值的极值分布。gamma_distribution
:生成浮点值的伽马分布。poisson_distribution
:生成整数值的泊松分布。weibull_distribution
:生成浮点值的韦伯分布。
- 抽样分布
discrete_distribution
:生成离散整数分布。piecewise_constant_distribution
:生成浮点值的分段常数分布。piecewise_linear_distribution
:生成浮点值的分段线性分布。
不同的分布方法允许不同的传入参数,举例来说,normal_distribution
允许传入两个参数,前者为期望,后者为标准差。
1 | mt19937 e; |
需要注意的是,尖括号内虽然允许传递模板参数,但不能传递不同的数据类型。举例来说,uniform_int_distribution
后允许传入 short
、int
、long long
等整型,但不允许传入 float
、double
等浮点型。
random_device
random_device
也是一种 URNG,但是不同之处是,random_device
可以获取真随机数。真随机数可能来源于 Linux 维护的熵池,在 Windows 上可能来源于微软的加密 API,在没有获取真随机数的途径时,它会生成伪随机数。
这种方法的优点就在于其真随机性,但是弊端也很明显:较慢的获取速度,同时熵池是有限的,在无法获取真随机数时,random_device
改为生成伪随机数。这表明 random_device
是不稳定的,并不适合直接作为随机数生成器调用多次。
一般来说,random_device
的用法是代替时间函数作为种子。用 random_device
生成一个真随机数,然后把这个随机数作为种子传递给其他伪随机数生成器。
1 | // 使用例 |
总结
一般情况下,rand()
配合时间函数作为种子,可以生成比较令人满意的随机数。在有特殊的对于随机数分布的要求或者是对于随机浮点数的需求时,可以调用 <random>
来实现。