Calling MS Excel function from MS Access VBA - vba

I am working an MS Access application a part of which uses Beta Distribution function. Since MS Access does not have Beta Distribution function of its own I'm using calling BetaDist function from MS Excel. I've tested the code in MS Excel and it seems to run successfully. In MS Access also the code is working fine and generating correct results but the time taken by Access is very high than the time taken by Excel. I'm posting the part of code which utilizes BetaDist function and also the slowest portion of the code. I want to reduce the time taken by Access. Any help is appreciated.
Part of Code which utilizes BetaDist:
For i = 1 To UBound(arrBetaParam)
If arrBetaParam(i).Alpha <= 0 Or arrBetaParam(i).Beta <= 0 Or tryOutValue > arrBetaParam(i).ExpValue Then
dblTempEP = 0
Else
If tryOutValue > arrBetaParam(i).LastKnownGoodValue Then
dblTempEP = 0
Else
dblTempEP = 1
End If
Dim bt As Double
bt = -1
On Error Resume Next
bt = Excel.WorksheetFunction.BetaDist(tryOutValue, arrBetaParam(i).Alpha, arrBetaParam(i).Beta, 0, arrBetaParam(i).ExpValue)
tj = bt
If bt > -1 Then
If bt > 1 Then bt = 1
If bt < 0 Then bt = 0
arrBetaParam(i).LastKnownGoodValue = tryOutValue
dblTempEP = 1 - bt
End If
On Error GoTo 0
End If
OEP = OEP + dblTempEP * arrBetaParam(i).Rate
'sumRate = sumRate + arrBetaParam(i).Rate
Next

