日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

C++标准库中的随机数生成

發(fā)布時間:2024/9/30 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++标准库中的随机数生成 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

C++標(biāo)準(zhǔn)庫中的隨機(jī)數(shù)生成

一、偽隨機(jī)與真隨機(jī)

數(shù)字計算機(jī)的結(jié)果可以說是固定的、必然的。都是根據(jù)現(xiàn)有數(shù)據(jù)的狀態(tài)得出接下來的狀態(tài)。除非硬件損壞,計算機(jī)不會產(chǎn)生真正的隨機(jī)和無法預(yù)料的事。在生活中隨手拋一個硬幣也受到出手動作、狀態(tài),以及風(fēng)速等環(huán)境影響。一只蟑螂的運動路線也可能不是隨機(jī)的。但是在隔絕的環(huán)境中一個分子的運動方向可以說是隨機(jī)的。在量子力學(xué)中的測不準(zhǔn)原理和波函數(shù)坍縮,都說明隨機(jī)是一個概率,這個概率是定值可估計的,但是具體在哪兒卻是隨機(jī)的,人類并不能掌控計算它。所以人造出來的計算機(jī)也無法實現(xiàn)真正的隨機(jī),僅僅是模擬出來接近隨機(jī)的效果,所以叫做偽隨機(jī)。即在初始條件一定的情況下,產(chǎn)生的隨機(jī)數(shù)結(jié)果是固定的。在玩許多可以生成隨機(jī)地圖的游戲時,很多游戲會提供一個種子值,固定的種子值會生成一模一樣的地圖。所以偽隨機(jī)也不一定沒有優(yōu)點,至少程序員不期望bug的出現(xiàn)是隨機(jī)的。在非硬件問題(比如太陽耀斑之比特翻轉(zhuǎn))的邏輯結(jié)果上,如果出現(xiàn)的結(jié)果與推導(dǎo)得出的結(jié)果不同,就說明推導(dǎo)過程錯誤。在這一點上,計算機(jī)模擬的世界就和人所在的世界大有不同,所以人工智能的什么就再等下輩子了,現(xiàn)有的AI只是建立在數(shù)學(xué)統(tǒng)計學(xué)的模型之上的,從理論上我就覺得不會有多么智能,除非換一種原理出發(fā),比如從大腦怎么運作的,人類如何思考的。

所以,程序上的隨機(jī)數(shù)一般都是通過生成一個種子值作為初始狀態(tài),再通過遞歸算法根據(jù)這個值計算出下一個值。只是按這個算法產(chǎn)生的數(shù)字序列分布看起來是隨機(jī)的。比如高中的數(shù)學(xué)課本后面就有偽隨機(jī)數(shù)表,幾頁的數(shù)字序列雖然本身是固定的。但我們可以隨便選一個數(shù)字(比如擲骰子),然后取百位往后得到另一個數(shù)字。比如百位是8就往后得到往后8個的數(shù)字,按這種方法也就取出了新的序列,雖然實際是確定的,但只要起點和規(guī)則不同,這個新的數(shù)列也可以作為一般的隨機(jī)用途了。

二、常用實現(xiàn)

C和C++的許多代碼都如下生成隨機(jī)數(shù):

//以距1970年1月1號的0點的秒數(shù)作為種子值(注意long轉(zhuǎn)到了uint) srand(time(0));//返回0至RAND_MAX,RAND_MAX被定義為2147483647 int num = rand();//分布到 [min, max) num = min + num % (max - min);

一直以來我都是使用的HGE引擎的算法,如下:

//省略了參數(shù)檢查 UINT32 Math::g_seed = 0; int DND::Math::GetRandInt(int min, int max) {g_seed = 214013 * g_seed + 2531011;return min + (g_seed ^ g_seed >> 15) % (max - min + 1); }float DND::Math::GetRandFloat(float min, float max) {g_seed = 214013 * g_seed + 2531011;return min + (g_seed >> 16)*(1.0f / 65535.0f)*(max - min); }void DND::Math::SetSeed(UINT32 seed) {if (!seed) g_seed = (UINT32)time(0);else g_seed = seed; }

