隨機與安全 指南
Modulo Bias 是什麼?取餘數偏差、Rejection Sampling 與均勻亂數完整指南
安全亂數來源只是第一步;把大範圍亂數映射到0~n−1時,演算法本身也可能偷偷加入偏差。
更新日期:
很多程式會這樣寫:
index = randomValue % n;
看起來很合理。
但如果亂數來源的總可能數:
不能被 n 整除
某些餘數就會多分到一個來源值。
這叫:
Modulo Bias。
速答:Rejection Sampling 怎麼避免 modulo bias?
假設原始亂數均勻分布在0到M−1,而你需要0到n−1。先計算最大的可整除區間limit = floor(M / n) × n,若亂數值 ≥ limit 就丟掉重抽;只對0到limit−1取% n。如此每個結果都對應完全相同數量的原始值。FunnyTools目前的Random Number、Password、Wheel、Dice與姓名抽選相關實作會使用Web Crypto搭配拒絕取樣,而不是直接把32位亂數無條件% n。
一、用10個值映射到6面骰
假設來源只有:
0,1,2,3,4,5,6,7,8,9
想映射到:
0~5
直接 %6:
- 0 → 0
- 1 → 1
- 2 → 2
- 3 → 3
- 4 → 4
- 5 → 5
- 6 → 0
- 7 → 1
- 8 → 2
- 9 → 3
結果:
- 0:2個來源
- 1:2個
- 2:2個
- 3:2個
- 4:1個
- 5:1個
0~3機率比4~5高。
二、為什麼會這樣?
因為:
10 ÷ 6
不能整除。
如果來源剛好有12個值:
0~11
每個餘數都能對應2次,就沒有這個問題。
三、Rejection Sampling 的做法
對10個來源值、6個結果:
最大的6倍數:
floor(10/6) × 6 = 6
只接受:
0~5
6~9全部丟掉重抽。
這樣0~5各自只有一個來源,完全均勻。
四、這不是浪費很多亂數嗎?
在真實的32位空間中,通常浪費比例很小。
FunnyTools使用 Uint32 類亂數空間時:
M = 2^32
對一般小n來說,只會丟掉最尾端不足一整組的值。
換來的是:
精確均勻索引。
五、密碼字元為什麼也需要?
假設字元池有:
73個字元。
32位亂數空間不一定能被73整除。
如果直接:
random32 % 73
某些字元會有非常微小的額外機率。
在安全工具中沒有必要接受這個偏差,因此FunnyTools密碼產生器使用rejection sampling。
六、抽名字也是一樣
候選名單有37人。
要選0~36其中一個index。
合理做法:
- 安全亂數
- 均勻索引
- 再選名單位置
不能只說:
「用了crypto就一定均勻」
映射算法也要正確。
七、Fisher–Yates Shuffle 為什麼常一起出現?
Fisher–Yates洗牌會逐步把每個位置和一個隨機位置交換。
若每一步的index是均勻的:
每種排列理論上具有相同機率。
如果隨機index本身偏掉:
洗牌也會偏。
所以:
- CSPRNG
- rejection sampling
- Fisher–Yates
是可以互補的不同層。
八、array.sort(() => Math.random() - 0.5) 呢?
這是常見一行式洗牌。
問題包括:
- sort comparator不是為隨機排序設計
- 不同排序實作可能有不同偏差
- Math.random也不是安全亂數來源
一般需要公平洗牌時:
Fisher–Yates更合理。
九、偏差很小是不是就沒差?
要看情境。
對:
- 畫面小動畫
- 隨機背景色
微小偏差可能沒實務意義。
對:
- 密碼
- token
- 正式機率實驗
- 大量重複抽選
沒有理由自己引入已知偏差。
十、Modulo Bias和短期不平均不同
Modulo bias:
演算法本身讓理論機率不相等。
短期不平均:
即使理論機率完全相等,小樣本也會自然波動。
兩者不能混為一談。
十一、怎麼驗證程式?
除了讀code,可用大量模擬做sanity check:
例如100萬次,觀察每個結果頻率。
但:
頻率看起來平均,不能證明沒有微小bias。
真正還是要檢查映射演算法。
十二、Rejection Sampling有沒有風險?
若n設定不合理或程式計算錯誤,可能:
- 無限重抽
- overflow
- 效能問題
因此實作應限制輸入範圍並測邊界。
十三、FunnyTools現行做法
目前西文技術頁已公開說明:
- Random Number:Web Crypto + rejection sampling
- Password:Web Crypto + rejection sampling
- Wheel:先均勻選segment,再動畫
- Dice:均勻選face
- Name Picker:安全shuffle
本指南的SEO文章就是把這些技術原理變成可搜尋、可理解的知識頁。
十四、常見錯誤
錯誤1
用了crypto就直接 % n。
亂數來源安全,不代表映射無bias。
錯誤2
把短期連續結果當modulo bias。 不是同一件事。
錯誤3
用sort + random當公平洗牌。 不建議。
錯誤4
拿頻率圖當cryptographic proof。 不夠。
十五、簡化公式
來源空間:
0 ... M-1
目標:
0 ... n-1
接受上限:
limit = floor(M/n) × n
若:
x < limit
則:
x mod n
否則:
重抽。
十六、FAQ
random % n一定有bias嗎?
當來源可能數可以被n整除時沒有;不能整除時會有。
32位亂數bias會很大嗎?
很多n下非常小,但安全實作通常仍會避免。
Fisher–Yates會自動避免bias嗎?
前提是每一步抽到的index本身均勻。
Math.random加rejection sampling就安全嗎?
只能解決映射偏差,不能把非密碼學亂數變成CSPRNG。
FunnyTools為什麼不用簡單取餘數?
因為現行工具可以用拒絕取樣取得更乾淨的均勻分布。
十七、延伸閱讀
下一步
需要在0到n−1之間公平選一個index?
不要只看亂數來源,還要確認映射方法是否避免modulo bias。
重點速記
10 source values → 6 outputs
Direct modulo: 2,2,2,2,1,1
Reject tail → 1,1,1,1,1,1