C++的RAND函數生成的值為什麼存在嚴重的不隨機性?
用到0~10000的隨機,在C++中使用了rand()%10000,結果測試了100億次數據後,發現在2767值處出現斷層,前面0~2767都是122萬次左右,後面的都只有91.5萬次左右。。。這個是怎麼回事,求助~~
謝邀。
先說結論:這裡出現不均勻的現象不是因為rand本身的問題,而是因為使用的方法不對。
首先,rand的返回值範圍為 [0, RAND_MAX]。 在Windows環境下,提供的 RAND_MAX 一般為32767。也就是說,對於 rand() % 10000 來說,如果取到 [0, 2767] 這個範圍的值,那麼有四種情況:0xxxx、1xxxx、2xxxx、3xxxx,概率為 ;而如果取到 [2768, 9999] 這個範圍的值,就只有三種情況:0xxxx、1xxxx、2xxxx,概率為 。
這樣問題就迎刃而解了:
以及
這也是題主測試所得到的數字。
解決方法:(轉成浮點數的方法不對,這條劃掉),或者改用C++11的偽隨機數生成器(推薦),或者乾脆自己寫個偽隨機數生成器。
話說,看到2767應該能想到32767吧。
rand() Considered Harmful
用浮點數也不能保證均勻分布。
2767 = 32767 % 10000
確實 rand 返回的數字範圍太小了。
讓我們用中學數學裡常用的一種思維,就是考慮一種極端案例,假設我們恰好一共調用32767次,rand 依次返回 0到32767 各一次,然後看看這些數字對 10000 取余的結果,設我們有一萬個桶排成一行,我們從頭走到尾,每個桶里投一個數字,走到行尾部10000以後進行了折回 ( mod 導致的),回到行首走第二趟。因此,前 2767 個桶里有4個數字。2767 以後的桶里有3個數字。因為數字在 32767 之後沒有了,在最後一趟時走到行中間就結束了,沒能覆蓋整行。說 minvalue + (int)((maxⅴalue - minvalue ) * rand() / 32767.0) 這個如果要的數字範圍很大,會不能全覆蓋。
本質原因是進位不同(10000 不是 2 的整次方)引起的。換句話說,如果題主要是對 256,512,1024,取模,就可以很容易的做到均勻分布。就好比假如說我是賣甘蔗的,我有15,16根甘蔗(bits),你要幾根(bits)我給你幾根(bit)是最好的,如果你非要5根加 1 / 3 根(bits),那我賣不了,這根線不能從中間劈開。
因為 rand 分布範圍是 2 的 k 次方範圍,是用二進位組合起來的,題主要的是10進位上的範圍,所以對題主預期意圖實際上沒有數學上完美的解決辦法,只能儘可能接近均勻全覆蓋。我們一共有 32767 個數字,如果減少桶的個數,桶中的數字個數就會越發接近,差異越無關緊要。因此下面通過分段組合來近似均勻全覆蓋,比如:
(1) rand % 100 * 100
+ rand % 100;(2) rand % 10 * 1000
+ rand % 10 * 100 + rand % 10 * 10+ rand % 10;
和以上的同理,你會發現實際上上面的表達式中每個 mod 都分布不均勻,但後者的那一行桶的行粒度更小,所以針對海量取樣的不均勻性就會更小,因此組合到一起以後, 後者比 前者覆蓋的更均勻。tl:dr:所有rand / x*y都越改越錯,五十步笑百步。
---我的天哪,這都几几年了還在閉門造車?隨機數取模還說得過去,畢竟最廣為流傳。但建議cast到float再cast回來,或者開不知道哪來沒驗證的私車的人,全是越改越錯。
難道就不知道用用標準&
std::default_random_engine generator;
//需要nondeterministic/true random的用std::random_device,可抗密碼學攻擊,除非是30年前的老爺機 std::uniform_int_distribution&
long sample = distribution(generator);
&
你這輩子都沒見過也不會見到的distribution全齊了,而且都是經得起密碼學攻擊的實現
pseudorandom generator有minstd_rand, minstd_rand0, minstd_rand0, mt19937, mt19937_64, ranlux24_base, ranlux48_base, ranlux24, ranlux48, knuth_b
一樣也都是你沒見過也不會見到的pseudorandom演算法全齊了,都是經過時間和經驗檢驗的演算法和標準實現。給你分析一下:
rand() 產生的數應該是在0~32767 均勻分布的,所以如果拿它mod 10000的話,則相當於把編號0到32767的球按照他們對於10000的餘數放到10000個箱子里去, 於是乎, 前2767個盒子放了4個球,後面的盒子只放了3個球,於是乎,最後數的其實是這10000個盒子里,每個盒子所得到的球數在32767次放球操作中的比例, 4/32767= 0.00012207, 3/32767=0.00009156, 正好與你的實驗結果相吻合,證明了說 rand函數在0~32767之間是均勻分布的。
然後你想產生0-10000的隨機數,正確的做法是 rand()/32767.0 * 10000.用 @lichray 大大的 randint(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4531.html)
std::experimental::randint(0, 10000);你都求模取余了,原來再均勻都被這樣搞成不均勻了。
好像有研究說,生成指定[minValue,maxValue]範圍的隨機數不能用%,需要這麼寫:
int value=minValue+int((maxValue-minValue)*(rand()/(float)RAND_MAX));
其中RAND_MAX是標準庫內置的一個宏,表示rand()函數可能生成的最大的隨機數,rand()/(float)RAND_MAX表示把rand()的值均勻縮放到[0,1]範圍內,再把這個[0,1]範圍內的值縮放到需要的範圍。
你可以試一下這個辦法。
for (;;){int x = rand(); if (x &< 3*10000) return x%1000}這樣就可以吧?
關於這個東西。。其實前面的答主給出的解決方案似乎都不太嚴謹。。其實這個問題沒那麼容易完美解決,最好的做法就是用輸出大小大點的prng..或者csprng..這些東西通過值域大或難預測性能一定程度上保證質量。。還有mt19937其實輸出大小可以看成是長度大於1K的二進位序列所以用哪個迭代的還是可接受的。。
1. 不要用取模,用按比例縮放後取整。
2. 不要用 rand(),改成讀 /dev/urandom,要求高的話(比如用於加密演算法)用 /dev/random。
如果非要用取模運算,千萬不要用10或者2的n次方
不查msdn的嗎?有例子啊。返回range_min&<=random number&
因為rand的生成值範圍在0-32767。
@SuperSodaSea 分析蠻合理的。
按照他的思路算了一下:
假設 RAND_MAX = 32767 , 欲生成 0~9999 範圍內隨機數。
直接 rand() % 10000 是不行,因為 rand() 的範圍是 0 ~ 32767 。
使用取模的方法,為了能夠獲取均勻的隨機值,必須使被取模的數是10000的整數倍。
假設這個數是 0~29999,那麼取模後的分布是均勻的。
試著寫了一下:
int get_rand(int n)
{
int rand_result;
int rand_max = RAND_MAX / n * n;
do {
rand_result = rand();
} while (rand_result &>= rand_max);
return rand_result % n;
}
P.S. C++11 出了&
int32 makeUniformRand(int32 fr, int32 to)
{
if (fr &> to) {
int32 tmp = to;
to = fr;
fr = tmp;
}
int32 num = (to - fr + 1);
int32 thisRandMax = (RAND_MAX/num)*num-1;
int32 r = 0;
do {
r = rand();
} while (r &> thisRandMax);
return fr + r%num;
}
主要你用mod了吧………我上次也遇到了……於是乎用(mod * mod * mod )mod這麼取隨機………
不能直接取模,得到的結果不均勻。一個簡單的演算法是,如果rand()的結果大於等於10000,則重新rand(),直到rand()結果小於10000
這是因為通常C/C++自帶的那個PRNG是個玩具級別的東西。干正事都不應該用。應該用更好的演算法。
推薦閱讀:
※在c語言中,使用函數指針是否可以提高函數的調用速度 ?
※C++中如何定義指向函數指針的指針?
※如何有效的練習並且提升寫代碼的能力?