三、標(biāo)準(zhǔn)庫的使用

在實際的使用中,我嚴(yán)重懷疑了上述代碼的可靠性。至于C語言的rand函數(shù)我就感覺更有局限性了。于是我決定花點時間用上新一點的、更強(qiáng)大的標(biāo)準(zhǔn)庫實現(xiàn)。標(biāo)準(zhǔn)庫提供了21個概率分布類,我只列舉幾個常用的:

1.離散均勻分布(整數(shù))

//返回[min, max]的概率相等,返回整型//定義種子發(fā)生器 random_device rd;//定義隨機(jī)數(shù)序列生成器rng,傳入無符號整型作為種子,rd()會返回一個隨機(jī)值(或許比time(0)好) default_random_engine rng {rd()};//指定類型和范圍 [1, 100] uniform_int_distribution<int> dist {1, 100};//返回隨機(jī)數(shù) [1, 100] dist(rng);

2.連續(xù)均勻分布(實數(shù))

//返回[min, max)的概率相等,返回浮點數(shù),注意前閉后開 //忽略了 序列生成器和種子值//指定類型和范圍 [0, 50) uniform_real_distribution<float> dist {0.0f, 50.0f};//返回隨機(jī)數(shù) [0, 50) dist(rng);

3.正態(tài)分布

也叫高斯分布,它的曲線由期望和期望差決定。期望為所有數(shù)據(jù)的平均值,也是最有概率出現(xiàn)的值。標(biāo)準(zhǔn)差為方差的算術(shù)平方根,代表著隨差值范圍出現(xiàn)概率的變化程度。比如期望為50,標(biāo)準(zhǔn)差為10。那么最可能出現(xiàn)的數(shù)字是50。小于40和大于60的概率相同,小于32%。隨機(jī)到小于30和大于70的值的概率小于5%。在確定一顆果樹掉落幾顆蘋果的時候,填入(10,2)。那么玩家最可能獲得10個蘋果,然后獲得小于8個或大于12個的概率大概三分之一。當(dāng)然也有二十分之一概率獲得小于6個或者大于14個。(注意正態(tài)分布只支持實數(shù),至于轉(zhuǎn)換為整數(shù)或許四舍五入會更符合需求)

//指定期望和標(biāo)準(zhǔn)差 (10.0, 2.0) normal_distribution<double> dist {10.0, 2.0};//返回10的概率最高 round(dist(rng));

4.對數(shù)分布?

對數(shù)分布與正態(tài)分布類似,即隨機(jī)變量的對數(shù)符合正態(tài)分布,即log x符合正態(tài)分布,x的概率分布就是對數(shù)分布。曲線特點是較快的到達(dá)高峰然后拖一條長尾巴。

//指定期望和標(biāo)準(zhǔn)差 (5.0, 0.5) lognormal_distribution<> dist {5.0, 0.5};dist(rng);

5.抽樣離散分布?

假設(shè)我們解決這樣一個問題:一個農(nóng)場出現(xiàn)3種不同動物的概率百分比分別是30、30、40,然后一共有N只動物,需要隨機(jī)生成每一種。一般想到的代碼實現(xiàn),只需要用離散均勻分布生成0到99的數(shù)字,然后判斷范圍即可知道生成哪一種,然后迭代N次即可。不過當(dāng)新加一種動物的時候,或者改變某一種動物的出現(xiàn)概率時,就需要改動很多的代碼,并且一大串的if-else看上去也很不美觀。用抽樣分布即可,所有項的概率之和不需要為1,也就是說用權(quán)值代替每一項的出現(xiàn)概率。

//指定權(quán)值 discrete_distribution<size_t> dist {10, 20, 15, 5, 99};//注意這個返回的范圍是索引 [0, 4] //所以返回4的概率最高,返回3的概率最低 dist(rng);