Your code is probably taking so long due to the fact it has to open the Excel application.
BetaDist is not complicated to implement. Why not create a VBA function in Acces VBA. Here is the formula:
f(x) = B(alpha,beta)-1 xalpha-1(1-x)beta-1
Here I found a decent implementation. Didn't test it though:
Option Explicit
Const n As Long = 200 ' increase for accuracy, decrease for speed
Public aa As Double
Public bb As Double
Function BetaDist1(x As Double, a As Double, b As Double)
Dim d1 As Double
Dim d2 As Double
Dim n1 As Long
Dim n2 As Long
aa = a
bb = b
n1 = x * n
n2 = n - n1
d1 = SimpsonInt(0, x, n1)
d2 = SimpsonInt(x, 1, n2)
BetaDist1 = d1 / (d1 + d2)
End Function
Function SimpsonInt(ti As Double, tf As Double, ByVal n As Long) As Double
' shg 2006
' Returns the integral of Func (below) from ti to tf _
using Composite Simpson's Rule over n intervals
Dim i As Double ' index
Dim dH As Double ' step size
Dim dOdd As Double ' sum of Func(i), i = 1, 3, 5, 7, ... n-1, i.e., n/2 values
Dim dEvn As Double ' sum of Func(i), i = 2, 4, 6, ... n-2 i.e., n/2 - 1 values
' 1 + (n/2) + (n/2 - 1) + 1 = n+1 function evaluations
If n < 1 Then Exit Function
If n And 1 Then n = n + 1 ' n must be even
dH = (tf - ti) / n
For i = 1 To n - 1 Step 2
dOdd = dOdd + Func(ti + i * dH)
Next i
For i = 2 To n - 2 Step 2
dEvn = dEvn + Func(ti + i * dH)
Next i
SimpsonInt = (Func(ti) + 4# * dOdd + 2# * dEvn + Func(tf)) * dH / 3# ' weighted sum
End Function
Function Func(t As Double) As Double
Func = t ^ (aa - 1) * (1 - t) ^ (bb - 1)
End Function

You could do like this:
Dim xls As Excel.Application
Set xls = New Excel.Application
' Begin loop.
bt = xls.WorksheetFunction.BetaDist(tryOutValue, arrBetaParam(i).Alpha, arrBetaParam(i).Beta, 0, arrBetaParam(i).ExpValue)
' End loop.
xls.Quit
Set xls = Nothing

Related

What is different between Microsoft.VisualBasic.VBMath.Rnd and the old VB Rnd() function?

I am trying to convert some old VB code to .Net but am having a problem with the Rnd function.
Old Code
Private Function Decode() As String
Dim r As Integer
Dim x As Integer
Dim c As Integer
Dom Code As String = "m[n-Msr0Xn*ca8qiGeIL""7'&;,_*EV{M;[{2bEmg8u!^s*+O37!692{-Y4IS"
x = Int(Rnd(-7))
For r = 1 To Len(Code)
x = Int(Rnd() * 96)
c = Asc(Mid(Code, r, 1))
c = c + x
If c >= 126 Then c = c - 126 + 32
Decode = Decode & Chr$(c)
Next
End Function
The decoded text is "Bet you needed more than a pencil and paper to get this one!"
This is what I have done:
Private Function Decode() As String
Dim r As Integer
Dim x As Integer
Dim c As Integer
Dim Answer As String
Dim Code As String = "m[n-Msr0Xn*ca8qiGeIL""7'&;,_*EV{M;[{2bEmg8u!^s*+O37!692{-Y4IS"
x = CType(Microsoft.VisualBasic.VBMath.Rnd(-7), Integer)
For r = 0 To sList.Length - 1
x = CType(Microsoft.VisualBasic.VBMath.Rnd() * 96 - 0.5, Integer)
c = Asc(sList.Substring(r, 1))
c = c + x
If c >= 126 Then c = c - 126 + 32
Answer &= Chr(c)
Next
Return Answer
End Function
but this is what I get "Bet you needed morB th(n a pencil and paper to get this one!"
I suspect its how I am castng to an int but I can't figure out how.
When I run your "Old Code" (after correcting the typos) under Office VBA (which should have the same Rnd implementation as VB6), I get the same result as you say you get from VB.Net. Therefore, there must be an error in the string assigned to Code.
Public Function Decode() As String
Dim r As Integer
Dim x As Integer
Dim c As Integer
Dim Code As String
Code = "m[n-Msr0Xn*ca8qiGeIL""7'&;,_*EV{M;[{2bEmg8u!^s*+O37!692{-Y4IS"
x = Int(Rnd(-7))
For r = 1 To Len(Code)
x = Int(Rnd() * 96)
c = Asc(Mid(Code, r, 1))
c = c + x
If c >= 126 Then c = c - 126 + 32
Decode = Decode & Chr$(c)
Next
End Function
Thanks to TnTinMan for leading me to the solution. The problem was created when I copied the string. I used a I instead of an l. The font that was used has these two characters looking identical. I also mistook ' for `.
so basically all I had to do was subtract .5

For Loop running error

This code doesn't find the correct output
for say n= 1 (although it gives the correct output for say n= 2,3,4..etc.)
if we put n= 1 to find x then the i loop will continue from 1 to 0, hence the first term in x should vanish and leftover should be the second term 5; but it gives 0 ?
Is there any limitation on the input n to run the for loop ?I would appreciate any help.
Function math(n As Integer) As Double
Dim i As Integer
Dim x As Double
For i = 1 To n - 1
x = (n - 1) * 2 + 5
sum = sum + x
Next i
math = sum
End Function
Why not simply:
Function math(n As Integer) As Double
Math = ((n - 1) * 2 + 5) * Abs((n - 1) - (n = 1))
End Function
???
if the answer is correct then Math = (n * 2 + 3) * Abs((n - 1) - (n = 1)) would be easier to understand and make much more sense
In the for loop, if you don't precise the Step, the variable will only increment by 1.
And here, you start at 1 to go to 0, so the loop won't execute, you need to test n to cover both cases :
Function math(n As Integer) As Double
If n < 0 Then Exit Function
Dim i As Integer
Dim x As Double
Dim Summ As Double
Select Case n
Case Is > 1
For i = 1 To n - 1
x = (i - 1) * 2 + 5
Summ = Summ + x
Next i
Case Is = 1
Summ = (n - 1) * 2 + 5
Case Is = 0
Summ = 5
Case Else
MsgBox "This case is not supported", vbInformation + vbOKOnly
Exit Function
End Select
math = Summ
End Function
If n = 1, you end up with For i = 1 To 0 which is incorrect and
should be expressed For i = 1 To 0 STEP -1.
So I suggest you add the STEP BYand make sure it is either 1 to -1 depending on N.

Function to insert a specific formula excel VBA (decimal to fractional inches)

I have a list of distances that I would like to display like you would read off a tape measure, for example 144.125 would display as 144 1/8". I have the following formula
=TEXT(A1,"0"&IF(ABS(A1-ROUND(A1,0))>1/32,"0/"&CHOOSE(ROUND(MOD(A1,1)*16,0),16,8,16,4,16,8,16,2,16,8,16,4,16,8,16),""))&""""
I'd like to simplify it to a 1 argument function (for A1) so I could use it throughout the workbook, but the amount of " quotes and vba keywords is causing problems. Is there an easier way to get a UDF to insert a complicated formula?
If you want to use a UDF with visual basic then try this:
Public Function Fraction(ByVal x As Double, Optional ByVal tol As Double = 1 / 64#) As String
Dim s As Long, w As Long, d As Long, n As Long, f As Double
s = Sgn(x): x = Abs(x)
If s = 0 Then
Fraction = "0"
Exit Function
End If
w = CInt(WorksheetFunction.Floor_Precise(x)): f = x - w
d = CInt(WorksheetFunction.Floor_Precise(1 / tol)): n = WorksheetFunction.Round(f * d, 0)
Dim g As Long
Do
g = WorksheetFunction.Gcd(n, d)
n = n / g
d = d / g
Loop While Abs(g) > 1
Fraction = Trim(IIf(s < 0, "-", vbNullString) + CStr(w) + IIf(n > 0, " " + CStr(n) + "/" + CStr(d), vbNullString))
End Function
With results:
The TEXT function can do this directly:
A B
1 144,1250 144 1/8 "
Formula in B1:
=TEXT(A1;"# ??/??\""")
Greetings
Axel

Simulation runs fine # 10k cycles, but gets error 13 (type mismatch) # 100k cycles

First off, here's my code:
Sub SimulatePortfolio()
Dim lambda As Double
Dim num As Integer
Dim cycles As Long
Column = 12
q = 1.5
lambda = 0.05
cycles = 100000
Dim data(1 To 100000, 1 To 10) As Integer
Dim values(1 To 10) As Double
For i = 1 To 10
values(i) = 0
Next i
temp = lambda
For i = 1 To cycles
lambda = temp
num = 10
t = 0
Dim temps(1 To 10) As Integer
For k = 1 To 10
temps(k) = 1000
Next k
Do While (t < 10 And num > 0)
t = t + tsim(lambda, num)
For j = 1 To 10
If (j > t) Then
temps(j) = temps(j) - 50
End If
Next j
num = num - 1
If (num <= 0) Then
Exit Do
End If
lambda = lambda * q
Loop
For l = 1 To 10
values(l) = values(l) + temps(l)
data(i, l) = temps(l)
Next l
Next i
For i = 1 To 10
Cells(i + 1, Column) = values(i) / cycles
'Problem occurs on this line:
Cells(i + 1, Column + 1).Value = Application.WorksheetFunction.Var(Application.WorksheetFunction.Index(data, i, 0))
Next i
End Sub
Function tsim(lambda As Double, num As Integer) As Double
Dim v As Double
Dim min As Double
Randomize
min = (-1 / lambda) * Log(Rnd)
For i = 1 To (num - 1)
Randomize
v = (-1 / lambda) * Log(Rnd)
If (min > v) Then
min = v
End If
Next i
tsim = min
End Function
When I set the value for cycles to 10000, it runs fine without a hitch. When I go to 100000 cycles, it gets an Error 13 at the indicated line of code.
Having been aware that Application.Tranpose is limited to 65536 rows with variants (throwing the same error) I tested the same issue with Index
It appears that Application.WorksheetFunction.Index also has a limit of 65536 rows when working with variants - but standard ranges are fine
So you will need to either need to dump data to a range and work on the range with Index, or work with two arrays
Sub Test()
Dim Y
Dim Z
'works in xl07/10
Debug.Print Application.WorksheetFunction.Index(Range("A1:A100000"), 1, 1)
Y = Range("A1:A65536")
`works
Debug.Print Application.WorksheetFunction.Index(Y, 1, 1)
'fails in xl07/10
Z = Range("A1:A65537")
Debug.Print Application.WorksheetFunction.Index(Z, 1, 1)
End Sub

Ignore overflow error when multiplication result is bigger than what a double can hold

During some iterative optimization, the following VBA code for the computation of the bivariate normal CDF sometimes throws an Overflow error on the line with z = hx * hy * c inside the while loop of the upper function.
I debugged the code and the overflow occurs when the numbers being multiplied result in a number bigger than what a double can hold.
Can you show me how to handle the problem by ignoring the iterations of the loop with such high values - I guess that's the only feasible solution (?). I tried myself with a On Error Goto nextiteration line before the multiplication and placing the nextiteration jump point before the Wend, but the error persists.
Function tetrachoric(x As Double, y As Double, rho As Double) As Double
Const FACCURACY As Double = 0.0000000000001
Const MinStopK As Integer = 20
Dim k As Integer
Dim c As Double
Dim z As Double
Dim s As Double
Dim hx As Double
Dim hx1 As Double
Dim hx2 As Double
Dim hy As Double
Dim hy1 As Double
Dim hy2 As Double
Dim CheckPass As Integer
hx = 1
hy = 1
hx1 = 0
hy1 = 0
k = 0
c = rho
z = c
s = z
CheckPass = 0
While CheckPass < MinStopK
k = k + 1
hx2 = hx1
hy2 = hy1
hx1 = hx
hy1 = hy
hx = x * hx1 - (k - 1) * hx2
hy = y * hy1 - (k - 1) * hy2
c = c * rho / (k + 1)
z = hx * hy * c
s = s + z
If Abs(z / s) < FACCURACY Then
CheckPass = CheckPass + 1
Else
CheckPass = 0
End If
Wend
tetrachoric = s
End Function
Public Function bivnor(x As Double, y As Double, rho As Double) As Double
'
' bivnor function
' Calculates bivariat normal CDF F(x,y,rho) for a pair of standard normal
' random variables with correlation RHO
'
If rho = 0 Then
bivnor = Application.WorksheetFunction.NormSDist(x) * _
Application.WorksheetFunction.NormSDist(y)
Else
bivnor = Application.WorksheetFunction.NormSDist(x) * _
Application.WorksheetFunction.NormSDist(y) + _
Application.WorksheetFunction.NormDist(x, 0, 1, False) * _
Application.WorksheetFunction.NormDist(y, 0, 1, False) * _
tetrachoric(x, y, rho)
End If
End Function
Source: Available for download at http://michael.marginalq.com/
you're hitting on the limits of the computer architecture. Many complex algorithms can't be implemented 1:1 with their mathematical representation because of performance reasons and/or erroneous behavior when overflowing. There's an exceptionally good blog about these issues - John D. Cook.
Please take a look here for a better implementation.
You can also try binding an external library, that gives you arbitrary precision number handling, of course implemented using very expensive (in terms of CPU time) software algorithms. More can be found here.
Updated code using On Error Resume Next instead of On Error Goto:
While CheckPass < MinStopK
k = k + 1
hx2 = hx1
hy2 = hy1
hx1 = hx
hy1 = hy
hx = x * hx1 - (k - 1) * hx2
hy = y * hy1 - (k - 1) * hy2
c = c * rho / (k + 1)
On Error Resume Next
z = hx * hy * c
If Err.Number = 0 Then
s = s + z
If Abs(z / s) < FACCURACY Then
CheckPass = CheckPass + 1
Else
CheckPass = 0
End If
Else
Err.Clear
End If
Wend
http://www.codeproject.com/KB/recipes/float_point.aspx treats how to "Use Logarithms to Avoid Overflow and Underflow", which is a simple but quite effective way of working around overflow problems. In fact, it's so simple yet logical, why haven't we thought of that solution ourselves? ;)