作为对这个问题的响应,我运行了以下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的连续调用有时会返回相同的种子(至少不够频繁地产生影响)。
但如果这不是自相关的来源--什么是?为什么它有时表现为消极的而不是正的自相关?
发布于 2016-12-13 17:27:26
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 (很好)。
向Randomize提供任何值仍将返回256个结果。
我们随机化了Rnd 每一个循环,那么这里发生了什么呢?
正如我在一开始所说的,如果Randomize没有值,它将使用系统时间作为种子。这个时候的解决方案,我似乎找不到来源,但我相信它是低的。
我使用timer的值进行了测试,它将一天中的时间以秒为单位返回到小数点后的2位(例如60287.81)。我还尝试了GetTickCount,它以毫秒为单位返回系统活动时间(启动时开始计数)。这两种方法都会导致256序列的限制。
那么,为什么当我们将每一个循环随机化时,序列会重复呢?实际上,代码是在毫秒内执行的。本质上,我们提供了相同的数字来随机化每一个循环,所以我们实际上并没有对种子进行洗牌。
因此,在没有Rnd Randomize**?**的情况下,更具有随机性吗?
在没有Randomize的情况下,我再次运行了上面的潜艇;没有返回任何内容。我把循环计数提高到2,000,000,还是什么也没有。
我设法通过工作簿算法来源 Rand公式,我认为它与没有初始化种子的Rnd相同:
在第一项输入前,应将C、IY、IZ设置为1至30000之间的整数值。 九=国防部(171* IX,30269) IY =国防部(172* IY,30307) IZ =国防部(170* IZ,30323) 随机=AMOD(浮点数(IX)/ 30269.0 +浮点数(IY)/ 30307.0 +浮点数(IZ)/ 30323.0,1.0)
它是一个迭代函数,它使用上一次调用的结果生成一个新的数字。作为Wichman过程的引用,它保证在序列重复之前会生成10^13个以上的数字。
用Rnd 解决问题
要使算法工作,首先需要使用IX、IY和IZ的值初始化它。这里的问题是,我们不能用随机变量初始化算法,因为我们需要这个算法来获得随机值,所以唯一的选择是提供一些静态值来使其正常运行。
我对此进行了测试,情况似乎是这样的。打开一个新的Excel实例,? Rnd()返回0.70554。再次执行相同的操作将返回完全相同的数字。
因此,我们遇到的问题是,没有使用Rnd的情况下,Randomize会给我们一个更长的随机数序列,但是每次打开Randomize时,这个序列都会从相同的位置开始。如果函数依赖于随机生成(如密码生成),这是不够的,因为每次打开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变量中添加一个,在每255个运行之后,宏被迫睡眠1毫秒(尽管在我的系统中这实际上是15毫秒),这样GetTickCount的种子就会改变,因此Rnd将返回一个新的数字序列。
这当然会返回相同的序列,如果偶然在相同的系统时间使用,但在大多数情况下,它将是一个充分的方法,以产生更多的随机数。如果不是,它将需要一些花哨的工作,使用类似的Random.Org API。
https://stackoverflow.com/questions/41102399
复制相似问题