Can I use API or something else to get close to a TRUE RANDOM NUMBER in VBA?

自闭症网瘾萝莉.ら 提交于 2021-02-08 07:26:12

问题


Is there an API, library, or some other chip-set based command I can access in VBA?

I currently have a setup for getting random numbers; however, when I put the result sets to the test, the numbers do not even come close to generating a good statistical curve. I tested this by generating 600 simulated rolls of 2 six-sided dice totaling the 2 dice together each time. I was hoping for the number 7 to take a huge lead; however it came in second twice with nowhere near the appropriate statistical curve being created.

My current code uses the standard VBA method, but as I said fails the statistics test:

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

回答1:


For a rather full answer:

There are numerous ways to generate random numbers, but one way is to use the Windows API to do the heavy lifting. Windows has API functions to generate cryptographically secure random bytes, and these functions can make use of hardware random number providers.

First, we declare the API functions:

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)

Then, we use this call, and use modulus to reduce our number to one in the desired range:

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

This approach is fine if you assume an integer has an infinite range of values. However, it hasn't, which means at the limits it's imprecise. Similarly, multiplication assumes an infinitely precise number, which also isn't true and causes a slight bias.

We can get around this by directly using the binary presentation of numbers, and discarding numbers that fall outside of this template:

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

Now, let's investigate the performance of your solution and both opportunities for rolling dice: I've simulated 100000 random numbers for each approach between 1 and 6.

This is the result:

While the first approach seems to have larger variance between numbers (specifically less ones and more twos), for most applications I'd assume the first is accurate enough.



来源:https://stackoverflow.com/questions/61450917/can-i-use-api-or-something-else-to-get-close-to-a-true-random-number-in-vba

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!