VBA random numbers produces a repeating sequence at regular intervals - vba

This code is supposed to generate a sequence of 10,000 random numbers in VBA. For some reason I am only able to produce a unique sequence of length 5842, and then it repeats. But, and this is the strangest part, each time I run the code, the sequence starts in a different place. For example in one run, the elements following element 2660 are the same as those following element 8502 (8502-2660= 5842). The next run, I get a sequence that repeats following elements 3704 and 9546 (9546-3704=5842). And so on.
Function NormRand() As Double
' NormRand returns a randomly distributed drawing from a
' standard normal distribution i.e. one with:
' Average = 0 and Standard Deviation = 1.0
Dim fac As Double, rsq As Double, v1 As Double, v2 As Double
Static flag As Boolean, gset As Double
' Each pass through the calculation of the routine produces
' two normally-distributed deviates, so we only need to do
' the calculations every other call. So we set the flag
' variable (to true) if gset contains a spare NormRand value.
If flag Then
NormRand = gset
' Force calculation next time.
flag = False
Else
' Don't have anything saved so need to find a pair of values
' First generate a co-ordinate pair within the unit circle:
Do
v1 = 2 * Rnd - 1#
v2 = 2 * Rnd - 1#
rsq = v1 * v1 + v2 * v2
Loop Until rsq <= 1#
' Do the Math:
fac = Sqr(-2# * Log(rsq) / rsq)
' Return one of the values and save the other (gset) for next time:
NormRand = v2 * fac
gset = v1 * fac
flag = True
End If
End Function

For some reason I am only able to produce a unique sequence of length
5842, and then it repeats. But, and this is the strangest part, each
time I run the code, the sequence starts in a different place
That's by design and well known - that's why the number generation is labelled pseudo random and not random.
By the way, I notice that you are multiplying the two values. That may not be a good idea - as mentioned here.
In your function, you may try to replace Rnd with RndDbl:
Public Function RndDbl(Optional ByRef Number As Single) As Double
' Exponent to shift the significant digits of a single to
' the least significant digits of a double.
Const Exponent As Long = 7
Dim Value As Double
' Generate two values like:
' 0.1851513
' 0.000000072890967130661
' and add these.
Value = CDbl(Rnd(Number)) + CDbl(Rnd(Number) * 10 ^ -Exponent)
RndDbl = Value
End Function
and then modify your code to include a dynamic seed by calling Timer:
Do
v1 = 2 * RndDbl(-Timer) - 1#
v2 = 2 * RndDbl(-Timer) - 1#
rsq = v1 + v2
Loop Until rsq <= 1#
The generated values will still not be true random, but should not take form of a repeated sequence.

Related

Comparing doubles returns false

I have three numbers from my database and want to compare them in an if statement.
I have a simple conevert function that returns only doubles.
Public Function RetDbl(ByVal obj As Variant) As Double
On Error Resume Next
RetDbl = val(Replace(Nz(obj, 0), ",", "."))
End Function
The statement is
If RetDbl(rs.value("NumA")) + RetDbl(rs.value("NumB")) <> (RetDbl(rs.value("NumC")) * 1000) Then
'[... do some code ...]
End If
With RetDbl(rs.value("NumA")) = 0.33, RetDbl(rs.value("NumB") = 0.5 and RetDbl(rs.value("NumC")) = 0.00083
This always returns false
I also tried:
In the direct field (STRG + G): ?cdbl(0.33) + cdbl(0.50) = cdbl(0.83) returns false. When i leave out the last part it returns 0.83
How can i compare these numbers?
Comparing floating numbers is hard. Only yesterday, I've posted this question
My solution:
Public Function DblSafeCompare(ByVal Value1 As Variant, ByVal Value2 As Variant) As Boolean
'Compares two variants, dates and floats are compared at high accuracy
Const AccuracyLevel As Double = 0.00000001
'We accept an error of 0.000001% of the value
Const AccuracyLevelSingle As Single = 0.0001
'We accept an error of 0.0001 on singles
If VarType(Value1) <> VarType(Value2) Then Exit Function
Select Case VarType(Value1)
Case vbSingle
DblSafeCompare = Abs(Value1 - Value2) <= (AccuracyLevelSingle * Abs(Value1))
Case vbDouble
DblSafeCompare = Abs(Value1 - Value2) <= (AccuracyLevel * Abs(Value1))
Case vbDate
DblSafeCompare = Abs(CDbl(Value1) - CDbl(Value2)) <= (AccuracyLevel * Abs(CDbl(Value1)))
Case vbNull
DblSafeCompare = True
Case Else
DblSafeCompare = Value1 = Value2
End Select
End Function
Note that the AccuracyLevel (epsilon) could be set to a smaller value, and I'm using the same value for singles and doubles, but it did well for my purposes.
I'm using a relative epsilon, but multiplying it with the first, and not the largest value, since if there's a significant difference the comparison will fail anyway.
Note that I'm using <= and not < since else DblSafeCompare(cdbl(0) ,cdbl(0)) would fail
Note that this function checks for type equality, so comparing integers to longs, doubles to singles, etc. all fails. Comparing Null to Null passes, however.
Implement it:
?DblSafeCompare(cdbl(0.33) + cdbl(0.50) ,cdbl(0.83))
?DblSafeCompare(cdbl(0.331) + cdbl(0.50) ,cdbl(0.83))
Comparing floating point numbers is really an issue, if you try to do it without understanding of the nature of the floating numbers.
Here is a nice article about it - https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html and How dangerous is it to compare floating point values?
In general, this problem is so big, that some languages like C# have developed a specific class called Decimal which makes comparing run as it would be expected by a non-programmer. Decimal info. In VBA, a similar class is Currency. Thus
CCur(0.33) + CCur(0.50) = CCur(0.83)
Returns True. VBA supports the function CDec, which converts a double to a decimal number, but it does not support the class Decimal. Thus:
CDec(0.33) + CDec(0.50) = CDec(0.83)
would also return True. And is with some better accuracy than Currency. CDec documentation.

VBA optimization - function with multiple args

Hi I am trying to create a function that takes as input a few parameters (irrelevant to the variables optimized) and a variable over which I want to optimize. For example for foo_function(a,b,c,x) I want to pass a,b,c as fixed parameters to foo_function, and finding the minimum of foo_function by changing x. If I was able to work in Matlab or Python this should be relatively easy as a function can return a handle to another function, but that's not possible in VBA. Im trying to use the code in http://www.quantcode.com/modules/mydownloads/singlefile.php?lid=424
Does anybody have any idea how to proceed?
You can declare foo_function to take a parameter array, treat it as a function of 1 variable if only 1 parameter is passed, and if more than 1 is passed, you can store the parameters in static variables. This allows your calling code to set the parameters of foo_function before passing its name to optimization code which will treat it as a function of 1 variable.
As a proof of concept, the following function represents a quadratic function:
Function quad(ParamArray args()) As Double
Dim x As Double
Static a As Double
Static b As Double
Static c As Double
If UBound(args) = 0 Then
x = args(0)
Else 'assumes that at least 3 parameters passed
a = args(0)
b = args(1)
c = args(2)
If UBound(args) = 3 Then
x = args(3)
Else
Exit Function 'function call just initializes statics
End If
End If
quad = a * x ^ 2 + b * x + c
End Function
When just 1 is passed, quad(x) just evaluates ax^2 + bx + c with its current values for those coefficients. If 4 is passed then it is interpreted as a call to quad(a,b,c,x), with the obvious meaning. If just 3 parameters are passed, it is called like a sub:
quad a,b,c
and doesn't return anything but sets the static parameters to the passed values.
To test it, I wrote a crude numerical differentiation function:
Function Derivative(f As String, x As Double, h As Double) As Double
Derivative = (Application.Run(f, x + h) - Application.Run(f, x)) / h
End Function
This approximates the derivative of f at the given x value using the given step size.
The following test sub shows how quad can be set before passing it the the derivative function:
Sub test()
quad 1, 2, -3 'initializes quad to be x^2 + 2x - 3
Debug.Print Derivative("quad", 2, 0.0001) 'should be approximately 6
quad 3, 2, 1
Debug.Print Derivative("quad", 2, 0.0001) 'now around 14
End Sub
Output:
6.00010000001205
14.0003000000277
Another, in some ways simpler, approach is to use public variables instead of actual parameters for the parameters to foo_function that are irrelevant to the variables being optimized. Then the calling code could assign to these public variables before optimizing. This approach would eliminate the need for parameter arrays but has the drawback of using global-type variables which tend to make programs not sufficiently modular.

Math.round: round number by conditions

I am trying to round a number by the next things:
number with unit digits between 5-10 will be rounded to the nearest 10*x:
(for example: 5->10, 6->10, 27->30, 40->40, 56->60, etc).
number with unit digits between 1-4 will be rounded to 0:
(for example: 4->0, 11->10, 12->10, 20->20, etc).
I want to write it bu Math.Round function.
Meantime, I did it without it:
Dim rest As Integer = r Mod 10
' round up
If rest >= 5 Then
r = r + (10 - rest)
Else ' round down
r = r - rest
End If
Any help appreciated!
Very simple to do with Math.Round
Dim roundedDecade as Double, originalNumber as Double
:
roundedDecade = Math.Round(originalNumber / 10, MidpointRounding.AwayFromZero) * 10
If you want to force the use of integers, just use CDbl and CInt to force some conversions.
Dim roundedDecade as Integer, originalNumber as Integer
:
roundedDecade = CInt(Math.Round(CDbl(originalNumber) / 10, MidpointRounding.AwayFromZero) * 10)

Random Number Generation to Memory from a Distribution using VBA

I want to generate random numbers from a selected distribution in VBA (Excel 2007).
I'm currently using the Analysis Toolpak with the following code:
Application.Run "ATPVBAEN.XLAM!Random", "", A, B, C, D, E, F
Where
A = how many variables that are to be randomly generated
B = number of random numbers generated per variable
C = number corresponding to a distribution
1= Uniform
2= Normal
3= Bernoulli
4= Binomial
5= Poisson
6= Patterned
7= Discrete
D = random number seed
E = parameter of distribution (mu, lambda, etc.) depends on choice for C
(F) = additional parameter of distribution (sigma, etc.) depends on choice for C
But I want to have the random numbers be generated into an array, and NOT onto a sheet.
I understand that where the "" is designates where the random numbers should be printed to, but I don't know the syntax for assigning the random numbers to an array, or some other form of memory storage instead of to a sheet.
I've tried following the syntax discussed at this Analysis Toolpak site, but have had no success.
I realize that VBA is not the ideal place to generate random numbers, but I need to do this in VBA. Any help is much appreciated! Thanks!
Using the inbuilt functions is the key. There is a corresponding version for each of these functions but Poisson. In my presented solution I am using an algorithm presented by Knuth to generate a random number from the Poisson Distribution.
For Discrete or Patterned you obviously have to write your custom algorithm.
Regarding the seed you can place a Randomize [seed] before filling your array.
Function RandomNumber(distribution As Integer, Optional param1 = 0, Optional param2 = 0)
Select Case distribution
Case 1 'Uniform
RandomNumber = Rnd()
Case 2 'Normal
RandomNumber = Application.WorksheetFunction.NormInv(Rnd(), param1, param2)
Case 3 'Bernoulli
RandomNumber = IIf(Rnd() > param1, 1, 0)
Case 4 'Binomial
RandomNumber = Application.WorksheetFunction.Binom_Inv(param1, param2, Rnd())
Case 5 'Poisson
RandomNumber = RandomPoisson(param1)
Case 6 'Patterned
RandomNumber = 0
Case 7 'Discrete
RandomNumber = 0
End Select
End Function
Function RandomPoisson(ByVal lambda As Integer) 'Algorithm by Knuth
l = Exp(-lambda)
k = 0
p = 1
Do
k = k + 1
p = p * Rnd()
Loop While p > l
RandomPoisson = k - 1
End Function
Why not use the inbuilt functions?
Uniform = rnd
Normal = WorksheetFunction.NormInv
Bernoulli = iif(rnd()<p,0,1)
Binomial = WorksheetFunction.Binomdist
Poisson = WorksheetFunction.poisson
Patterned = for ... next
Discrete =
-
select case rnd()
case <0.1
'choice 1
case 0.1 to 0.4
'choice 2
case >0.4
'choice 3
end select

I need some help on designing a program that will perform a minimization using VBA Excel

How do I use Excel VBA to find the minimum value of an equation?
For example, if I have the equation y = 2x^2 + 14, and I want to make a loop that will slowly increase/decrease the value of x until it can find the smallest value possible for y, and then let me know what the corresponding value of x is, how would I go about doing that?
Is there a method that would work for much more complicated equations?
Thank you for your help!
Edit: more details
I'm trying to design a program that will find a certain constant needed to graph a nuclear decay. This constant is a part of an equation that gets me a calculated decay. I'm comparing this calculated decay against a measured decay. However, the constant changes very slightly as the decay happens, which means I have to use something called a residual-square to find the best constant to use that will fit the entire decay best to make my calculated decay as accurate as possible.
It works by doing (Measured Decay - Calculated Decay) ^2
You do that for the decay at several times, and add them all up. What I need my program to do is to slowly increase and decrease this constant until I can find a minimum value for the value I get when I add up the residual-squared results for all the times using this decay. The residual-squared that has the smallest value has the value of the constant that I want.
I already drafted a program that does all the calculations and such. I'm just not sure how to find this minimum value. I'm sure if a method works for something like y = x^2 + 1, I can adapt it to work for my needs.
Test the output while looping to look for the smallest output result.
Here's an Example:
Sub FormulaLoop()
Dim x As Double
Dim y As Double
Dim yBest As Double
x = 1
y = (x ^ 2) + 14
yBest = y
For x = 2 To 100
y = (x ^ 2) + 14
If y < yBest Then
yBest = y
End If
Next x
MsgBox "The smallest output of y was: " & yBest
End Sub
If you want to loop through all the possibilities of two variables that make up x then I'd recommend looping in this format:
Sub FormulaLoop_v2()
Dim MeasuredDecay As Double
Dim CalculatedDecay As Double
Dim y As Double
Dim yBest As Double
MeasuredDecay = 1
CalculatedDecay = 1
y = ((MeasuredDecay - CalculatedDecay) ^ 2) + 14
yBest = y
For MeasuredDecay = 2 To 100
For CalculatedDecay = 2 To 100
y = ((MeasuredDecay - CalculatedDecay) ^ 2) + 14
If y < yBest Then
yBest = y
End If
Next CalculatedDecay
Next MeasuredDecay
MsgBox "The smallest output of y was: " & yBest
End Sub