随機數生成方法可以說是任何程式設計語言必備的功能,它的重要性不言而言,在C#中我們通常使用Random類生成随機數,在一些場景下,我卻發現Random生成的随機數并不可靠,在下面的例子中我們通過循環随機生成5個随機數:
這段代碼執行後的結果如下所示:
通過以上結果可知,随機數類生成了5個相同的數,這并非我們的預期,為什麼呢?為了弄清楚這個問題,零度剖析了微軟官方的開源Random類,發現在C#中生成随機數使用的算法是線性同餘法,經百科而知,這種算法生成的不是絕對随機,而是一種僞随機數,線性同餘法算法的的公式是:
上面的公式中A、B和M分别為常數,是生成随機數的因子,如果之前從未通過同一個Random對象生成過随機數(也就是調用過Next方法),那麼第N個随機數為将被指定為一個預設的常數,這個常數在建立一個Random類時被預設值指定,Random也提供一個構造函數允許開發者使用自己的随機數因子,這一切可通過微軟官方開源代碼看到:
通過預設構造函數建立Random類時,一個Environment.TickCount對象作為因子被預設傳遞給第二個構造函數,Environment.TickCount表示作業系統啟動後經過的毫秒數,計算機的運算運算速度遠比毫秒要快得多,這導緻一個的具有毫秒精度的因子參與随機數的生成過程,但在5次循環中,我們使用了同一個毫秒級的因子,進而生成相同的随機數,另外,第N+1個數的生成與第N個數有着直接的關系。
在上面的例子中,假設系統啟動以來的毫秒數為888毫秒,執行5次循環用時隻有0.1毫秒,這導緻在循環中建立的5個Random對象都使用了相同的888因子,每次被建立的随機對象又使用了相同的第N個數(預設為常數),通過這樣的假設我們不難看出,上面的結果是必然的。
現在我們改變這個格局,在循環之外建立一個Random對象,在每次循環中引用它,并通過它生成随機數,并在同一個對象上多次調用Next方法,進而不斷變化第N個數,代碼如下所示:
執行後的結果如下所示:
我們看到這個結果确實證明了我們上面的推斷,第1次循環時公式中的第N個數為預設常數;當第二次循環時,第N個數為391098894,随後不斷變化的第N個數作為因子參與計算,這保證了結果的随機性。
雖然通過我們的随機數看起來也很随機了,但必定這個算法是僞随機數,當第N個數和因子都相同時,生成的随機數仍然是重複的随機數,由于Random提供一個帶參的構造函數允許我們傳入一個因子,如果傳入的因子随機性強的話,那麼生成的随機數也會比較可靠,為了提供一個可靠點的因子,我們通常使用GUID産生填充因子,同樣放在循環中測試:
這樣的方式保證了填充因子的随機性,是以生成的随機數也比較可靠,運作結果如下所示:
在一些場景下這樣的随機數并不可靠,為了生成更加可靠的随機數,微軟在System.Security.Cryptography命名空間下提供一個名為RNGCryptoServiceProvider的類,它采用系統目前的硬體資訊、程序資訊、線程資訊、系統啟動時間和目前精确時間作為填充因子,通過更好的算法生成高品質的随機數,它的使用方法如下所示:
通過這種算法生成的随機數,經過成千上萬次的測試,并未發現重複,品質的确比Random高了很多。另外windows api也提供了一個非托管的随機數生成函數CryptGenRandom,CryptGenRandom與RNGCryptoServiceProvider的原理類似,采用C++編寫,如果要在.NET中使用,需要進行簡單的封裝。它的原型如下所示:
以上就是零度為您帶來的随機數生成方法和基本原理,您可以通過需求和場景選擇最佳的方式,Random算法簡單,性能較高,适用于随機性要求不高的情況,由于RNGCryptoServiceProvider在生成期間需要查詢上面提到的幾種系統因子,是以性能稍弱于Random類,但随機數品質高,可靠性更好。
本文轉自94cool部落格園部落格,原文連結:http://www.cnblogs.com/94cool/p/7928749.html,如需轉載請自行聯系原作者