随机数
约 2272 字大约 8 分钟
2025-04-06
随机数库提供生成随机和伪随机数的类。这些类包括:
- 均匀随机位生成器 (URBG) ,包含随机数引擎,它们是伪随机数生成器,生成拥有均匀分布整数序列的伪随机数生成器,以及真随机数生成器,若可用。
- 随机数分布(例如均匀、正态或泊松分布),它们将 URBG 的输出转换为各种统计分布。
URBG 和分布被设计为相互使用以生成随机值。所有随机数引擎都可以指定地播种、序列化和反序列化,以用于可重复的模拟器。
随机数引擎
随机数引擎以种子数据为熵源生成伪随机数。数种不同类的伪随机数生成算法实现为能定制的模板。
引擎 | 描述 |
---|---|
linear_congruential_engine | 实现线性同余算法 |
mersenne_twister_engine | 实现梅森缠绕器算法 |
subtract_with_carry_engine | 实现带进位减(一种延迟斐波那契)算法 |
选择使用哪个引擎涉及到许多权衡:
- 线性同余引擎速度适中,并且对状态的存储需求非常小。
- 延迟的斐波那契生成器即使在没有高级算术指令集的处理器上也非常快,但代价是更大的状态存储和有时不太理想的频谱特性。
- 梅森缠绕器引擎更慢,有更大的状态存储要求,但在正确的参数下,它具有最长的非重复序列,具有最理想的光谱特性(对于给定的理想定义)。
随机数引擎适配器
随机数引擎适配器生成以另一随机数引擎为熵源的伪随机数。它们通常用于改换底层引擎的谱特性。
适配器 | 描述 |
---|---|
discard_block_engine | 舍弃随机数引擎的某些输出 |
independent_bits_engine | 将一个随机数引擎的输出打包为指定位数的块 |
shuffle_order_engine | 以不同顺序发送一个随机数引擎的输出 |
预定义随机数生成器
定义了数个特别的流行算法。
类型 | 定义 |
---|---|
minstd_rand0 | std::linear_congruential_engine<std::uint_fast32_t, 16807, 0, 2147483647> 由 Lewis、Goodman 及 Miller 发现于 1969,由 Park 与 Miller 于 1988 采纳为“最小标准” |
minstd_rand | std::linear_congruential_engine<std::uint_fast32_t, 48271, 0, 2147483647> 较新的“最小标准”,为 Park、 Miller 及 Stockmeyer 于 1993 推荐 |
mt19937 | std::mersenne_twister_engine<std::uint_fast32_t 32, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253> 32 位梅森缠绕器,由松本与西村设计于 1998 |
mt19937_64 | std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31, 0xb5026f5aa96619e9, 29, 0x5555555555555555, 17, 0x71d67fffeda60000, 37, 0xfff7eee000000000, 43, 6364136223846793005> 64 位梅森缠绕器,由松本与西村设计于 2000 |
ranlux24_base | std::subtract_with_carry_engine<std::uint_fast32_t, 24, 10, 24> |
ranlux48_base | std::subtract_with_carry_engine<std::uint_fast64_t, 48, 5, 12> |
ranlux24 | std::discard_block_engine<std::ranlux24_base, 223, 23> 24 位 RANLUX 生成器,由 Martin Lüscher 与 Fred James 设计于 1994 |
ranlux48 | std::discard_block_engine<std::ranlux48_base, 389, 11> 48 位 RANLUX 生成器,由 Martin Lüscher 与 Fred James 设计于 1994 |
knuth_b | std::shuffle_order_engine<std::minstd_rand0, 256> |
default_random_engine | 实现定义(mt19937) |
使用随机数生成器
一般情况下,我们使用default_random_engine
即可!
随机数生成
先创建随机数生成器对象。
std::default_random_engine gen;
然后就可以使用它来生成随机数了。
std::cout << gen() << std::endl; // engine.operator()();
//output:3499211612
因为重载了operator(),所以可以直接使用对象()来生成随机数。
要生成多个随机数,调用多次即可!
for (size_t i = 0; i < 5; i++)
{
std::cout << gen() << " ";
}
//output:581869302 3890346734 3586334585 545404204 4161255391
根据输出结果可以看到生成的数据挺大的,那么它能生成的最大值是多少,最小值又是多少呢?
std::cout << "min:" << gen.min() << ",max:" << gen.max() << std::endl;
//output: min:0,max:4294967295
设置种子
在上述的测试代码中,如果我们运行多次,就会发现,每次生成的随机数序列都是一样的。因为默认的随机数种子是确定的。
static constexpr result_type default_seed = 5489U;
这个默认种子,在mersenne_twister_engine
类中定义。
如果想要每次运行产生的随机数序列不一样,那么需要手动设置随机数种子。
time
用时间作为随机数种子是最合适的,下列代码分别用两种方式获取时间设置了随机数种子。
std::default_random_engine gen(time(nullptr));
//std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count());
seed_seq
除此之外,还可以给随机数生成器指定一个种子序列。
std::seed_seq seed_seq{time(nullptr), 567LL};
std::default_random_engine gen(seed_seq);
std::seed_seq还提供了获取种子序列参数和大小的方法。
void test()
{
std::seed_seq seq{1,3,5,7,9};
std::vector<int> seq_vec(seq.size());
seq.param(seq_vec.begin());
for (auto& v : seq_vec)
{
std::cout << v << " ";
}
//1 3 5 7 9
}
还可以随机数填充区间。
std::seed_seq seq{time(nullptr)};
std::vector<uint32_t> int_list(5);
seq.generate(int_list.begin(), int_list.end());
for (auto& v : int_list)
{
std::cout << v << " ";
}
random_device
真随机数,不过去取决于具体实现,在Linux下一定是真随机的,因为Linux有熵池;在Windows下目前测试也是随机的,而不是确定的序列。( ・´ω`・ )
Linux内核采用熵来描述数据的随机性。熵(entropy)是描述系统混乱无序程度的物理量,一个系统的熵越大则说明该系统的有序性越差,即不确定性越大。在信息学中,熵被用来表征一个符号或系统的不确定性,熵越大,表明系统所含有用信息量越少,不确定度越大。
计算机本身是可预测的系统,因此,用计算机算法不可能产生真正的随机数。但是机器的环境中充满了各种各样的噪声,如硬件设备发生中断的时间,用户点击鼠标的时间间隔等是完全随机的,事先无法预测。Linux内核实现的随机数产生器正是利用系统中的这些随机噪声来产生高质量随机数序列。
内核维护了一个熵池用来收集来自设备驱动程序和其它来源的环境噪音。理论上,熵池中的数据是完全随机的,可以实现产生真随机数序列。为跟踪熵池中数据的随机性,内核在将数据加入池的时候将估算数据的随机性,这个过程称作熵估算。熵估算值描述池中包含的随机数位数,其值越大表示池中数据的随机性越好。
我们可以使用random_device生成随机数,而不需要设置随机数种子。
std::random_device rd;
for (size_t i = 0; i < 10; i++)
{
std::cout << rd() << " ";
}
也可以把random_device生成的数,作为为随机数生成器的种子。
std::random_device rd;
std::default_random_engine gen(rd());
随机数分布
随机数分布后处理 URBG 的输出,以使得输出结果按照定义的统计概率密度函数分布。
均匀分布
uniform_int_distribution
生成随机整数值 i,均匀分布于闭区间 [a,b]。
std::uniform_int_distribution uni_int_dis(2,10);
std::cout << uni_int_dis(gen) << " "; //生成[2,10]之间的整数
std::cout << uni_int_dis(gen,5) << " "; //生成[0,5]之间的整数
std::cout << uni_int_dis(gen, std::uniform_int_distribution<int>::param_type {5,12}) << " "; ////生成[5,12]之间的整数
uniform_real_distribution
生成随机浮点数x,均匀分布在区间 [a,b)。
std::uniform_real_distribution uni_real_dis(2.0,10.0);
std::cout << uni_real_dis(gen) << " "; //生成[2.0,10.0)之间的浮点数
std::cout << uni_real_dis(gen, std::uniform_real_distribution<double>::param_type(0.0,1.0)) << " "; ////生成[0,1)之间的浮点数
除此之外还有一个方便的函数,用来生成[0,1)之间的浮点数。
template <class _Real, size_t _Bits, class _Gen>
_Real generate_canonical(_Gen& _Gx);
举例:
std::cout << std::generate_canonical<double, 10>(gen) << " \n";
伯努利分布
bernoulli_distribution
根据离散概率函数产生随机布尔值。true的概率为p,false的概率为(1-p)。
std::bernoulli_distribution bd(0.25);
std::map<bool, int> map;
for (size_t i = 0; i < 10000; i++)
++map[bd(gen)];
std::cout
<< map[true] << " "
<< map[false]<<" "
<< map[true]/1000.0 //计算true的比率,是否约等于0.25
<< std::endl;
binomial_distribution
生产随机非负整数值 i ,分布依照离散概率函数:
P(i∣t,p)=(it)⋅pi⋅(1−p)t−i
获得的值是 t 次是/否实验序列中的成功次数,每次成功的概率为 p
negative_binomial_distribution
产生负二项分布上的整数值
geometric_distribution
产生几何分布上的整数值。
泊松分布
poisson_distribution
exponential_distribution
gamma_distribution
weibull_distribution
extreme_value_distribution
正态分布
normal_distribution
产生上的实数值。
lognormal_distribution
产生对数正态分布上的实数值。
chi_squared_distribution
产生 χ2 分布上的实数值。
cauchy_distribution
产生柯西分布上的实数值。
fisher_f_distribution
产生费舍尔 F 分布上的实数值。
student_t_distribution
产生学生 t 分布上的实数值。
采样分布
discrete_distribution
产生离散分布上的随机整数。
piecewise_constant_distribution
产生分布在常子区间上的实数值。
piecewise_linear_distribution
产生分布在定义的子区间上的实数值。