偽隨機數產生器(pseudorandom number generator)
/ SOO-doh-RAN-dum /
假設你要跑一個模擬,需要一長串「隨機」數字——但你的電腦是一台完全聽話的機器,叫它做什麼就做什麼,身上又沒有一枚可以拋的硬幣。一台確定性的裝置怎麼可能產生隨機性?嚴格說它做不到。它只能產生一個「看起來」隨機的序列:每個數字都由前一個用固定的配方算出,但輸出通過隨機性的統計檢驗到足以騙過模擬的程度。這個配方就是偽隨機數產生器。
具體來說,PRNG 持有一個內部「狀態」s,並有兩條規則:把狀態往前推的轉移 s_{n+1} = T(s_n),以及把每個狀態擠成一個數字(通常是一個落在 [0, 1) 的分數 u_n)的輸出映射。你用一個叫「種子」的初始狀態啟動它;此後整條序列就被完全決定。由於狀態活在一個有限集合裡,序列終究必定重複——重複前的長度稱為「週期」,好的產生器週期長到天文數字。真正重要的兩項品質是:統計品質(輸出不該有可偵測的樣式、相關或偏差)與長週期(使長時間執行不會繞回頭重複用同一批數字)。
這裡誠實的字眼是「偽」:這些數字完全確定且可重現。這多半是優點——固定種子讓你能逐位元重跑一個模擬以除錯,或讓別人重現你的結果。但若你忘了這一點,它就是陷阱:週期短或藏有相關的劣質產生器,會無聲無息地汙染一項蒙地卡羅研究,給出自信卻錯誤的答案,而且什麼都不會當掉,所以這種錯誤是隱形的。做資安(金鑰、亂數值)時,普通 PRNG 不安全,你需要密碼學安全的產生器,使輸出即使在看過許多樣本後仍無法被預測。歷史上惡名昭彰的劣質產生器(如老舊的 RANDU)教會整個領域:在信任一個產生器之前,要狠狠地檢驗它。
一個極小的玩具:狀態 x_{n+1} = (5 * x_n + 3) mod 16。種子 x_0 = 7 給出 7, 6, 1, 8, 11, 10, 5, 12, 15, 14, 9, 0, 3, 2, 13, 4,然後又回到 7——完整週期為 16。除以 16 把每個數變成 [0, 1) 內的分數。真正的產生器用同樣的想法,只是模數大得多、狀態也多得多。
每個 PRNG 都是一組發條:用固定規則把一個狀態變成下一個。
偽隨機數並非真正隨機——它本來就設計成可重現。在平行執行緒或重複實驗中用同一個全域產生器配同一顆種子,會悄悄讓你以為彼此獨立的執行相互關聯。