在回复this question时,我运行了以下VBA实验:
Sub Test()
Dim i As Long, A As Variant
Dim count1 As Long, count2 As Long
ReDim A(1 To 10000)
For i = 1 To 10000
Randomize
A(i) = IIf(Rnd() < 0.5, 0, 1)
Next i
'count how often A(i) = A(i+1)
For i = 1 To 9999
If A(i) = A(i + 1) Then count1 = count1 + 1
Next i
For i = 1 To 10000
A(i) = IIf(Rnd() < 0.5, 0, 1)
Next i
'count how often A(i) = A(i+1)
For i = 1 To 9999
If A(i) = A(i + 1) Then count2 = count2 + 1
Next i
Debug.Print "First Loop: " & count1
Debug.Print "Second Loop: " & count2 & vbCrLf
End Sub
当我看到这样的输出时:
First Loop: 5550
Second Loop: 4976
我很确定我知道发生了什么:VBA正在将系统时钟转换为较低分辨率(可能是微秒),因此导致Randomize
有时会在两次或多次传递中产生相同的种子通过循环。在我原来的答案中,我甚至自信地断言了这一点。但后来我又运行了一些代码并注意到输出有时是这样的:
First Loop: 4449
Second Loop: 5042
交替仍然引起明显的自相关 - 但是处于相反(和意外)的方向。连续通过循环使用相同的种子应该产生相同的输出,因此我们应该看到连续的值比机会预测的更频繁地达成一致,而不是经常比机会预测的不同意。
现在好奇,我将代码修改为:
Sub Test2()
Dim i As Long, A As Variant
Dim count1 As Long, count2 As Long
ReDim A(1 To 10000)
For i = 1 To 10000
Randomize
A(i) = Rnd()
Next i
'count how often A(i) = A(i+1)
For i = 1 To 9999
If A(i) = A(i + 1) Then count1 = count1 + 1
Next i
For i = 1 To 10000
A(i) = Rnd()
Next i
'count how often A(i) = A(i+1)
For i = 1 To 9999
If A(i) = A(i + 1) Then count2 = count2 + 1
Next i
Debug.Print "First Loop: " & count1
Debug.Print "Second Loop: " & count2 & vbCrLf
End Sub
总是提供以下输出:
First Loop: 0
Second Loop: 0
似乎不是这种情况,即对Randomize
的连续调用有时会返回相同的种子(至少不足以产生差异)。
但如果那不是自相关的来源 - 那是什么?并且 - 为什么它有时表现为消极而不是积极的自相关?
答案 0 :(得分:3)
仅部分答案,可自由编辑和完成。
当你过度使用Randomize函数时,显然存在相关性。
我尝试了以下代码,条件格式化(值为&gt; 0.5的黑色填充),并且显示出明显的模式(尝试评论Randomize以查看更“随机”模式。(最好看到20 pt列和10%缩放)
Function Rndmap()
Dim i As Long, j As Long
Dim bmp(1 To 512, 1 To 512) As Long
For i = 1 To 512
For j = 1 To 512
' Rnd -1 ' uncomment this line to get a big white and black lines pattern.
Randomize 'comment this line to have a random pattern
bmp(i, j) = IIf(Rnd() < 0.5, 0, 1)
Next j
Next i
Range(Cells(1, 1), Cells(512, 512)) = bmp
End Function
因此,虽然MSDN声明“使用具有相同数值的随机化不会重复前一个序列。”,这意味着如果Timer返回相同值的两倍,则Rnd应保持相同的随机序列而不重置,现场链接还有一些......
一些截图:
仅限Rnd():
使用随机化:
使用Rnd -1和Randomize:
答案 1 :(得分:1)
Randomize
方法使用当前系统时间作为其种子初始化Rnd
函数,您还可以指定一个用Randomize
作为种子的数字。
我决定测试序列在重复之前持续多长时间:
Sub randomRepeatTest()
For i = 1 To 100000
Randomize
randomThread = randomThread & Int(9 * Rnd + 1)
If i Mod 2 = 0 Then
If Left(randomThread, i / 2) = Right(randomThread, i / 2) Then
Debug.Print i / 2
Exit Sub
End If
End If
Next i
End Sub
该子组生成数字0-9的随机序列,并且当序列变为偶数长度时,测试该序列的前半部分是否与后半部分匹配,如果是,则输出序列的长度在重复之前达成在运行了很多次之后,并且在开头重复两次数字的折扣时,结果出现在256( nice )。
向Randomize
提供任何值仍会返回256的结果。
我们在每个循环中随机Rnd
,那么这里发生了什么?
正如我在开头所说的那样,如果没有给Randomize
赋值,它将使用系统时间作为种子。这个时间的解决方案似乎是我无法找到的,但我认为它很低。
我使用timer
的值进行了测试,该值以秒为单位将时间返回到2位小数(例如60287.81)。我还尝试了GetTickCount
,它以毫秒为单位返回系统活动时间(在启动时开始计数)。这两个仍然导致256个序列限制。
那么,为什么当我们将每个循环随机化时,序列会重复?实际情况是,代码在一毫秒内执行。基本上,我们提供相同的数字来随机化每个循环,因此我们实际上并没有改变种子。
那么,如果没有Rnd
,Randomize
会更随机吗?
我在没有Randomize
的情况下再次运行上面的子程序;没有回来。我将循环次数增加到2,000,000;仍然没有。
我已经通过工作簿Rand
公式设法source the algorithm used,我认为该公式与没有初始化种子的Rnd
相同:
C IX,IY,IZ应该在首次进入之前设置为1到30000之间的整数值
IX = MOD(171 * IX,30269)
IY = MOD(172 * IY,30307)
IZ = MOD(170 * IZ,30323)
RANDOM = AMOD(FLOAT(IX)/ 30269.0 + FLOAT(IY)/ 30307.0 + FLOAT(IZ)/ 30323.0,1.0)
它是一个迭代函数,它使用前一个调用的结果生成一个新数字。作为Wichman-Hill程序引用,它保证在序列重复之前将生成超过10 ^ 13个数字。
Rnd
要使算法正常工作,首先需要使用IX
,IY
&amp;的值进行初始化。 IZ
。我们在这里遇到的问题是我们不能用随机变量初始化算法,因为我们需要这个算法来获得随机值,因此唯一的选择是提供一些静态值来实现它。 / p>
我测试了这个,似乎就是这样。打开一个新的Excel实例,? Rnd()
返回0.70554。再次执行相同操作将返回完全相同的数字。
所以我们遇到的问题是Rnd
而不使用Randomize
会给我们一个更长的随机数序列,但是每次打开Excel时该序列都会从同一个地方开始。如果函数依赖于随机生成,例如密码生成,这并不足以因为每次打开Excel时都会得到相同的重复结果。
解决方案
这是我提出的一项功能,似乎效果很好:
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal Milliseconds As LongPtr)
Public Declare Function GetTickCount Lib "kernel32" () As Long
Public randomCount As Long
Function getRandom()
If randomCount Mod 255 = 0 Then
Sleep 1
End If
Randomize GetTickCount
getRandom = Rnd()
randomCount = randomCount + 1
End Function
它使用GetTickCount
函数作为Randomize
种子。每次调用都会向randomCount
变量添加1,并且在每运行255次之后,宏被强制休眠1毫秒(尽管这实际上在我的系统上大约为15),因此GetTickCount
的种子将被更改,因此Rnd
如果偶然在相同的系统时间使用,这当然会返回相同的序列,但是对于大多数情况,它将是生成更多随机数的充分方法。如果没有,则需要使用像Random.Org API这样的东西进行一些奇特的工作。