Excel VBA Powerful Random Number Generator - vba

I'll try and keep this as basic and to the point as possible.
Basically, I have weights/probabilities associated with a certain range of numbers. For example :
0: 10%
1: 50%
2: 15%
3: 25%
This then translates into cumulative probabilities :
0: 10%
1: 60%
2: 75%
3: 100%
Using a uniform RNG in VBA, the program generates numbers between 0 and 1, or whatever inferior limit it is set to. Using the same values as the previous example, but only generating numbers greater than 60% (and <= 100%), this results in numbers between 0.6 - 1.0.
This is where I'm stuck. I need to convert these random numbers very efficiently into their "corresponding values".
All of it is stored in VBA variables and needless to say, I don't want to have to write a Select Case for every situation since they're actually 120 different variables and weights.
As of right now, this is what I have to generate those numbers:
RandomNumber = LowerLimit + Rnd() * (1 - LowerLimit)
Thanks is advance for all your help! If I missed a post that was discussing this particular issue please feel free to refer me to it but I really didn't find anything relating to corresponding random numbers.

Place the following function into a public module. You would call it like so mynumber = WeightedRnd(Array(0, 1, 2, 3), Array(0.1, 0.5, 0.15, 0.25)).
Public Function WeightedRnd(values As Variant, weights As Variant) As Double
'First, calculate the cumulative weights
Dim cumulativeWeight As Double
For i = 0 To UBound(weights)
weights(i) = weights(i) + cumulativeWeight
cumulativeWeight = weights(i)
Next
'Next, generate our random number
Dim randomNumber As Double
randomNumber = Rnd()
'Finally, figure out which "bucket" it falls into
For i = 0 To UBound(weights)
If randomNumber <= weights(i) Then
WeightedRnd = values(i)
Exit Function
End If
Next
End Function

Related

Is there a way to generate a random number using a Minimum and Maximum value using Single in VB.Net?

I'm looking for a random number generator that I can use to alter the size of a picture box on my form. Currently, I have this solution found here on StackOverflow:
Public Function GetRandomNumber(ByVal Min As Integer, ByVal Max As Integer) As Integer
Static lcGenerator As System.Random = New System.Random()
Return lcGenerator.Next(Min, Max)
End Function
As it stands, the function will generate a random integer between the range specified. This is great, but I am noticing that often the picture box doesn't change in size with a decent amount of variation. The following code shows how I am calculating variance in size:
Dim lcModifier As Decimal = (GetRandomNumber(-20, 11) * 0.01)
pbForeground.Width = CInt((0.4 - lcModifier) * pbBackground.Width)
pbForeground.Height = CInt((0.4 - lcModifier) * pbBackground.Height)
The idea is that I generate a random number between -20 and 11. This would yield a 20% increase or 10% decrease, as the minimum is inclusive but the maximum is exclusive, and I am subtracting the modifier.
I had the idea to use a Single value in place of the integer value. The Single type gave me more "room" away from 0 than a Double would, but the Next method only uses Integer values, and we're back to my initial problem.
The NextDouble method works differently to the Next method, and I am unable to specify Min and Max values, nor can I generate a negative value.
Is there any way I can generate a value using Minimum and Maximum bounds that is not close to zero?
Thanks,
sunnyCr
The NextDouble return a percentage. You just need to multiply by the range of value and then translate to the desired starting position.
Function GetRandomNumber(ByVal low As Double, high As Double) As Double
Static rng As New Random
Return (rng.NextDouble() * (high - low)) + low
End Function

Randomize seems to miss many possible seeds

In trying to solve this question, I wrote the following in an attempt to implement the Box-Muller transform to generate random normal variables in pure VBA:
Function RandNorm(Optional mean As Double = 0, Optional sd As Double = 1) As Double
Dim s As Double
s = Sqr(-2 * Log(Rnd())) * Cos(6.283185307 * Rnd()) '6.28 etc. is 2*pi
RandNorm = mean + sd * s
End Function
The following somewhat weak test always works, returning a number close to 0:
Sub test1()
Randomize
Dim s As Double
Dim i As Long
For i = 1 To 17000000
s = s + RandNorm()
Next i
Debug.Print s / 17000000
End Sub
On the other hand, the following test never works (because it tries to take the log of 0, which is undefined):
Sub test2()
Randomize
Dim s As Double
Dim i As Long
Debug.Print Rnd() 'just to clock it
For i = 1 To 17000000
s = s + RandNorm()
Next i
Debug.Print s / 17000000
End Sub
The problem is that rnd() returns 0 on average once out of every 2^24 (a bit less than 17,000,000) calls. It is of course easy enough to tweak the definition of RandNorm to avoid the zero (see the linked-to question), but I am still puzzled by the above code. It would make perfect sense to me if each test failed half the time (when the zero is fed into Log()) and worked half the time (when the zero is fed into Cos()). It seems that Randomize avoids at least half of the possible seeds.
Why does Randomize behave this way? Is there a way to seed the random number generator so that all possible states of the random number generator can occur?
On Edit
If I define the following sub:
Sub ReRandomize()
Dim r As Double
Randomize
If Rnd() > 0.5 Then r = Rnd()
End Sub
And modify test1 and test2 above to use ReRandomize instead of Randomize, both of the test subs will fail 50% of the time, so that might answer the part of the question about if there is "a way to seed the random number generator so that all possible states of the random number generator can occur?" It is still mysterious as to why Randomize behaves the way that it does. This is the second time that an Excel VBA question made me realize that Randomize is a weird sub. None of this matters very much for typical use of rnd(), but it does underscore that it is a somewhat low quality random number generator which shouldn't be used for serious statistical work.
I simply modified the Rnd calc to not include 0 or 1. You have to remember that the Rnd Function can produce a number (of type double) in the range of 0 or 1. Therefore, it's chances of having a duplicate number are pretty low.
dbl1stRnd = Rnd()
dblRnd = (0.9999 - 0.0001) * dbl1stRnd + 0.0001
s = Sqr(-2 * Log(dblRnd)) * Cos(6.283185307 * dblRnd) '6.28 etc. is 2*pi
Some example outputs of the regular Rnd() function with Randomize:
3.633606E-02
0.2324036
0.3460443
0.5870923
5.553758E-02
0.2629338
0.2400494
0.1982901
0.5923058
0.7915452
0.4874671
0.2062811
0.5676001
0.1178594
1.932621E-03
0.4326598
0.8291379
I hope this explains some and is what you are looking for.

