JS隨機數生成演算法淺析:9301、49297、233380做基數的原因
見到這個隨機數生成演算法好幾次了,乍看有點雞肋,本來用Math.random()就可以的事,想不清楚為什麼他要用9301,49297,233280這三個數字?其中有道理嗎?還是僅是隨意選的三個數?但是這個組合貌似流傳很廣,好多網站原始碼裡都見到過。
// 生成隨機數
export function generateRandom (number) {
function rnd (seed) {
seed = ( seed * 9301 + 49297 ) % 233280
return seed / ( 233280.0 )
}
let today = new Date()
let seed = today.getTime()
return Math.ceil(rnd(seed) * number)
}
很多人認為這是簡單的Magic Number,其實這背後有內在的原因,這三個數字並不是隨便亂選出來的。
1、入門級的選擇標準
這種偽隨機數生成器叫做線性同餘生成器(LCG, Linear Congruential Generator),幾乎所有的執行庫提供的rand都是採用的LCG,形如:
生成的偽隨機數序列最大週期m,範圍在0到m-1之間。要達到這個最大週期,必須滿足
- c與m互質
- a - 1可以被m的所有質因數整除
- 如果m是4的倍數,a - 1也必須是4的倍數
以上三條被稱為Hull-Dobell定理。
作為一個偽隨機數生成器,週期不夠大是不好意思混的,所以這是要求之一。可以看到,a=9301, c = 49297, m = 233280這組引數,以上三條全部滿足。
2、進階級的選擇標準
要在偽隨機數生成器界混,僅僅入門是不夠的。
從工程的角度來講,的值要(在合理的範圍內)足夠小,以避免溢位的問題。
從安全(實用)性的角度來講,還要滿足良好的隨機性,這一點可以通過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就是質數)
- 接近,(m = 233280時為49297.86460172205)
所以有了這樣一些基本的標準,能夠選擇的引數範圍就小了很多,弄個程式跑下Spectral Test,就能得到可選的引數組。
如果想要更加詳盡的瞭解LCG偽隨機數生成器的性質以及引數選取、測試的數學理論,可以嘗試閱讀《計算機程式設計藝術》卷2第3章。