一般來說權(quán)值的值和個數(shù)應(yīng)該是動態(tài)變化的,如下改變:

//使用新的權(quán)值(個數(shù)不限) dist.param({2,2,2,3,4,7,9});

6.抽樣分段常數(shù)分布?

這個分布需要指定兩個列表,一個說明了數(shù)軸如何分段,第二個指定了每一段的權(quán)值。由于n個值可以分成n-1段,所以第二個列表長度應(yīng)該是第一個列表長度減一。其中每一段內(nèi)的數(shù)字出現(xiàn)概率相同。

//定義分段,分為了 [0, 60),[60, 90), [90, 100) //連續(xù)分布均為前閉后開區(qū)間 vector<float> b {0, 60, 90, 100};//指定3段的權(quán)值 vector<float> w{7, 5, 9};//應(yīng)用數(shù)據(jù) piecewise_constant_distribution<> d {begin(b), end(b), begin(w)};

7.分段線性分布

這個和前一個類似,只不過權(quán)值列表多一個,和分段的邊界一一對應(yīng),因為它表示的不是區(qū)間的權(quán)值了,而是區(qū)間邊界的權(quán)值。意思就是0分出現(xiàn)的權(quán)值是7,而60分出現(xiàn)的權(quán)值是5,那么30分的權(quán)值可計算得出是6。而前一個分段常數(shù)分布的權(quán)值指的是[0,60)的權(quán)值為7。

//定義分段,分為了 [0, 60),[60, 90), [90, 100) //連續(xù)分布均為前閉后開區(qū)間 vector<float> b {0, 60, 90, 100};//指定4個邊界的權(quán)值,區(qū)間的權(quán)值由邊界權(quán)值線性計算 //例如可以假想 30分的權(quán)值為 6 vector<float> w{7, 5, 9, 2};//應(yīng)用數(shù)據(jù) piecewise_linear_distribution<> d {begin(b), end(b), begin(w)};

8.其它分布

其他分布的類名如下,每一種都有各自適用的模型,使用形式都差不多,想要了解更多的可以再查資料^_^。

泊松分布:poisson_distribution

幾何分布:geometric_distribution

指數(shù)分布:exponetial_distribution

伽馬分布:gamma_distribution

威爾分布:weibull_distribution

二項式分布:binomial_distribution

負(fù)二項式分布:negative_binomial_distribution

極值分布:extreme_value_distribution

PS:概率密度函數(shù)簡稱PDF(Probability Density Function)

9.其他隨機(jī)數(shù)序列的生成器

看了這么多分布后,我們應(yīng)該知道了生成一個隨機(jī)數(shù)大致分為了三步。第一步是設(shè)定種子值起點,第二步生成隨機(jī)數(shù)序列,第三步應(yīng)用分布。而STL提供的default_random_engine生成器是默認(rèn)的隨機(jī)數(shù)序列生成器,它生成隨機(jī)的無符號整型序列。雖然是無符號整型,但也可以用到所有分布上。至于分布類是如何完成和保證這些結(jié)果正確的,確實有些像變魔術(shù)。

所以額外的標(biāo)準(zhǔn)庫還提供了不同的隨機(jī)數(shù)序列生成器。如下:

線性同余引擎:minstd_rand(32位無符號整型)、minstd_rand0、knuth_b

馬特賽特旋轉(zhuǎn)算法引擎:mt19937(32位無符號整型)、mt19937_64(64位無符號整型)

帶進(jìn)位減法引擎:ranlux24_base(24位整數(shù))、anlux48_base(48位整數(shù))、ranlux24、ranlux48

其中minstd_rand是minstd_rand0的改進(jìn)版本,ranlux24和ranlux48是base版本產(chǎn)生的序列舍棄一大部分得來的。不過我們用default_random_engine默認(rèn)生成器就夠了。

四、原樣封裝一下

