偽隨機(jī)數(shù)生成器(PRNG)
具有任何編程經(jīng)驗(yàn)的人都知道計(jì)算機(jī)是確定性機(jī)器。如果你提供相同的輸入,則將始終獲得相同的輸出。這就是為什么讓計(jì)算機(jī)偶然生成隨機(jī)數(shù)比看起來(lái)復(fù)雜的多。
隨機(jī)數(shù)應(yīng)用在密碼學(xué)到博彩,視頻游戲等很多行業(yè)。但是,計(jì)算機(jī)天生就不能隨機(jī)。相反,程序員依靠偽隨機(jī)數(shù)生成器(PRNG),從稱(chēng)為種子/seed的給定起始值以編程方式生成新的隨機(jī)數(shù)。
這些算法有其自身的局限性。由于隨機(jī)數(shù)是通過(guò)程序生成的,因此,如果有人能夠識(shí)別出使用的種子值和PRNG算法,他們將能夠預(yù)測(cè)序列中的下一個(gè)隨機(jī)數(shù)。這將使攻擊者可以解密,預(yù)測(cè)序列中的下一張紙牌,在視頻游戲中作弊等。
但是PRNG在涉及建模和仿真的情況下仍然非常有用,因?yàn)樗试S通過(guò)使用相同的種子初始化隨機(jī)數(shù)生成器來(lái)“重播”一系列隨機(jī)事件。
“真”隨機(jī)數(shù)生成器(TRNG)
在隨機(jī)性第一的場(chǎng)景下,我們使用“真”隨機(jī)數(shù)生成器(TRNG)。與具有任意種子值的PRNG不同,TRNG從其環(huán)境/外部數(shù)據(jù)中選擇一個(gè)種子值。
以下是一些潛在的選擇:
- 鼠標(biāo)動(dòng)作
- 風(fēng)扇噪音
- 氣壓
- 自上一整秒以來(lái)的微秒數(shù)
只需要選擇攻擊者無(wú)法預(yù)測(cè)的種子即可。然后,該種子值將被傳遞到類(lèi)似于PRNG的算法中,該算法將生成一個(gè)隨機(jī)數(shù)。
兩種方法之間的實(shí)際差異。
PRNG比TRNG更快,PRNG的確定性在你要重播一系列“隨機(jī)”事件的情況下非常有用。
此外,某些PRNG算出的隨機(jī)數(shù),本質(zhì)上是周期性的,有一定的確定概率,但是使用好的初始化參數(shù)的現(xiàn)代PRNG,這個(gè)周期可以足夠長(zhǎng)。
相反,TRNG比PRNG慢,沒(méi)有確定性,并且不是周期性的。
線(xiàn)性同余生成器
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的PRNG。一個(gè)稱(chēng)為線(xiàn)性同余生成器(LCG)算法的變體。LCG以前是最常用和研究最多的PRNG之一(https://en.wikipedia.org/wiki/Linear_congruential_generator)。
這是LCG的迭代算法:
LCG上的Wikipedia頁(yè)面(https://en.wikipedia.org/wiki/Linear_congruential_generator)記錄了一些常用的模數(shù)m,乘數(shù)a和增量值c。關(guān)于最佳值的建議尚無(wú)統(tǒng)一看法,因此各個(gè)實(shí)現(xiàn)之間存在不同的值。
我們必須注意這些參數(shù)的取值。選擇錯(cuò)誤的值可能會(huì)導(dǎo)致創(chuàng)建一個(gè)過(guò)短的周期,從而使我們的隨機(jī)數(shù)生成器無(wú)用。
在下圖中,可以看到對(duì)參數(shù)的微小更改會(huì)極大地影響周期長(zhǎng)度。
代碼實(shí)現(xiàn)
這里使用早期C語(yǔ)言標(biāo)準(zhǔn)(C90 / C99 / ANSI C和C11)中記錄的值。
a = 1103515245
m = 2³¹
c = 12345
無(wú)論選擇哪種PRNG算法,都應(yīng)導(dǎo)致隨機(jī)數(shù)的均勻分布和足夠長(zhǎng)的時(shí)間。
import Foundation
final class LCG{
static func generate(modulus: Int, multiplier: Int, increment: Int, seed: Int) -> Int {
return (multiplier * seed + increment) % modulus
} // Generating 100 random numbers using LCG var seed = 0for _ in 0..<100 { let randomNumber = LCG.generate(modulus: 2147483648, multiplier: 1103515245, increment: 12345, seed: seed) seed = randomNumber print(randomNumber)}
模擬扔骰子
假設(shè)你要模擬骰子投擲。github地址:
https://gist.github.com/aryamansharda/070d508ee6c61d168d06d5aea9ecf946#file-linearcongruentialgenerator-dice-swift
將模數(shù)設(shè)置為6似乎很合理,但這會(huì)導(dǎo)致周期太短而無(wú)法使用。所以這里需要對(duì)最后的隨機(jī)數(shù)結(jié)果取模6處理。
var seed = 0
for _ in 1...40000 {
let randomNumber = LCG.generate(modulus: 2147483648, multiplier: 1103515245, increment: 12345, seed: seed)
seed = randomNumber
let diceRoll = 1 + (randomNumber % 6)
print(diceRoll)
}
使用上面的代碼,模擬40,000次骰子擲骰,查看結(jié)果,可以看到結(jié)果確實(shí)是均勻分布。






