Algorithm Implementation/Pseudorandom Numbers/Wichmann-Hill (1982) PRNG

This algorithm has behavior that is broadly similar to that of the built-in VBA Rnd() function, and this is perhaps not surprising since it was the basis of the Excel Rand() function for quite some time. A short summary is given here:

  • The Randomize() function is used to set the working variables located at module level. In turn, these variables are used at every stage to calculate RndX() outputs and to re-calculate their own values, ready for the next RndX() call.
  • RandomizeX() sets variables using an input from the system timer when it is called without a parameter. When given a parameter, the output stream of characters generated will be distinct and repeatable for that parameter. This latter state applies when no parameter for RndX() is given, or if given, provided that it is a positive number.
  • The most common configuration, and most useful, calls RandomizeX() before RndX(), both without parameters. However, if RandomizeX() is not included in the code, the work variable defaults that are used by RndX() at startup lead to a corresponding default output stream of characters. This same stream is repeated whenever RndX() is run without first calling RandomizeX().
  • Adding positive parameters for RndX() will not seed the generation process. Apart from the starup defaults for RndX(), all seeding depends directly or indirectly on having RandomizeX() in the code.
  • Refer to the drop box below for a complete tabulation of the parameter settings and their outcomes.
PRNG RndX() and RandomizeX() Parameter Details
RndX() and RandomizeX() Parameter Details
RandomizeX()
parameter
RndX()
parameter
Behaviour of function
(assuming coding is to produce a sequence)
none. none PRNG stream determined by runtime sampling of the computer’s system timer. Stream uncertain.
none positive PRNG stream determined by runtime sampling of the computer’s system timer. Stream uncertain. Positive parameters of RndX() do not affect it at all.
none negative One number, repeatable, and each one different and depending on the value of parameter.
Example; RndX(-3) leads to 0.05079271
none zero One number, repeatable, decided by runtime sampling of the computer’s system timer;
Example; sequence is 0.1741…, 01741…
numeric2 none PRNG stream, repeatable, and each one different and depending on the value of parameter.
numeric positive PRNG stream, repeatable, and each one different and depending on the value of parameter. Positive parameters of RndX() do not affect it at all.
numeric negative One number, repeatable, and each one different and depending on the value of RndX() parameter. The RandomizeX() parameter value has no effect at all.
Example; RndX(-51) leads to 0.8634…
numeric zero One number, repeatable, and each one different and depending on the value of parameter.
Example; RandomizeX(2346) leads to 0.2322…
function
not used
none Default PRNG stream, repeatable, and always same.
Example; sequence is 0.8952…, 0.1114…, 0.9395…
function
not used
positive Default PRNG stream, repeatable, and always same.
Example; sequence is 0.8952…, 0.1114…, 0.9395…
function
not used1
negative
or zero
One number, repeatable, and each one different and depending on the value of parameter.
Example; RndX(0) = 0.8694...: -5 = 0.0846…


1. The term Function not used is intended to mean that the function is not specifically called in code by the user. In some cases, for example this one, the RandomizeX() function still needs to be available to the RndX() function itself.
2. Numeric items are those that can be made into a number. The RandomizeX() function produces a positive integer using the seed value given in its variant parameter. It does this for any leading part of a string also, right up to the first character that cannot be recognized as numeric.

 

 


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

References

edit
  • Wichmann, Brian; Hill, David (1982), Algorithm AS183: An Efficient and Portable Pseudo-Random Number Generator, Journal of the Royal Statistical Society. Series C