由于已經(jīng)有很多地方使用了現(xiàn)有的隨機(jī)數(shù)接口,所以暫時不可能大幅度的修改接口。不過只替換一下實現(xiàn)倒是沒問題,等以后重寫的時候再寫得好看點(再說標(biāo)準(zhǔn)庫實現(xiàn)得這么好,也沒必要封裝了,我也不考慮封裝進(jìn)dll了,開源就好了)。

首先我們需要定義變量,用于保存種子值、隨機(jī)數(shù)序列生成器、分布對象(放到函數(shù)定義):

//引入頭文件和命名空間 #include <random> using namespace std;class Math { private://種子值static unsigned int g_seed;//隨機(jī)數(shù)序列生成器static default_random_engine g_random; };//靜態(tài)成員需要類外定義 unsigned int Math::g_seed = 0; default_random_engine Math::g_random;

?對于種子值,為無符號整型。傳入0時讓程序自動生成隨機(jī)種子,有時候我們需要知道隨機(jī)產(chǎn)生的種子值是多少,所以有必要緩存一下:

//設(shè)置種子 0代表隨機(jī) static void SetSeed(unsigned int s) {if (s == 0){random_device rd;g_random.seed(g_seed = rd());}elseg_random.seed(g_seed = s); } //返回種子值 static unsigned int GetSeed() { return g_seed; }

分布的一般化寫法可以寫成模板,將分布對象放入函數(shù)作為靜態(tài)變量:

//返回[min,max]區(qū)間的 整型隨機(jī)值 template <typename T> static T GetRandInteger(T min, T max) {static uniform_int_distribution<T> dist_int;//設(shè)定區(qū)間dist_int.param(uniform_int_distribution<T>::param_type{ min, max });return dist_int(g_random); }//返回[min,max)區(qū)間的 實數(shù)隨機(jī)值 template <typename T> static T GetRandReal(T min, T max) {static uniform_real_distribution<T> dist_real;//設(shè)定區(qū)間dist_real.param(uniform_real_distribution<T>::param_type{ min, max });return dist_real(g_random); }

?這下舊的接口特例化模板即可:

//返回[min,max]區(qū)間的隨機(jī)int static int GetRandInt(int min, int max) {return GetRandInteger<int>(min, max); }//返回[min,max)區(qū)間的隨機(jī)float static float GetRandFloat(float min, float max) {return GetRandReal<float>(min, max); }

滿足了舊的需求,我再簡單用一下正態(tài)分布吧 ,也很簡潔:

//返回 期望mu,標(biāo)準(zhǔn)差sigma 的正態(tài)分布隨機(jī)值 template <typename T> static T GetRandNormal(T mu, T sigma) {static normal_distribution<T> dist_normal;//設(shè)定期望與標(biāo)準(zhǔn)差dist_normal.param(normal_distribution<T>::param_type{ mu, sigma });return dist_normal(g_random); }

?五、更新實現(xiàn)后的地圖效果

從HGE的算法改為STL實現(xiàn)后,并沒有出現(xiàn)bug。但是生成的島嶼風(fēng)格還是有那么一點點的不同,其中必定有一些原因。不過算是如愿以償?shù)慕鉀Q了隨機(jī)數(shù)的需求,以后再用隨機(jī)數(shù)的時候就不用擔(dān)心暗藏bug了。

六、蒙特卡洛方法估計圓周率

向一塊正方形區(qū)域投擲飛鏢,假設(shè)飛鏢隨落在正方形內(nèi)任意位置的概率相等,那么落在內(nèi)接圓的概率等于圓面積除以正方形面積。通過統(tǒng)計圓內(nèi)飛鏢數(shù)就可以估算出pi值,這種方法就叫統(tǒng)計模擬方法。

即?,落在圓內(nèi)的數(shù)量比上總數(shù)的比值應(yīng)該為四分之pi。

通過編寫代碼,測出了以下數(shù)據(jù):

