Rick Rothstein said:
The above statement from your post is not really true...
it will hurt to call Randomize multiple times.
First, I want to reiterate and emphasize that I agree wholeheartedly that
"Randomize should be called only one time". That's why I said that.
But let me explain what I meant by "[a]rguably, it should not hurt to call
it multiple times".
First, note that the operative word is "arguably". By that, I mean it is
indeed dubious to do so.
Second, I would never recommend calling Randomize in a tight loop; I would,
indeed, expect that to be "hurtful". Since it is common to seed the PRNG
using the time of day or system uptime, and we do not know (and should not
consider) the internal algorithm for deriving the seed from time, it would
not be unreasonable to assume that consecutive Randomize seeds in a tight
loop might be very close.
However, I think it is less risky (albeit still not recommended) to call
Randomize each time a function or macro is executed. For example, see
BerndP's
http://sulprobil.com/html/randint.html, which directs the user to
uncomment the Randomize statement "if you like".
I cannot address the observations with your algorithm.
But the following macro demonstrates that even when Randomize is called in a
tight loop (surprise!), it does not seem to unduly hurt the "randomness" of
the results, measured in simple terms.
Of course, the true measure of "randomness" requires rigorous and complex
tests.
The measurements I used are more simplistic: distribution among the buckets
defined by the tenths digit (i.e., 0.0x, 0.1x, 0.2, etc); the maximum run
length, counting instances in the same bucket +/- 1 to be part of the same
run; and average and standard deviation of the difference between
consecutive random numbers. The latter two statistics attempt to measure
locality.
The macro should be run with the #Const doOnce first set True, then set
False. Then compare the results in A1:B10 and A11:B21. A1 and A12 are the
std dev of the bucket distribution in B1:B10 and B11:B21 respectively. A2
and A13 are the max run legnth. A3:A4 and A14:A15 are the average and std
dev of the difference between consecutive random numbers.
Of course, the results will vary somewhat from run to run. YMMV.
But in the experiments that I tried, A3:A4 and A14:A15 consistently very
similar; for example 3.32E-1 and 2.36E-1 when doOnce is True v. 3.29E-1 and
2.35E-1 when doOnce is False.
Likewise, the A2 and A13 were often close and consistently very small; for
example 8 when doOnce is True v. 5 when doOnce is False.
Sometimes, A1 and A12 were similar; for example, about 80 and 98.
But frequently, A12 was significantly higher than A1, 2 to 3 times higher.
I don't know what to make of that statistic. I did not find any consistent
difference in the bucket distributions, e.g. some buckets consistently
significantly more or less when doOnce is False then when doOnce is True.
Perhaps it suggests some localized ping-ponging between one set of buckets
and another set. That could be "hurtful". But I reiterate: this macro
calls Randomize in a tight loop (when doOnce is False), which I was
surprised performed as well as it did.
Based on the surprisingly good results from a tight loop, I feel more
confident in saying that calling Randomize once every time a function or
macro is executed (assuming the call itself is not in a tight loop), which
is more like what I had in mind, would probably not be hurtful.
Nonethess, I still would not recommend it. I would call Randomize in a
Workbook_Open macro, or I would use a "first-time" static or global variable
so that Randomize is called only once ostensibly.
(As I wrote previously, the first-time variable approach will call Randomize
once each time VBA is reset.)
-----
Macro....
Option Explicit
#Const doOnce = True
Sub doit()
Dim f(0 To 9, 0) As Double
Dim n As Double, r As Double, rprev As Double
Dim nrun As Double, nrunMax as Double
Dim i As Integer, iprev As Integer, d As Integer
Dim x As Double, sumX As Double, sumX2 As Double, a As Double, s As Double
#If doOnce Then
Randomize
#End If
nrun = 1: nrunMax = 1
sumX = 0: sumX2 = 0
For n = 1 To 100000
#If Not doOnce Then
Randomize
#End If
r = Rnd
i = Int(r * 10) 'bucket number (tenths digit)
f(i, 0) = f(i, 0) + 1
If n > 1 Then
x = Abs(r - rprev) 'diff btwn consecutive Rnd's
sumX = sumX + x
sumX2 = sumX2 + x * x
If (i = iprev) Or (i = iprev - 1) Or (i = iprev + 1) Then nrun = nrun
+ 1 _
Else nrunMax = IIf(nrun > nrunMax, nrun, nrunMax): nrun = 1
End If
iprev = i
rprev = r
Next
If nrun > nrunMax Then nrunMax = nrun
a = sumX / (n - 2) 'avg
s = Sqr(sumX2 / (n - 2) - a * a) 'std dev
#If doOnce Then
d = 0 'a1:a4,b1:b10
#Else
d = 11 'a12:a15,b12:b21
#End If
Range("b1:b10").Offset(d, 0).NumberFormat = "0"
Range("b1:b10").Offset(d, 0) = f
Range("a1").Offset(d, 0).NumberFormat = "0.00"
Range("a1").Offset(d, 0).Formula = "=stdevp(offset(b1:b10," & d & ",0))"
Range("a2").Offset(d, 0).NumberFormat = "0"
Range("a2").Offset(d, 0) = nrunMax
Range("a3").Offset(d, 0).NumberFormat = "0.00E+00"
Range("a3").Offset(d, 0) = a
Range("a4").Offset(d, 0).NumberFormat = "0.00E+00"
Range("a4").Offset(d, 0) = s
End Sub
----- original message -----