VBA excel generate any random number [duplicate]

This question already has answers here:
Generate random number VBA [closed]
(4 answers)
Closed 5 years ago.
I just learning VBA in excel and just want to know how to generate any random number using I believe RND function.
Say we want to randomly pick a number from any number that Excel can represent (positive or negative).
The largest positive value in Excel is 9.99999999999999E+307
Multiply this by Rnd() should generate a random value between 0 and the max
Then randomly pick the sign (+-) of the result:
Sub Randum()
Dim a As Double, b As Double, wf As WorksheetFunction
Set wf = Application.WorksheetFunction
a = 9.99999999999999E+307
Randomize
b = wf.Choose(wf.RandBetween(1, 2), -1, 1) * Rnd() * a
MsgBox b
End Sub
You need to focus on the concept of general numbers in computer science, rather than the more abstract understanding of random. In computer science, random numbers (almost) always start out, as a number between 0 and 1. You can then multiply it, to increase the size of the range and add/subtract numbers to change the offset.
'Example of using Rnd in VBA
Sub RandomPrint()
Dim rand As Double
rand = Rnd * 49 + 1
rand = Round(rand)
Debug.Print "Random integer between 1 and 50: " + CStr(rand)
End Sub
https://www.techonthenet.com/excel/formulas/rnd.php
https://msdn.microsoft.com/en-us/library/f7s023d2(v=vs.90).aspx

Converting input range to array in function

I have been learning VBA and thought I was getting the hang of it, but for some reason this basic task is eluding me. I am trying to create a function where I select an input region of numbers (really only one row or column), and then output the summation of the numbers in a cell. Here is my code:
Function CashFlow(CashArray As Excel.Range)
Dim cashflows() As Variant
cashflows = CashArray.Value
amount = CashArray.Rows.Count
dim y()
redim y(amount)
Sum = 0
For i = 1 To amount
y(i) = cashflows(i)
Sum = Sum + y(i)
Next i
CashFlow = Sum
End Function
Despite me knowing how to do essentially this in a subroutine, the fact that I'm getting my data from the function's input is throwing me off. How do I accomplish this task?
Just to summarize the above comments, please give the following code a try:
Option Explicit
Function CashFlow(CashArray As Excel.Range)
Dim sum As Double
Dim x As Long, y As Long
Dim cashflows() As Variant
cashflows = CashArray.Value
sum = 0
For y = LBound(cashflows, 1) To UBound(cashflows, 1)
For x = LBound(cashflows, 2) To UBound(cashflows, 2)
sum = sum + cashflows(y, x)
Next x
Next y
CashFlow = sum
End Function
Note, that this code summarizes all cells in the given range (even if there are multiple rows and columns in that range). Basically, the range is stored in the array cashflows. As mentioned in the comments, this will yield a two-dimensional array. The function LBound(cashflows) will give you the Lower Boundary of that array.
By specifying LBound(cashflows, 1) I am specifically asking for the lower boundary of the first dimension (in this case the rows). UBound returns the Upper Boundary of that array for the specified dimension. Similarily UBound(cashflows, 2) returns the last column (second dimension) of the given array.
Remark: I am always using LBound() and UBound() in my code instead of starting with 0 or 1 to avoid coding errors. Some people prefer Option Base 0 and some prefer Option Base 1. While neither will have an impact on the above (as ranges always yield an array starting with 1) it is just good coding practice to prevent bugs in the future.

Random number macro does not return desired value

I have created a simple module that is meant to do following:
generate random number between 0 and 999;
if number is lower than 500, assign value "lower", otherwise "higher"
write the random number and assigned value in cells A1 and B1
repeat the process 100,000 times.
Problem is, it returns assigned value "lower" even if the number is higher than or equal to 500, i.e., all assigned values, 100,000 of them, are "lower"!
I'd appreciate if someone can check where I'm going wrong with this code; I'm not an expert in VBA, but I thought I could do this myself... :\
Sub MacroRanNum()
Dim RunNum As Integer
Dim Outcome As String
For i = 1 To 100000
Randomize
RanNum = Int((999 - 0 + 1) * Rnd + 0)
If RunNum < 500 Then
Outcome = "Lower"
ElseIf RunNum >= 500 Then
Outcome = "Higher"
Else
Outcome = "Error!"
End If
Sheets("podatak").Cells(i, 1) = RanNum
Sheets("podatak").Cells(i, 2) = Outcome
Next i
End Sub
Your variable name is RanNum but you check for RunNum
OPTION EXPLICIT could help to avoid such problems.
You are mixing your variable names. You define and check against RunNum but your random value and your display value are RanNum. You are never testing against the value you randomly generated.