Могу ли я использовать API или что-то еще, чтобы приблизиться к ИСТИННОМУ СЛУЧАЙНОМУ НОМЕРУ в VBA? - PullRequest
1 голос
/ 27 апреля 2020

Есть ли API, библиотека или какая-либо другая команда на основе набора микросхем, к которой я могу получить доступ в VBA?

В настоящее время у меня есть установка для получения случайных чисел; однако, когда я проверяю наборы результатов, числа даже не приближаются к созданию хорошей статистической кривой. Я проверил это, сгенерировав 600 смоделированных бросков из двух шестигранных кубиков, каждый раз получая по два кубика вместе. Я надеялся, что номер 7 станет лидером; однако он был вторым дважды, и не было создано соответствующей статистической кривой.

Мой текущий код использует стандартный метод VBA, но, как я уже сказал, не проходит тест статистики:

Randomize
GetRandomNo = Int((6 * RND) + 1)

1 Ответ

2 голосов
/ 27 апреля 2020

Для довольно полного ответа:

Существует множество способов генерации случайных чисел, но одним из них является использование API Windows для выполнения тяжелой работы. Windows имеет функции API для генерации криптографически безопасных случайных байтов, и эти функции могут использовать аппаратные поставщики случайных чисел.

Сначала мы объявляем функции API:

Public Declare PtrSafe Function BCryptOpenAlgorithmProvider Lib "bcrypt.dll" (ByRef phAlgorithm As LongPtr, ByVal pszAlgId As LongPtr, ByVal pszImplementation As LongPtr, ByVal dwFlags As Long) As Long
Public Declare PtrSafe Function BCryptGenRandom Lib "bcrypt.dll" (ByVal hAlgorithm As LongPtr, pbBuffer As Any, ByVal cbBuffer As Long, ByVal dwFlags As Long) As Long
Public Declare PtrSafe Function BCryptCloseAlgorithmProvider Lib "bcrypt.dll" (ByVal hAlgorithm As LongPtr, ByVal dwFlags As Long)

Затем, мы используем этот вызов и используем модуль для уменьшения нашего числа до единицы в желаемом диапазоне:

Public Function RandomRangeWinApi(Lower As Long, Upper As Long) As Long
    Dim hAlg As LongPtr
    Dim iAlg As String
    iAlg = "RNG" & vbNullChar
    BCryptOpenAlgorithmProvider hAlg, StrPtr(iAlg), 0, 0
    Dim lRandom As Long
    BCryptGenRandom hAlg, lRandom, LenB(lRandom), 0
    RandomRangeWinApi = Abs(lRandom) Mod (Upper - Lower + 1) + Lower
    BCryptCloseAlgorithmProvider hAlg, 0
End Function

Этот подход хорош, если вы предполагаете, что целое число имеет бесконечный диапазон значений. Тем не менее, это не так, что означает, что на пределе это неточно. Точно так же умножение предполагает бесконечно точное число, которое также не соответствует действительности и вызывает небольшое смещение.

Мы можем обойти это, непосредственно используя двоичное представление чисел и отбрасывая числа, которые выходят за пределы этого шаблона :

Public Function RandomRangeExact(Lower As Long, Upper As Long) As Long
    'Initialize random number generator
    Dim hAlg As LongPtr
    Dim iAlg As String
    iAlg = "RNG" & vbNullChar
    BCryptOpenAlgorithmProvider hAlg, StrPtr(iAlg), 0, 0
    'Initialize bit template
    Dim template As Long
    Dim i As Long
    Do While template < Upper - Lower
        template = template + 2# ^ i
        i = i + 1
    Loop
    Dim lRandom As Long
    Do
        'Generate random number
        BCryptGenRandom hAlg, lRandom, LenB(lRandom), 0
        'Limit it to template
        lRandom = lRandom And template
    Loop While lRandom > (Upper - Lower) 'Regenerate if larger than desired range (should happen less than 50% of times)
    RandomRangeExact = lRandom + Lower
    BCryptCloseAlgorithmProvider hAlg, 0
End Function

Теперь давайте исследуем производительность вашего решения и обе возможности для игры в кости: я симулировал 100000 случайных чисел для каждого подхода от 1 до 6.

Это результат:

enter image description here

Хотя первый подход, по-видимому, имеет большую дисперсию между числами (особенно меньше единиц и больше двойок), для большинства приложений я бы предположил первое достаточно точно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...