網上常能見到的一段 JS 隨機數生成演算法如下,為什麼用 9301, 49297, 233280 這三個數字做基數?

見到這個隨機數生成演算法好幾次了,乍看有點雞肋本來用Math.random()就可以的事。想不清楚為什麼他要用9301,49297,233280這三個數字?其中有道理嗎?還是僅是隨意選的三個數?但是這個組合貌似流傳很廣。好多小網站源碼里都見到過。

function rnd( seed ){
seed = ( seed * 9301 + 49297 ) % 233280; //為何使用這三個數?
return seed / ( 233280.0 );
};

function rand(number){
today = new Date();
seed = today.getTime();
return Math.ceil( rnd( seed ) * number );
};

myNum=(rand(5));

Google了一下這3個數字,一些說法也是人云亦云沒有找到合理的解釋。
例如:Generate Repeatable Random Numbers (in JS)

You may ask: Why 『(seed * 9301 + 49297) % 233280『 ?!
The answer is both simplecomplicated: The combination of 9301, 49297 and 233280 provide a very even distributed set of 「random」 numbers. Please don』t ask WHY – that』s the complicated part, some very smart people figured out those numbers quite some time ago, and I also cannot tell you how they did it.

很聰明的前人算出來的?。。。

=============================================================
又找到一個頁面 http://www.ict.griffith.edu.au/anthony/info/C/RandomNumbers 好像有列舉,但是沒能看懂,ACM之類的。。,有人能解釋下不?

Simple (bad) Psuedo Random Number Generator (Sic)
The low bit typically just toggles between calls.

random() {
seed = ( seed * mulitiplier + increment ) % modulus;
return seed;
}

Table of Good values
Multiplier Increment Modulus
25173 13849 65536
9301 49297 233280

??


很多人認為這是簡單的Magic Number,其實這背後有內在的原因,這三個數字並不是隨便亂選出來的。

入門級的選擇標準
這種偽隨機數生成器叫做線性同餘生成器(LCG, Linear Congruential Generator),幾乎所有的運行庫提供的rand都是採用的LCG,形如:
I_{n+1}=aI_n+c (mod m)
生成的偽隨機數序列最大周期m,範圍在0到m-1之間。要達到這個最大周期,必須滿足

  • c與m互質
  • a - 1可以被m的所有質因數整除
  • 如果m是4的倍數,a - 1也必須是4的倍數

以上三條被稱為Hull-Dobell定理。
作為一個偽隨機數生成器,周期不夠大是不好意思混的,所以這是要求之一。
可以看到,a=9301, c = 49297, m = 233280這組參數,以上三條全部滿足。

進階級的選擇標準
要在偽隨機數生成器界混,僅僅入門是不夠的。
從工程的角度來講,(m-1)a+c的值要(在合理的範圍內)足夠小,以避免溢出的問題。
從安全(實用)性的角度來講,還要滿足良好的隨機性,這一點可以通過Knuth"s Spectral Test來評估(見[2][3]),要通過2,3,4,5以及6維的Spectral Test才行。Spectral Test考察的就是生成的偽隨機數序列在超空間的網格結構(lattice structure),當年IBM的RANDU子程序鬧出的烏龍,連3維的Spectral Test就不能通過,上圖嘲諷下:

其中每個點代表三個連續的RANDU生成的偽隨機數值,可以看到所有偽隨機數分布在了15個二維平面上。在這種要求面前,c的值最好:

  • 是質數 (c = 49297就是質數)
  • 接近(frac{1}{2}-frac{1}{6}sqrt{3} )m,(m = 233280時為49297.86460172205)

所以有了這樣一些基本的標準,能夠選擇的參數範圍就小了很多,弄個程序跑下Spectral Test,就能得到可選的參數組。

如果想要更加詳盡的了解LCG偽隨機數生成器的性質以及參數選取、測試的數學理論,可以嘗試閱讀《計算機程序設計藝術》卷2第3章。參考資料:
[1] http://nuclear.fis.ucm.es/COMP-PHYS/RANDOM/RandomNumbers.pdf
[2] http://random.mat.sbg.ac.at/tests/theory/spectral/
[3] Knuth, Donald E. (1981), The Art of Computer Programming volume 2: Seminumerical algorithms (2nd ed.), Addison-Wesley, p. 89.


為啥不用random? 新瀏覽器 可能會用 音效卡噪音 因素加入到 隨機數生成的方法中去。

在瀏覽器里玩的話,如果自己來搞,也許陀螺儀 等設備的 一些 信息的加入。也可以增加隨機演算法的健壯性。如果再加入一些用戶態 比如 滑鼠坐標 滾動條位置啊 等等都參與其中 都可以把隨機性 這件事 做到更好 會不會更有趣些?


順便說一下,這個代碼是有問題的。
首先,rand函數裡面today沒有var,會造成全局污染,其次,如果你以很高的頻率調用這個隨機函數,會出現一大堆相同的數值。雖然js的效率比較低所以重複的次數不會很多……大概會重複個10~20次……這個和各種因素有關。。
你可以這樣試試
for (var i = 0; i &< 1000; i++) console.log(rand(10));
這玩意應該是這樣的。

rand = (function(){
var today = new Date();
var seed = today.getTime();
function rnd(){
seed = ( seed * 9301 + 49297 ) % 233280;
return seed / ( 233280.0 );
};
return function rand(number){
// return Math.ceil(rnd(seed) * number);
return Math.ceil(rnd() * number);
};
})();
myNum = (rand(5));

應該這樣會合理一些。


線性同餘生成器,常用的一組參數。

公式很簡單,a,c,m分別就是你這3個數字:

適用這個公式,類似的還有很多組合。

維基百科有這個演算法的介紹,以及很多常見組合的來源:

Linear congruential generatorLinear congruential generator


你這個流派,可能是來自Numerical Recipes

Original source of #x60;(seed * 9301 + 49297) % 233280#x60; random algorithm?


return seed / ( 233280.0 );
想問下這裡的233280.0 為什麼要帶個一位小數?


酷帖留名~,再修改一小下

var rand = (function(){
var seed = (new Date()).getTime()
function r(){
seed = (seed*9301+49297)%233280
return seed/(233280.0)
}
return function(number){
return Math.ceil(r()*number)
}
})()


推薦閱讀:

大學學的不是喜歡的專業怎麼辦?
寫代碼時,縮進使用 tab 還是空格?
計算機系學生,感覺自己編程能力很差勁,怎麼提高自己編程能力?
遊戲編程裡面有哪些經典或者很酷的演算法?
為什麼很多人喜歡 Python?

TAG:前端開發 | JavaScript | 演算法 | 數學 | 編程 |