跳轉到內容

演算法實現/偽隨機數/Wichmann-Hill (1982) PRNG

來自華夏公益教科書,開放的書籍,開放的世界

該演算法的行為與內建的 VBA Rnd() 函式非常相似,這可能並不奇怪,因為它是 Excel Rand() 函式相當長一段時間的基礎。這裡給出了一個簡短的總結

  • Randomize() 函式用於設定位於模組級別的工作變數。這些變數在每個階段都用於計算 RndX() 輸出,並重新計算它們自己的值,為下次 RndX() 呼叫做好準備。
  • RandomizeX()沒有引數的情況下呼叫時,使用來自系統計時器的輸入來設定變數。當給出引數時,生成的字元輸出流將對於該引數是不同的且可重複的。後一種情況適用於沒有為 RndX() 提供引數,或者在提供引數的情況下,只要它是一個正數。
  • 最常見的配置,也是最有用的一種配置,是在 RndX() 之前呼叫 RandomizeX(),兩者都沒有引數。但是,如果程式碼中沒有包含 RandomizeX()RndX() 在啟動時使用的工作變數預設值會導致相應的預設字元輸出流。每次在不首先呼叫 RandomizeX() 的情況下執行 RndX() 時,都會重複此流。
  • RndX() 新增正引數不會播種生成過程。除了 RndX() 的啟動預設值之外,所有播種都直接或間接地依賴於程式碼中是否包含 RandomizeX()
  • 請參閱下面的下拉框,以獲取引數設定及其結果的完整表格。
PRNG RndX() 和 RandomizeX() 引數詳細資訊
RndX() 和 RandomizeX() 引數詳細資訊
RandomizeX()
引數
RndX()
引數
函式的行為
(假設編碼是產生一個序列)
無。 PRNG 流由計算機系統計時器的執行時取樣決定。流不確定。
正數 PRNG 流由計算機系統計時器的執行時取樣決定。流不確定。RndX() 的正引數根本不影響它。
負數 一個數字,可重複,並且每個數字都不同,並且取決於引數的值。
例如;RndX(-3) 導致 0.05079271
一個數字,可重複,由計算機系統計時器的執行時取樣決定;
例如;序列為 0.1741…,01741…
數字2 PRNG 流,可重複,並且每個數字都不同,並且取決於引數的值。
數字 正數 PRNG 流,可重複,並且每個數字都不同,並且取決於引數的值。RndX() 的正引數根本不影響它。
數字 負數 一個數字,可重複,並且每個數字都不同,並且取決於 RndX() 引數的值。RandomizeX() 引數值完全沒有影響。
例如;RndX(-51) 導致 0.8634…
數字 一個數字,可重複,並且每個數字都不同,並且取決於引數的值。
例如;RandomizeX(2346) 導致 0.2322…
函式
未使用
預設 PRNG 流,可重複,並且始終相同。
例如;序列為 0.8952…,0.1114…,0.9395…
函式
未使用
正數 預設 PRNG 流,可重複,並且始終相同。
例如;序列為 0.8952…,0.1114…,0.9395…
函式
未使用1
負數
或零
一個數字,可重複,並且每個數字都不同,並且取決於引數的值。
例如;RndX(0) = 0.8694...: -5 = 0.0846…


1. 術語函式未使用意指使用者沒有在程式碼中專門呼叫該函式。在某些情況下,例如這種情況,RandomizeX() 函式仍然需要對RndX() 函式本身可用。
2. 數字項是可以轉換為數字的項。RandomizeX() 函式使用其變體引數中給定的種子值生成一個正整數。它對字串的任何前導部分也這樣做,直到第一個無法識別為數字的字元。



Option Explicit
Dim nX As Long, nY As Long, nZ As Long

Sub TestRndX()
    Dim n As Long
    RandomizeX
    For n = 1 To 1000
        'Debug.Print RndX()
        MsgBox RndX()    
    Next n
End Sub

Sub RandomizeX(Optional ByVal nSeed As Variant)
   'sets variables for PRNG procedure RndX()
      
   Const MaxLong As Double = 2 ^ 31 - 1
   Dim nS As Long
   Dim nN As Double
   
   'make multiplier
   If IsMissing(nSeed) Then
      nS = Timer * 60
   Else
      nN = Abs(Int(Val(nSeed)))
      If nN > MaxLong Then 'no overflow
         nN = nN - Int(nN / MaxLong) * MaxLong
      End If
      nS = nN
   End If
   
   'update variables
   nX = (nS Mod 30269)
   nY = (nS Mod 30307)
   nZ = (nS Mod 30323)
   
   'avoid zero state
   If nX = 0 Then nX = 171
   If nY = 0 Then nY = 172
   If nZ = 0 Then nZ = 170

End Sub

Function RndX(Optional ByVal nSeed As Long = 1) As Double
   'PRNG - gets pseudo random number - use with RandomizeX
   'Wichmann-Hill algorithm of 1982
   
   Dim nResult As Double
   
   'initialize variables
   If nX = 0 Then
      nX = 171
      nY = 172
      nZ = 170
   End If
   
   'first update variables
   If nSeed <> 0 Then
      If nSeed < 0 Then RandomizeX (nSeed)
      nX = (171 * nX) Mod 30269
      nY = (172 * nY) Mod 30307
      nZ = (170 * nZ) Mod 30323
   End If
   
   'use variables to calculate output
   nResult = nX / 30269# + nY / 30307# + nZ / 30323#
   RndX = nResult - Int(nResult)

End Function

參考資料

[編輯 | 編輯原始碼]
  • Wichmann, Brian; Hill, David (1982), Algorithm AS183: An Efficient and Portable Pseudo-Random Number Generator, Journal of the Royal Statistical Society. Series C
華夏公益教科書