如何理解由于过多地播种RNG而导致的自相关?

时间:2016-12-12 13:51:45

标签: vba random

在回复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的连续调用有时会返回相同的种子(至少不足以产生差异)。

但如果那不是自相关的来源 - 那是什么?并且 - 为什么它有时表现为消极而不是积极的自相关?

2 个答案:

答案 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

使用随机化: randomize

使用Rnd -1和Randomize: Rnd -1

答案 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个序列限制。

那么,为什么当我们将每个循环随机化时,序列会重复?实际情况是,代码在一毫秒内执行。基本上,我们提供相同的数字来随机化每个循环,因此我们实际上并没有改变种子。

那么,如果没有RndRandomize会更随机吗?

我在没有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

的问题

要使算法正常工作,首先需要使用IXIY&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这样的东西进行一些奇特的工作。