日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

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

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

C++標準庫中的隨機數生成

一、偽隨機與真隨機

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

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

二、常用實現

C和C++的許多代碼都如下生成隨機數:

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

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

//省略了參數檢查 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; }

三、標準庫的使用

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

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

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

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

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

3.正態分布

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

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

4.對數分布?

對數分布與正態分布類似,即隨機變量的對數符合正態分布,即log x符合正態分布,x的概率分布就是對數分布。曲線特點是較快的到達高峰然后拖一條長尾巴。

//指定期望和標準差 (5.0, 0.5) lognormal_distribution<> dist {5.0, 0.5};dist(rng);

5.抽樣離散分布?

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

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

一般來說權值的值和個數應該是動態變化的,如下改變:

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

6.抽樣分段常數分布?

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

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

7.分段線性分布

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

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

8.其它分布

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

泊松分布:poisson_distribution

幾何分布:geometric_distribution

指數分布:exponetial_distribution

伽馬分布:gamma_distribution

威爾分布:weibull_distribution

二項式分布:binomial_distribution

負二項式分布:negative_binomial_distribution

極值分布:extreme_value_distribution

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

9.其他隨機數序列的生成器

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

所以額外的標準庫還提供了不同的隨機數序列生成器。如下:

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

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

帶進位減法引擎:ranlux24_base(24位整數)、anlux48_base(48位整數)、ranlux24、ranlux48

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

四、原樣封裝一下

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

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

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

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

//設置種子 0代表隨機 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; }

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

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

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

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

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

//返回 期望mu,標準差sigma 的正態分布隨機值 template <typename T> static T GetRandNormal(T mu, T sigma) {static normal_distribution<T> dist_normal;//設定期望與標準差dist_normal.param(normal_distribution<T>::param_type{ mu, sigma });return dist_normal(g_random); }

?五、更新實現后的地圖效果

從HGE的算法改為STL實現后,并沒有出現bug。但是生成的島嶼風格還是有那么一點點的不同,其中必定有一些原因。不過算是如愿以償的解決了隨機數的需求,以后再用隨機數的時候就不用擔心暗藏bug了。

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

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

即?,落在圓內的數量比上總數的比值應該為四分之pi。

通過編寫代碼,測出了以下數據:

默認序列 + 連續均勻分布
總量圓內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一分鐘內未得出結果-

代碼如下,得出的數據算是符合了。

#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 << "隨機了 " << all_num << "個點,在圓內的有: " << circle_num << "個" << endl;cout << "估計PI值為: " << 4.0 * circle_num / all_num << endl;cout << endl; }int main() {//隨機種子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; }

不過標準庫還有一種分布叫標準均勻分布(generate_canonical)專門產生[0,1)的連續分布,區別是可以指定尾數比特個數。不過這是一個模板函數,所以如下使用:

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 << "隨機了 " << all_num << "個點,在圓內的有: " << circle_num << "個" << endl;cout << "估計PI值為: " << 4.0 * circle_num / all_num << endl;cout << endl; } 默認序列 + 標準均勻分布(32位)
總量圓內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一分鐘內未得出結果-

?這個標準均勻分布的結果在總量相同的時候,pi的精度也大概高一位,所以還是比前者好。不過梅森旋轉算法被稱為最好的隨機數算法,所以我們也該試一下:

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

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

//帶進位減法隨機數序列生成器 ranlux48 rng_3{ rd() };//連續均勻分布 for (unsigned i = 1; i != 10; ++i) {one_test(pow(10, i), dist, rng_3); }//標準均勻分布 for (unsigned i = 1; i != 10; ++i) {one_test2(pow(10, i), rng_3); }

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

2019年11月21日,略游

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。