默認(rèn)序列 + 連續(xù)均勻分布
總量圓內(nèi)pi值
10^152
10^2692.76
10^37843.136
10^479003.16
10^5785933.14372
10^67851443.14058
10^778552223.14209
10^8785358283.14143
10^9一分鐘內(nèi)未得出結(jié)果-

代碼如下,得出的數(shù)據(jù)算是符合了。

#include <iostream> #include <random>using namespace std;template <typename T, typename T2> void one_test(unsigned all_num, T& dist, T2& rng) {double x, y;unsigned circle_num = 0;for (unsigned i = 0; i != all_num; ++i){x = dist(rng);y = dist(rng);if (x*x + y*y < 1.0){++circle_num;}}cout << "隨機(jī)了 " << all_num << "個點,在圓內(nèi)的有: " << circle_num << "個" << endl;cout << "估計PI值為: " << 4.0 * circle_num / all_num << endl;cout << endl; }int main() {//隨機(jī)種子random_device rd;//序列生成器default_random_engine rng{ rd() };//分布到 [-1.0, 1.0)uniform_real_distribution<double> dist{ -1.0, 1.0 };for (unsigned i = 1; i != 10; ++i){one_test(pow(10, i), dist, rng);}return 0; }

不過標(biāo)準(zhǔn)庫還有一種分布叫標(biāo)準(zhǔn)均勻分布(generate_canonical)專門產(chǎn)生[0,1)的連續(xù)分布,區(qū)別是可以指定尾數(shù)比特個數(shù)。不過這是一個模板函數(shù),所以如下使用:

template <typename T> void one_test2(unsigned all_num, T& rng) {double x, y;unsigned circle_num = 0;for (unsigned i = 0; i != all_num; ++i){x = generate_canonical<double, 32>(rng) * 2.0 - 1.0;y = generate_canonical<double, 32>(rng) * 2.0 - 1.0;if (x*x + y*y < 1.0){++circle_num;}}cout << "隨機(jī)了 " << all_num << "個點,在圓內(nèi)的有: " << circle_num << "個" << endl;cout << "估計PI值為: " << 4.0 * circle_num / all_num << endl;cout << endl; } 默認(rèn)序列 + 標(biāo)準(zhǔn)均勻分布(32位)
總量圓內(nèi)pi值
10^172.8
10^2692.76
10^37893.156
10^478983.1592
10^5784783.13912
10^67852713.14108
10^778532673.14131
10^8785392353.14157
10^9一分鐘內(nèi)未得出結(jié)果-

?這個標(biāo)準(zhǔn)均勻分布的結(jié)果在總量相同的時候,pi的精度也大概高一位,所以還是比前者好。不過梅森旋轉(zhuǎn)算法被稱為最好的隨機(jī)數(shù)算法,所以我們也該試一下:

//梅森旋轉(zhuǎn)隨機(jī)數(shù)序列生成器 mt19937_64 rng_2{ rd() };for (unsigned i = 1; i != 10; ++i) {one_test2(pow(10, i), rng_2); }

這個的結(jié)果我就不貼圖了,效果也一般。不過還說有個特別適合蒙特卡洛模擬的生成器(帶進(jìn)位減法),如下:

//帶進(jìn)位減法隨機(jī)數(shù)序列生成器 ranlux48 rng_3{ rd() };//連續(xù)均勻分布 for (unsigned i = 1; i != 10; ++i) {one_test(pow(10, i), dist, rng_3); }//標(biāo)準(zhǔn)均勻分布 for (unsigned i = 1; i != 10; ++i) {one_test2(pow(10, i), rng_3); }

經(jīng)測試,無論哪一種分布,在10^6數(shù)量生成的時候就超過了十幾秒,并且精度也沒有顯著提高。所以使用默認(rèn)序列生成器應(yīng)該能滿足一般的需求。

2019年11月21日,略游

總結(jié)

以上是生活随笔為你收集整理的C++标准库中的随机数生成的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。