一个随机数是在一个范围内产生的,从一个最小数到一个最大数。假设这些最小和最大的数字都大于1,那么在这个范围内产生的数字就是num。让最小数为min,让最大数为max。有了这些,为了将数字转换到0和1之间,使用公式。
random_number= (num- min)/(max - min)
random_number现在应该在0和1之间。
接下来的问题是如何生成随机数以及如何决定min和max。事实上,C++20规范所描述的随机数,实际上是伪随机数。C++20规范给出了产生真正的随机数(非决定性随机数)的指南。这种真正的随机数生成器的问题是,编译器的责任,或者说程序员的责任,是提供被认为是非确定性随机数生成的算法。本文不涉及非确定性的随机数。
伪随机数是以数字的序列(一种顺序)产生的,看起来像随机数。随机数的生成需要一个所谓的种子。种子是一些起始值。这篇文章解释了C++20中随机数生成的基础知识。如果生成的数字大于1,就用上述公式将其降到0和1之间。必须在程序中包含C++ 库,才能有一个随机的或者说随机的数字序列。
文章内容
分布
均匀分布
均匀分布是指一个数字的概率是序列中的数字总数的1。考虑下面的序列。
0, 10,20,30,40,50,60,70,80,90,100
如果这11个数字是一个随机数字序列,每个数字在11次出现中出现过一次。这意味着它是一个均匀分布。在实践中,不一定都会出现一次。一个或两个或三个可能会出现不止一次,而且它们不会按常规顺序出现。
如果返回的随机数是40,那么程序必须用以下方法将随机数转换为0和1之间的数字
random_number= (40-0)/(100- 0)
= 4/10 = 0.4
这里,num是40;min是0,max是100。
二项式分布
二项分布不是一种均匀分布。"Bi "是二项式的前缀,意味着两个。二项分布中的值的数量在C++中用t表示。如果该分布所涉及的bi数是2和3,如果t是1,那么序列就是。
2,3
如果t是2,同样的双数(2和3),那么序列就变成了。
4,12,9
如果t是3,对于相同的双数(2和3),那么这个序列就变成了。
8,36,54,27
如果t是4,同样的双数(2和3),那么序列就变成了。
16,96,216,216,81
t是一个正整数,可以超过4。对于t的每个值,序列中有t+1个元素。一个序列取决于所选择的双数和t的值。双数可以是任何一对,例如,13和17。双数的总和也很重要。一个序列是由所谓的二项式定理发展而来的。
在C++的随机库中还有其他分布。
线性_共轭_引擎
C++中有许多随机数引擎。 linear_congruential_engine就是其中之一。这个引擎取一个种子,用一个乘法器相乘,并在乘积上加上一个常数c,从而得到第一个随机数。第一个随机数成为新的种子。这个新的种子被乘以同样的'a',其乘积被添加到同样的c,从而得到第二个随机数。这第二个随机数成为下一个随机数的新种子。根据程序员的要求,这个过程可以重复多少个随机数。
这里的种子有一个索引的作用。默认的种子是1。
linear_congruential_engine的语法是。
linear_congruential_engine<classUIntType, UIntType a, UIntType c, UIntType m> lce
lce是程序员选择的名称。这个语法使用默认的1的种子。这里的第一个模板参数应该用 "无符号int "来专门化。第二个和第三个应该有 "a "和c的实际值,第四个应该有预期最大随机数的实际值,再加上1。
假设需要一个值为2的种子,那么语法将是。
linear_congruential_engine<classUIntType, UIntType a, UIntType c, UIntType m> lce(2)
注意lce后面的括号里的种子。
下面的程序,说明了 linear_congruential_engine 的使用,默认的种子为 1。
#include
#include
using namespacestd;
int main()
{
linear_congruential_engine<unsigned int,3,1,500>lce;
cout <<lce() <<endl;
cout <<lce() <<endl;
cout <<lce() <<endl;
cout <<lce()<<endl;
cout <<lce() <<endl;
cout <<endl;
cout <<lce.min () <<endl;
cout <<lce. max () <<endl;
return 0;
}
输出结果是。
4
13
40
121
364
0
499
注意引擎的lce对象被实例化的方式。这里,'a'是3,c是1,最大的、希望达到的数字m是500。m实际上是一个模子--见后面。它是一个运算符,用于返回输出序列中引擎所需的下一个随机数。这个方案的最小值是0,最大值是499,这些可以用来将返回的数字转换为0和1之间--见下文。
返回的第一个随机数是4,它等于1 X 3 + 1 = 4。4成为新的种子。下一个随机数是13,它等于4 X 3 + 1 = 13。13成为新的种子。下一个随机数是40,它等于13 X 3 + 1 = 40。这样一来,后面的随机数是121和364。
下面的代码,说明了Linear_congruential_engine的使用情况,种子为2。
linear_congruential_engine<unsigned int,3,1,1000>lce(2);
cout <<lce() <<endl;
cout <<lce() <<endl;
cout <<lce() <<endl;
cout <<lce() <<endl;
cout <<lce() <<endl;
cout <<endl;
cout <<lce.min () <<endl;
cout <<lce. max () <<endl;
输出结果是。
7
22
67
202
607
0
999
这里希望的最大随机数是1000。这个方案的最小值仍然是0,最大值现在是999,这些可以用来将返回的数字转换为0和1之间--见下文。
第一个返回的随机数是7,它等于2 X 3 + 1 = 7,7成为新的种子。下一个随机数是22,它等于7 X 3 + 1 = 22。22成为新的种子。下一个随机数是67,它等于22 X 3 + 1 = 67。这样一来,后面的随机数是202和607。
下面的代码使用上述公式来产生一个介于0和1之间的随机数,用于这个引擎。
linear_congruential_engine<unsigned int,3,1,1000>lce(2);
无符号 intnum=lce();// 正常的随机数
无符号 intmin=lce.min();
unsigned intmax=lce.max();
floatrandom_number=((float)(num -min))/((float)(max- min));
cout <<random_number<<endl;
输出结果是
0.00700701
这里,num是7,所以
random_number= (7-0)/(999-0) = 7/999 = 0.00700701,四舍五入到小数点后8位。
linear_congruential_engine不是随机库中唯一的专门引擎;还有其他的。
default_random_engine
这就像一个通用的引擎。它产生随机数。序列的顺序不保证是不确定的。然而,程序员很可能不知道这个顺序。下面两行显示了如何使用这个引擎。
random_device rd;
default_random_engine eng(rd())。
random_device是一个类,rd已经被实例化了。注意引擎的参数列表中rd的括号。分配器需要这个引擎进行操作--见下文。
随机数分布类
uniform_int_distribution
uniform_int_distribution
任何数字出现的概率是1除以该类数字的总数。例如,如果有十个可能的输出数字,每个数字被显示的概率是1/10。下面的代码说明了这一点。
random_device rd;
default_random_engine eng(rd());
uniform_int_distributiondist(3, 12);
cout <<dist(eng ) <<' 'dist(eng ) <<' 'dist(eng )<<' ' <dist(eng ) <<' <dist(eng ) <<' <endl;
cout <<dist(eng )<<' '< dist(eng)<<dist(eng )<<' <dist(eng ) <<'<dist(eng )<< '<dist(eng )' <endl;
作者电脑的输出结果是。
9 8 3 5 12
7 4 11 7 6
不幸的是,7出现了两次,却牺牲了10。dist的参数是数字3和13(包括10个连续的整数)。 dist(eng)是一个返回下一个数字的运算符。它使用了引擎。注意使用int模板的特殊化。
在这种情况下,没有必要寻找num、min和max,然后使用上述公式来获得0和1之间的数字,这是因为这个类有一个使用float特殊化的float等价物。每次运行的输出结果都不会相同。
uniform_real_distribution
uniform_real_distribution与uniform_int_distribution类似。有了它,为了获得一个介于0和1之间的数字,只需使用0和1作为参数。下面的代码说明了这一点。
random_device rd;
default_random_engine eng(rd());
uniform_real_distributiondist(0, 1);
cout <<dist(eng)< ' <dist(eng )<' <dist(eng)<<' ' <dist(eng ) <<' <dist(eng ) <<' <endl;
cout <<dist(eng )<<' '< dist(eng)<<dist(eng )<<' <dist(eng ) <<'<dist(eng )<< '<dist(eng )' <endl;
作者电脑的输出结果是。
0.384051 0.745187 0.364855 0.122008 0.580874
0.745765 0.0737481 0.48356 0.184848 0.745821
注意使用浮动模板的特殊化。每次运行的输出结果都不会相同。
二项分布(binomial_distribution
有了这个分布,每个输出数字的概率就不一样了。上面已经说明了binomial_distribution。下面的代码显示了如何使用二项分布来产生10个随机数。
random_device rd;
default_random_engine eng(rd());
binomial_distributiondist(10);
cout <<dist(eng ) <<' ' <dist(eng ) <<'' <dist(eng )<<' ' <dist(eng ) <<'<dist(eng ) <<' <endl;
cout <<dist(eng )<<' '< dist(eng)<<dist(eng )<<' <dist(eng ) <<'<dist(eng )<< '<dist(eng ) ' <endl;
作者电脑的输出结果是。
5 3 5 5 7
6 6 5 8 3
每次运行的输出结果都不会相同。这里使用的模板特殊化是int。
下面的代码使用上述公式来产生一个介于0和1之间的随机数,对于这个分布。
random_device rd;
default_random_engine eng(rd());
binomial_distributiondist(10);
unsigned intnum=dist(eng);// normal random number
unsigned intmin=dist.min();
unsigned intmax=dist.max();
cout <<min<<endl;
cout << max << endl;<max<<endl;
cout <<endl;
cout <<num<<endl;
floatrandom_number=((float)(num -min))/((float)(max- min));
cout <<random_number<<endl;
作者电脑的输出结果是:。
0
10
7
0.7
更好的随机数
UNIX Epoch以来的秒数可以作为种子。黑客要知道种子就变得很困难。下面的程序用linear_congruential_engine说明了这一点。
#include
#include
#include
using namespacestd;
int main()
{
const autop1=chrono::system_clock::now();
unsigned intseed=chrono::duration_caststd::chronono::seconds(p1.time_since_epoch())。count();
linear_congruential_engine<unsigned int,3,1,1000>.lce(seed);
cout <<lce() <<'' <lce() <<<' ' <lce() <<'' <lce()<< '' <lce() <<''<<endl;
cout <<endl;
cout <<lce.min () <<endl;
cout <<lce. max () <<endl;
return 0;
}
作者电脑的输出结果是。
91 274 823 470 411
0
999
请注意,CHRONO库已被包括在内。每次运行的输出都是不同的。
结论
要想获得0到1之间的随机数,最简单的方法是使用random_device、default_random_engine和uniform_real_distribution(参数为0和1)。任何其他的引擎或分布可能需要使用公式,随机数=(num - min)/(max - min)。