loquin

03-27-2014, 03:23 PM

I recently moved the Box-Muller (Polar) algorithm for transforming MS's standard random number to a random number with a normal distribution to dotNet. (Did not make a class of it; just migrated the code module)

Original, VB6 thread (http://www.xtremevbtalk.com/showthread.php?threadid=76270) from the Code Library.

A discussion of the algorithm is available here (http://www.taygeta.com/random/gaussian.html)

Imports System.Math

Module GAUSSE

Public Sub Init()

Static Initialized As Boolean = False

If Not Initialized Then

Randomize()

Initialized = True

End If

End Sub

Public Function Normal(Optional ByVal Sigma As Double = 1.0#, Optional ByVal Mean As Double = 0.0#) As Double

Dim Norm As Double = 0.0#

Norm = (GetGausse() * Sigma) + Mean

Return Norm

End Function

Private Function GetGausse() As Double

' This Function returns a standard Gaussian random number, based upon the polar form of the Box-Muller transform.

' since this calc is capable of returning two calculations per call, it's been written to save the second calc for the next

' pass through the function, thus saving some time on alternate calls.

' Only call the Init sub once in the life of the app, but it's only needed if you haven't seeded the random number generator already.

' NEVER call INIT or randomize within a loop, or as a function of time - this can seriously degrade the 'randomness' of the data.

' (The reason for this is that the randomize function reseeds the random number generator with a seed derived from the system clock.

' If you call randomize within a loop, especially a tight loop, the odds are good that you will repeatedly reseed the random number

' generator with the SAME seed as the prior call - which results in the same 'random' number being generated...) If randomize is called

' once from an event that depends upon operator input, such as a form load event, you would be OK, however.

'

Static blReturn2 As Boolean = True ' Flag to calc new value pair, or return previously calculated second normal rnd.

Static dblReturn2 As Double = 0.0# ' Alternate return value

Dim Work1 As Double = 0.0#, Work2 As Double = 0.0#, Work3 As Double = 0.0#

Const Two As Double = 2.0#, One As Double = 1.0#

blReturn2 = Not blReturn2 ' toggle the return value source flag

If blReturn2 Then ' On odd numbered calls

Return dblReturn2

Else

Work3 = Two

Do Until Work3 < One

Work1 = Two * Rnd() - One

Work2 = Two * Rnd() - One

Work3 = Work1 * Work1 + Work2 * Work2

Loop

Work3 = Sqrt((-(Two) * Log(Work3)) / Work3)

' a second valid value will be returned with Work2 * Work3. Calculate & save in static variable for the next pass, for efficiency.

dblReturn2 = Work2 * Work3

Return Work1 * Work3

End If

End Function

End Module

Note: I did add a subroutine version of both the GetGausse and Normal functions. This allows you to return both available results in one call, and you don't need the static variables in the subs. These two changes resulted in a 40% performance improvement. (0.15 seconds down to .09 seconds per million numbers retrieved.)

Original, VB6 thread (http://www.xtremevbtalk.com/showthread.php?threadid=76270) from the Code Library.

A discussion of the algorithm is available here (http://www.taygeta.com/random/gaussian.html)

Imports System.Math

Module GAUSSE

Public Sub Init()

Static Initialized As Boolean = False

If Not Initialized Then

Randomize()

Initialized = True

End If

End Sub

Public Function Normal(Optional ByVal Sigma As Double = 1.0#, Optional ByVal Mean As Double = 0.0#) As Double

Dim Norm As Double = 0.0#

Norm = (GetGausse() * Sigma) + Mean

Return Norm

End Function

Private Function GetGausse() As Double

' This Function returns a standard Gaussian random number, based upon the polar form of the Box-Muller transform.

' since this calc is capable of returning two calculations per call, it's been written to save the second calc for the next

' pass through the function, thus saving some time on alternate calls.

' Only call the Init sub once in the life of the app, but it's only needed if you haven't seeded the random number generator already.

' NEVER call INIT or randomize within a loop, or as a function of time - this can seriously degrade the 'randomness' of the data.

' (The reason for this is that the randomize function reseeds the random number generator with a seed derived from the system clock.

' If you call randomize within a loop, especially a tight loop, the odds are good that you will repeatedly reseed the random number

' generator with the SAME seed as the prior call - which results in the same 'random' number being generated...) If randomize is called

' once from an event that depends upon operator input, such as a form load event, you would be OK, however.

'

Static blReturn2 As Boolean = True ' Flag to calc new value pair, or return previously calculated second normal rnd.

Static dblReturn2 As Double = 0.0# ' Alternate return value

Dim Work1 As Double = 0.0#, Work2 As Double = 0.0#, Work3 As Double = 0.0#

Const Two As Double = 2.0#, One As Double = 1.0#

blReturn2 = Not blReturn2 ' toggle the return value source flag

If blReturn2 Then ' On odd numbered calls

Return dblReturn2

Else

Work3 = Two

Do Until Work3 < One

Work1 = Two * Rnd() - One

Work2 = Two * Rnd() - One

Work3 = Work1 * Work1 + Work2 * Work2

Loop

Work3 = Sqrt((-(Two) * Log(Work3)) / Work3)

' a second valid value will be returned with Work2 * Work3. Calculate & save in static variable for the next pass, for efficiency.

dblReturn2 = Work2 * Work3

Return Work1 * Work3

End If

End Function

End Module

Note: I did add a subroutine version of both the GetGausse and Normal functions. This allows you to return both available results in one call, and you don't need the static variables in the subs. These two changes resulted in a 40% performance improvement. (0.15 seconds down to .09 seconds per million numbers retrieved.)