Подтвердить что ты не робот

Действительно ли Excel VBA Rnd() это плохо?

Мне нужен генератор псевдослучайных чисел для моделирования 2D Монте-Карло, у которого нет характерных гиперплоскостей, которые вы получаете с помощью простых LCG. Я проверил генератор случайных чисел Rnd() в Excel 2013, используя следующий код (требуется около 5 секунд для запуска):

Sub ZoomRNG()

Randomize
For i = 1 To 1000
    Found = False
    Do
        x = Rnd()   ' 2 random numbers between 0.0 and 1.0
        y = Rnd()
        If ((x > 0.5) And (x < 0.51)) Then
            If ((y > 0.5) And (y < 0.51)) Then
                ' Write if both x & y in a narrow range
                Cells(i, 1) = i
                Cells(i, 2) = x
                Cells(i, 3) = y
                Found = True
            End If
        End If
    Loop While (Not Found)
Next i

End Sub

Вот простой график x vs y для запуска вышеуказанного кода

введите описание изображения здесь

Не только это не очень случайный взгляд, он имеет более очевидные гиперплоскости, чем печально известный алгоритм RANDU в 2D. В принципе, я использую функцию неправильно или является функцией Rnd() в VBA на самом деле не самым младшим битом?

Для сравнения, вот что я получаю для Mersenne Twister MT19937 в С++.

введите описание изображения здесь

4b9b3361

Ответ 1

Чтобы получить лучший случайный генератор и повысить его производительность, я изменил ваш код следующим образом:

Const N = 1000           'Put this on top of your code module
Sub ZoomRNG()

Dim RandXY(1 To N, 1 To 3) As Single, i As Single, x As Single, y As Single

For i = 1 To N
    Randomize            'Put this in the loop to generate a better random numbers
    Do
        x = Rnd
        y = Rnd
        If x > 0.5 And x < 0.51 Then
            If y > 0.5 And y < 0.51 Then
                RandXY(i, 1) = i
                RandXY(i, 2) = x
                RandXY(i, 3) = y
                Exit Do
            End If
        End If
    Loop
Next
Cells(1, 9).Resize(N, 3) = RandXY
End Sub

Я получаю это после построения результата

введите описание изображения здесь

Результат выглядит лучше, чем ваш вывод кода. Модифицирование приведенного выше кода немного к чему-то вроде этого

Const N = 1000
Sub ZoomRNG()

Dim RandXY(1 To N, 1 To 3) As Single, i As Single, x As Single, y As Single

For i = 1 To N
    Randomize
    Do
        x = Rnd
        If x > 0.5 And x < 0.51 Then
            y = Rnd
            If y > 0.5 And y < 0.51 Then
                RandXY(i, 1) = i
                RandXY(i, 2) = x
                RandXY(i, 3) = y
                Exit Do
            End If
        End If
    Loop
Next
Cells(1, 9).Resize(N, 3) = RandXY
End Sub

дает лучший результат, чем предыдущий

введите описание изображения здесь

Уверен, что Mersenne Twister MT19937 на С++ все еще лучше, но последний результат неплох для проведения симуляций Монте-Карло. FWIW, вам может быть интересно прочитать этот документ: О точности статистических процедур в Microsoft Excel 2010.

Ответ 2

Кажется, что для завершения потребуется в среднем 1000 * 100 * 100 итераций, а VBA обычно немного медленнее, чем собственные формулы Excel. Рассмотрим этот пример

Sub ZoomRNG()
    t = Timer
    [a1:a1000] = "=ROW()"
    [b1:c1000] = "=RAND()/100+0.5"
    [a1:c1000] = [A1:C1000].Value
    Debug.Print CDbl(Timer - t) ' 0.0546875 seconds
End Sub

Обновление

Это не так уж плохо! Это будет работать даже без Randomize

Sub ZoomRNGs() ' VBA.Rnd returns Single
    t = Timer
    For i = 1 To 1000
        Cells(i, 1) = i
        Cells(i, 2) = Rnd / 100 + 0.5
        Cells(i, 3) = Rnd / 100 + 0.5
    Next i
    Debug.Print Timer - t ' 0.25 seconds
End Sub

Sub ZoomRNGd() ' the Excel Function RAND() returns Double
    t = Timer
    For i = 1 To 1000
        Cells(i, 1) = i
        Cells(i, 2) = [RAND()] / 100 + 0.5
        Cells(i, 3) = [RAND()] / 100 + 0.5
    Next i
    Debug.Print Timer - t ' 0.625 seconds
End Sub

и Single имеет примерно половину точности Double:

s = Rnd: d = [RAND()]
Debug.Print s; d; Len(Str(s)); Len(Str(d)) ' " 0.2895625  0.580839555868045  9  17 "

Обновление 2

Я нашел альтернативу C так же быстро, как VBA Rnd.
C:\Windows\System32\msvcrt.dll - это библиотека времени выполнения Microsoft C:

Declare Function rand Lib "msvcrt" () As Long ' this in a VBA module

а затем вы можете использовать его в этом коде x = rand / 32767:

Sub ZoomRNG()
    t = Timer
    Dim i%, x#, y#, Found As Boolean
    For i = 1 To 1000
        Found = False
        Do
            x = rand / 32767 ' RAND_MAX = 32,767
            y = rand / 32767
            If ((x > 0.5) And (x < 0.51)) Then
                If ((y > 0.5) And (y < 0.51)) Then
                    ' Write if both x & y in a narrow range
                    Cells(i, 1) = i
                    Cells(i, 2) = x
                    Cells(i, 3) = y
                    Found = True
                End If
            End If
        Loop While (Not Found)
    Next i
    Debug.Print Timer - t ' 2.875 seconds
End Sub

Ответ 3

Как баланс между скоростью и добротой, я думал о их объединении, как

for...
  z = [rand()] ' good but slow.
  for .. ' just a few
     t = z + rnd()
     t = t - int(t)
     ...

Помните, что хорошая энтропия + плохая энтропия = лучшая энтропия.

Тем не менее, только 0,05 мс за [rand()]. ​​