C++11引入了random頭文件,可以更加得到更精確和功能更完善的隨機數以及相關領域問題。這個標準庫分爲兩大部分,分別是:
- 生成器:定義了用來產生均勻分佈的僞隨機數的機制,也稱爲隨機數引擎(engine)。
- 分佈:以生成器得到的均勻分佈的隨機數序列轉換爲某種特定數學概率分佈的序列,如均勻分佈、正態分佈、泊松分佈等。
生成器
random_device
這是標準庫提供的一個非確定性隨機數生成設備,是所有生成器中唯一不需要隨機數種子的生成方式。在Linux的實現中,是讀取/dev/urandom
設備;在Windows的實現中是調用rand_s
。這種生成器基於隨機過程來產生均勻分佈的隨機數序列,可以視爲一個真隨機數。
random_device
在某些系統中可無法使用,會在構造函數或者調用operator()函數時拋出異常,因此對於移植性而言需要格外注意。
結構
class random_device;
//Member
/**
random_device::min() --- 隨機數範圍的最小值
random_device::max() --- 隨機數範圍的最大值
operator() --- 生成一個真隨機數
entropy() --- 計算operator()調用生成的數的熵,如果random庫使用隨機數引擎(僞隨機數算法)實現而不是真隨機數生成器,那麼這個值爲0
*/
示例
#include <random>
#include <iostream>
int main()
{
std::random_device rd;
std::cout << rd.min() << '\t' << rd.max() << std::endl;
std::cout << rd() << '\t' << rd.entropy() << std::endl;
std::cout << rd() << '\t' << rd.entropy() << std::endl;
return 0;
}
結果如下:
0 4294967295
1282567443 0
3103481474 0
僞隨機數引擎
random庫提供了三種常用的隨機數生成引擎,都以模版類的方式定義。分別是:
- linear_congruential_engine
:線性同餘生成引擎,是最常用也是速度最快的,但隨機效果一般
- mersenne_twister_engine
:梅森旋轉算法,隨機效果最好。
- subtract_with_carry_engine
:滯後Fibonacci算法。
這些生成隨機數的數學算法的原理需要查閱相關數論等數學知識,在此不做涉及。主要關注在標準庫的設計和使用上。
結構
在上述三種生成器模版外,random庫還定義了三種引擎適配器,通過與上述模版的某個實例進行組合,從而定義了10個隨機數生成器模版實例(類)。
///線性同餘引擎的實例類
//x = x * 48271 % 2147483647
typedef linear_congruential_engine<uint_fast32_t, 48271, 0, 2147483647> minstd_rand;
//x = x * 16807 % 2147483647
typedef linear_congruential_engine<uint_fast32_t, 16807, 0, 2147483647> minstd_rand0;
///梅森旋轉引擎的實例類
//狀態大小爲19937bits,生成32bits的隨機數
typedef mersenne_twister_engine<uint_fast32_t, 32,624,397,31,0x9908b0df,11,0xffffffff,7,0x9d2c5680,15,0xefc60000,18,1812433253> mt19937;
//狀態大小爲19937bits,生成64bits的隨機數
typedef mersenne_twister_engine<uint_fast64_t,
64,312,156,31,0xb5026f5aa96619e9,
29,0x5555555555555555,
17,0x71d67fffeda60000,
37,0xfff7eee000000000,
43,6364136223846793005> mt19937_64;
///滯後Fibonacci引擎實例類
typedef subtract_with_carry_engine <uint_fast32_t, 24, 10, 24> ranlux24_base;//生成24bits的隨機數
typedef subtract_with_carry_engine <uint_fast64_t, 48, 5, 12> ranlux48_base;//生成48bits的隨機數
///將隨機數引擎Engine生成的隨機數序列塊中p個元素的r選中,其餘的不使用
template <class Engine, size_t p, size_t r>
class discard_block_engine;
///適配 subtract_with_carry_engine,得到ranlux24,ranlux48兩個生成器模版實例類
typedef discard_block_engine <ranlux24_base, 223, 23> ranlux24;
typedef discard_block_engine <ranlux48_base, 389, 11> ranlux48;
///將Engine生成的隨機數適配到w個位大小的值
template <class Engine, size_t w, class UIntType>
class independent_bits_engine;
///將Engine產生的k個隨機數的順序打亂,內部維護了長度爲k的生成的隨機數的buffer
template <class Engine, size_t k> class shuffle_order_engine;
//適配minstd_rand0
typedef shuffle_order_engine <minstd_rand0,256> knuth_b;
///這是經過對實現平臺的各種判斷後,得到的默認隨機數生成引擎
class std::default_random_engine;
示例
所有生成器引擎,或者經過adapter修飾後的類實例,都提供如下接口供使用
- min:返回最小值,靜態函數
- max:返回最大值,靜態函數
- seed:設置隨機數生成的種子
- operator():產生隨機數
- void discard (unsigned long long z):調用z次operator()函數
另外,都定義了輸入輸出操作符和關係運算的非成員函數。
隨機數引擎接收一個整數作爲種子,不提供就會使用默認值。一般可以使用chrono庫中的時間或者random_device
生成一個隨機數作爲種子。
using clock = std::chrono::high_resolution_clock;
clock::time_point begin = clock::now();
auto seed = begin.time_since_epoch().count();
std::minstd_rand0 rand_gen(seed);
cout << rand_gen() << endl;
分佈
通過強大的生成器引擎得到了均勻分佈的隨機數之後,爲了滿足特定場景的需求,random提供的分佈可以以生成器爲輸入,得到滿足不同分佈的隨機數序列,廣義上看,可以認爲是對生成器的進一步修飾和包裝。主要有三個作用:
- 利用模版參數,改變生成值的類型
- 利用構造函數參數,改變生成值的區間範圍
- 選擇不同的分佈得到滿足不同分佈的隨機數序列
不同的分佈涉及到相應的數學知識,因此僅列出支持的分佈:
//均勻分佈:
uniform_int_distribution //整數均勻分佈
uniform_real_distribution //浮點數均勻分佈
//伯努利類型分佈
bernoulli_distribution //伯努利分佈
binomial_distribution //二項分佈
geometry_distribution //幾何分佈
negative_biomial_distribution //負二項分佈
// Rate-based distributions:
poisson_distribution //泊松分佈
exponential_distribution //指數分佈
gamma_distribution //伽馬分佈
weibull_distribution //威布爾分佈
extreme_value_distribution //極值分佈
//正態分佈相關:
normal_distribution //正態分佈
chi_squared_distribution //卡方分佈
cauchy_distribution //柯西分佈
fisher_f_distribution //費歇爾F分佈
student_t_distribution // t分佈
//分段分佈相關:
discrete_distribution //離散分佈
piecewise_constant_distribution //分段常數分佈
piecewise_linear_distribution //分段線性分佈
對於每種分佈,都定義了相同的接口:
- 構造函數:針對不同分佈提供所需參數,如二項分佈需要給出實驗次數N和每次實驗成功的概率p,其他分佈針對具體需要給出
- operator():使用函數調用運算符生成隨機數,這個函數有一個參數,需要提供使用的生成器對象
- param:返回該分佈的參數
- min、max:最大最小的範圍
- reset:重置分佈的狀態,這樣後續生成的隨機序列不會依賴與之前生成的隨機數
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
std::default_random_engine generator(seed);
std::normal_distribution<double> normalDist(5.0, 2.0);
int p[10] = {};
for(int i = 0; i < 10000; ++i)
{
auto r = normalDist(generator);
if (r >= 0.0 && r < 10) ++p[int(r)];
}
for(int i = 0; i < 10; ++i)
{
std::cout << i << '-' << i+1 << ':';
std::cout << std::string(p[i]/100, '*') << '\n';
}
結果如下:
0-1: *
1-2: ****
2-3: *********
3-4: ***************
4-5: ******************
5-6: *******************
6-7: ***************
7-8: ********
8-9: ****
9-10: *
附加設施
random另外提供了seed_seq
和generate_canonical
兩個額外的設施可以輔助進行隨機數的生成。
- seed_seq
:一個專門用來生成隨機數種子序列的類,生成的都是32bit大小的無符號整數序列,用來在生成器引擎的對象構造時可以作爲參數
- generate_canonical
:將均勻分佈的隨機數映射到[0,1)區間內的一個模版函數。