VBA root finding trough bisection - vba

My vba code keeps returning a value of 0 when I know the roots of my function are not 0.
It's pretty simple code but I can't seem to debug it. Any idea where this error might be coming from??
Option Explicit
Public Function Bisect(ByVal xlow As Double, ByVal xhigh As Double) As Double
Dim i As Integer
Dim xmid As Double
xmid = (xlow + xhigh) / 2
For i = 1 To 100
If f(xlow) * f(xmid) < 0 Then
xhigh = xmid
xmid = (xlow + xhigh) / 2
Else
xlow = xmid
xmid = (xlow + xhigh) / 2
End If
Next i
Bisect = xmid
End Function
Function f(ByVal x As Double, Optional ByRef inputArray As Range) As Variant
Dim ca0 As Double
Dim v0 As Double
Dim k As Double
Dim e As Double
Dim ac As Double
Dim L As Double
inputArray(2, 2) = ca0
inputArray(3, 2) = v0
inputArray(4, 2) = k
inputArray(5, 2) = e
inputArray(6, 2) = ac
inputArray(7, 2) = L
f(x) = (v0 / (k * ca0 * ac)) * ((2 * e * (1 + e) * Log(1 - x)) + (e ^ 2 * x) + (((1 + e) ^ 2 * x) / (1 - x))) - L
End Function

' i Think you want to take those constant values from cells presentin the sheet
Function f(ByVal x As Double) As Variant
Dim inputArray As Range
Dim ca0 As Double
Dim v0 As Double
Dim k As Double
Dim e As Double
Dim ac As Double
Dim L As Double
' i Think you want to take values from cells in the sheet
ca0 = ActiveSheet.Cells(2, 2).Value
v0 = ActiveSheet.Cells(3, 2).Value
k = ActiveSheet.Cells(4, 2).Value
e = ActiveSheet.Cells(5, 2).Value
ac = ActiveSheet.Cells(6, 2).Value
L = ActiveSheet.Cells(7, 2).Value

Could it be that you try to assign the inputarray with empty variables?
In my mind it should be:
ca0 = inputArray(2, 2)
v0 = inputArray(3, 2)
And so on.
I'm guessing
f(x) = (v0 / (k * ca0 * ac)) * ((2 * e * (1 + e) * Log(1 - x)) + (e ^ 2 * x) + (((1 + e) ^ 2 * x) / (1 - x))) - L
Should be
f = (v0 / (k * ca0 * ac)) * ((2 * e * (1 + e) * Log(1 - x)) + (e ^ 2 * x) + (((1 + e) ^ 2 * x) / (1 - x))) - L

Related

I need to solve an implicit equation in VBA

I want to give the other parameters that are mentioned in the function, and get a solution for a (the angle), but I get error: "invalid procedure call or argument" Run-time error 5.
I need to call the function in excel worksheet. It is a pretty long equation. Also, it could be that I enter a infinite loop but I don't know how to avoid that.
Function calculateangle(r, h, C, g, d, m, t, x, y As Single) As Single
Dim a As Single
a = 0
While y <> (d + r - r * Cos(a) + (x - (t - r + r * Sin(a))) * Tan(a) - (g
/ (2 * ((((C * m * (2 * g * (h - (d + r - r * Cos(a)))) ^
(1 / 2)) + m * (2 * g * (h - (d + r - r * Cos(a)))) ^ (1 / 2)) / (m +
0.04593)) ^ 2) * (Cos(a)) ^ 2)) * (x - (t - r + r * Sin(a))) ^ 2)
a = a + 0.01
Wend
MsgBox Round(a, 2)
End Function
One obvious issue is that you are using a Function but not returning a value.
This really is a complex piece of spaghetti! However, I suggest an approach like below which will help separate out various bits and thus make it easier to do debugging
Function calculateangle(<...all the bits ...>) As Double
Dim a As Double
Dim tTolerance as Double
dim f1 as Double ' sub sections to help untangle the spaghetti
Dim f2 as Double
Dim f3 as Double
Dim fFinal as Double
Dim tWithinTolerance as Boolean
tWithinTolerance = false
a = 0
tTolerance = 0.01
While not tWithinTolerance
f1 = d + r - r * Cos(a)
f2 = m*2*g*(h - f1)
f3 = x - (t - r + r * Sin(a))
fFinal = (f1 + f3 * Tan(a) - (g / (2 * ((((C * f2) ^
(1 / 2)) + f2 ^ (1 / 2)) / (m + 0.04593)) ^ 2) * (Cos(a)) ^ 2)) * f3 ^ 2)
tWithinTolerance = (Abs(y - fFinal) < tTolerance)
a = a + 0.01
Wend
Calculateangle = a ' note how this sets a return value for the function
End Function
I have left the rounding (which is a presentation issue) to the code that calls this function - this way you can display the answer to whatever level of detail you want!
(apologies if I have mangled any of the calculation on the way through - but you get the concept!)
For the author and those who want to deal with his solitaire. I hope I did not confuse anything in parentheses and simplifications.
Do
vCosA = Cos(a)
vCosADR = d + r * (1 - vCosA) ' d + r - r * vCosA '
vCosMGHADR = m * (2 * g * (h - vCosADR))
vSinAXTR = (x - (t - r * (1 - Sin(a)))) ' - r + r * Sin(a)
'((C * vCosMGHADR) + vCosMGHADR) == ((C + 1) * vCosMGHADR)
If (y = _
(vCosADR + vSinAXTR * Tan(a) - _
(g / _
(2 * _
( _
( _
((C + 1) * vCosMGHADR) / _
(m + 0.04593) _
) ^ 2 _
) * (vCosA ^ 2) _
) _
) * vSinAXTR ^ 2 _
)) Then Exit Do ' *** EXIT DO ***
a = a + 0.01
Loop

Increment decimal place by 0.05 in Word VBA

I'll start off by saying i have jut started teaching myself VBA about a week ago, so I may not be asking the right question, but...
I am attempting to write a loop in Word VBA that will increment a number calculated partially from text retrieved from bookmarks. I want it to round up to the nearest .05, so .87 becomes .90 and .21 becomes .25.
The module that I have written follows:
A = ActiveDocument.Bookmarks("SRebateIncome").Range.Text
B = ActiveDocument.Bookmarks("RebateDefault").Range.Text
C = ((A - 6000) * 0.15)
D = B - C
E = B + D
F = (18200 + ((445 + E) / 0.19)) + 1
G = (0.19 * 18200) + 445 + E + (37000 * (0.015 + 0.325 - 0.19))
H = (G / (0.015 + 0.325)) + 1
I = ActiveDocument.Bookmarks("TRebateIncome").Range.Text
If F < 37000 = True Then
J = (0.125 * (I - F))
Else
J = (0.125 * (I - H))
End If
K = E - J
K = Format(Round(K, 2), "###,##0.00")
'round K up to the nearest .00 or .05
If K <> "###,###.#0" = False or K <> "###,###.#5") = False Then
Do
K = K + 0.01
Loop Until K = "###,###.#0" = True or K <> "###,###.#5") = True
End If
Set RebateOutput = ActiveDocument.Bookmarks("RebateOutput").Range
RebateOutput.Text = K
Now assuming that the value input for bookmarks "SRebateIncome", "RebateDefault" and "TRebateIncome" are 10175, 1602 and 43046 respectively, I expected the output to be 1460.80, but instead "K" is returned as 1460.78.
At this stage I don't know anything about using Excel within word (except copy/paste a spreadsheet into a document and I don't want to do that with this).
Any help would be appreciated
Thanks!
You can do it with an excel object and the Ceiling function
Option Explicit
Sub RoundText()
Dim dblSRebateIncome As Double
Dim dblRebateDefault As Double
Dim dblTRebateIncome As Double
Dim dblFinal As Double
Dim rngOutput As Range
Dim oExcel As Object
' Load the variables
Set oExcel = CreateObject("Excel.Application")
Set rngOutput = ActiveDocument.Bookmarks("RebateOutput").Range
dblSRebateIncome = CDbl(ActiveDocument.Bookmarks("SRebateIncome").Range.Text)
dblRebateDefault = CDbl(ActiveDocument.Bookmarks("RebateDefault").Range.Text)
dblSRebateIncome = CDbl(ActiveDocument.Bookmarks("TRebateIncome").Range.Text)
dblFinal = GetCalculatedValue(dblSRebateIncome, dblRebateDefault, dblTRebateIncome)
dblFinal = oExcel.worksheetfunction.Ceiling(dblFinal, 0.05)
rngOutput.Text = Format$(dblFinal, "###,##0.00")
End Sub
Function GetCalculatedValue(ByVal dblSIncome As Double, _
ByVal dblDefault As Double, _
ByVal dblTIncome) As Double
' Declare all the intermediate variables.
Dim c As Double, d As Double, e As Double
Dim f As Double, g As Double, h As Double
Dim j As Double, ret As Double
' Perform the complicated calculation
c = ((dblSIncome - 6000) * 0.15)
d = dblDefault - c
e = dblDefault + d
f = (18200 + ((445 + e) / 0.19)) + 1
g = (0.19 * 18200) + 445 + e + (37000 * (0.015 + 0.325 - 0.19))
h = (g / (0.015 + 0.325)) + 1
If f < 37000 Then
j = (0.125 * (dblTIncome - f))
Else
j = (0.125 * (dblTIncome - h))
End If
ret = e - j
' Return the value of the fucntion
GetCalculatedValue = ret
End Function
Hope this helps. :)
Dim x As Double
x = 1.111 'E.g.
Debug.Print Round(x * 20, 0)/20 '>> 1.10

Binomial European Options Pricing Model

I created a program, using VBA, to calculate the European Call option price, as follows:
Private Sub CallPrice_Click()
Dim K As Single
Dim So As Single
Dim r As Single
Dim T As Single
Dim sigma As Single
Dim u As Single
Dim d As Single
Dim p As Single
Dim CP As Single
Dim M As Single
Dim S As Single
Dim CB As Double
Dim n As Integer
Dim i As Integer
K = Cells(2, 2)
So = Cells(3, 2)
r = Cells(4, 2)
T = Cells(5, 2)
sigma = Cells(6, 2)
n = Cells(7, 2)
u = Exp(sigma * Sqr(T / n))
d = 1 / u
p = (Exp(r * T / n) - d) / (u - d)
CP = 0
For i = 0 To n Step 1
M = WorksheetFunction.Max(So * (u ^ i) * d ^ (n - i) - K, 0)
CB = WorksheetFunction.Combin(n, i)
S = M * CB * (p ^ n) * (1 - p) ^ (n - i)
CP = CP + S
Next i
Cells(9, 2) = CP / (1 + r) ^ n
End Sub
Here is the layout of the spreadsheet:
When I ran the program, the error occurred.
Could someone here explain what is wrong in my program and how to fix it?
Is your equation for S correct? It seems like it should be:
S = M * CB * (p ^ i) ...
instead of
S = M * CB * (p ^ n) ...
If your equation is indeed wrong, then you can use BINOMDIST instead of COMBIN, because by definition:
Binom_Dist(i, n, p, False) = (p ^ i) * (1 - p) ^ (n - i) * Combin(n, i)
So your code would be:
S = M * WorksheetFunction.Binom_Dist(i, n, p, False)
instead of
CB = WorksheetFunction.Combin(n, i)
S = M * CB * (p ^ n) * (1 - p) ^ (n - i)
BINOMDIST is not as sensitive to large n, i.
You are getting an overflow error. If you check on a work sheet:
COMBIN(5000, 161) = 3.3E+307
COMBIN(5000, 162) = #NUM!
COMBIN(5000, 4838) = #NUM!
COMBIN(5000, 4839) = 3.3E+307
Remember that the number of combinations increases exponentially up until the halfway point in which it will start to go down at an inverse rate.

Run-time error 5?

I am trying to define a derivative function for solving a system of differential equations, however when I run the actual macro that calls up this sub routine, i keep getting Run-time error 5: Invalid procedure call or argument. This error occurs in the If statement when x is not greater than 1 and Qv is to be calculated using the equation provided. While stepping into to debug, there are values for all of the variables, but it gives me the error and I have no idea why. Can someone help please?
Sub Derivs(x As Double, y() As Double, dydx() As Double)
Const g As Double = 32.1740485564
Const Hr As Double = 100
Const h0 As Double = 80
Const fm As Double = 0.024
Const L As Double = 1500
Const dp As Double = 2
Const tc As Double = 5
Const k As Double = 25.7
Const Di As Double = 5
Dim u0 As Double
Dim Qv As Double
Dim Qv0 As Double
Dim hstar As Double
u0 = ((g * h0 / ((1 / 2) * fm * (L / dp))) * ((Hr / h0) - 1)) ^ (1 / 2)
Qv0 = (u0 * 3.14 * Di ^ 2) / 4
hstar = h0 - (Qv0 / k) ^ 2
If x >= 1 Then
Qv = 0
Else
Qv = k * (h0 ^ 0.5) * (1 - x) * (y(1) - hstar / h0) ^ 0.5
End If
dydx(0) = ((tc * g * h0) / (L * u0)) * (((Hr / h0) - y(1)) - ((Hr / h0) - 1) * y(0) * Abs(y(0)))
dydx(1) = ((dp / Di) ^ 2) * (u0 * tc / h0) * y(0) - ((4 * Qv * tc) / (3.14 * h0 * Di ^ 2))
End Sub
Well the macro that calls this sub routine is:
Sub RungeKutta()
Dim y(1) As Double
Dim dydx(1) As Double
Dim yout(1) As Double
Dim yerr(1) As Double
Dim x As Double
Dim hdid As Double
Dim yscal(1) As Double
Dim hnext As Double
Dim ystart(1) As Double
Dim NOk As Integer
Dim NBad As Integer
Dim h As Double
Const n As Integer = 2
Dim htry As Double
Const eps As Double = 0.00000001
Dim x1 As Double
Dim x2 As Double
Const nvar As Integer = 2
Dim h1 As Double
Const hmin As Double = 0.001
h = 0.001
x1 = 0
x2 = 10
h1 = 0.01
x = x1
h = Sgn(x2 - x1) * Abs(h1)
NOk = 0
NBad = 0
kount = -1
x = 0
y(0) = 1#
y(1) = 1#
Call Derivs(x, y(), dydx())
Call odeint(ystart(), nvar, x1, x2, eps, h1, hmin, NOk, NBad)
' I have a bunch of coding to input the calculations into a spreadsheet that I am omitting
End Sub
The main program in the macro is:
Sub odeint(ystart() As Double, nvar As Integer, x1 As Double, x2 As Double, eps As Double, h1 As Double, hmin As Double, NOk As Integer, NBad As Integer)
Const MaxStp As Double = 10000
Const Tiny As Double = 10 ^ (-30)
Dim y() As Double
Dim yscal() As Double
Dim dydx() As Double
Dim x As Double
Dim h As Double
Dim hdid As Double
Dim hnext As Double
Const n As Integer = 2
NM1 = n - 1
nvar = 2
ReDim y(NM1)
ReDim dydx(NM1)
ReDim yscal(NM1)
x = x1
h = Sgn(x2 - x1) * Abs(h1)
NOk = 0
NBad = 0
kount = -1
kmax = 500
ReDim xp(kmax)
ReDim yp(NM1, kmax)
dxsav = (x2 - x1) / kmax
For I = 0 To nvar - 1
y(I) = ystart(I)
Next I
If kmax > 0 Then xsav = x - 2 * dxsav
For nstp = 1 To MaxStp
Call Derivs(x, y(), dydx())
For I = 0 To nvar - 1
yscal(I) = Abs(y(I)) + Abs(h * dydx(I)) + Tiny
Next I
If kmax > 0 Then
If Abs(x - xsav) > Abs(dxsav) Then
If kount < kmax - 1 Then
kount = kount + 1
xp(kount) = x
For I = 0 To nvar - 1
yp(I, kount) = y(I)
Next I
xsav = x
End If
End If
End If
If (x + h - x2) * (x + h - x1) > 0 Then h = x2 - x
Call rkqs(y(), dydx(), nvar, x, h, eps, yscal(), hdid, hnext)
If hdid = h Then
NOk = NOk + 1
Else
NBad = NBad + 1
End If
If (x - x2) * (x2 - x1) >= 0 Then
For I = 0 To nvar - 1
ystart(I) = y(I)
Next I
If Not kmax = 0 Then
kount = kount + 1
xp(kount) = x
For I = 0 To nvar - 1
yp(I, kount) = y(I)
Next I
End If
Exit Sub
End If
If Abs(hnext) < hmin Then MsgBox "Stepsize smaller than minimum in odeint!", vbExclamation
h = hnext
Next nstp
MsgBox "Too many steps in odeint", vbExclamation
End Sub
Which calls this sub routine:
Sub rkqs(y() As Double, dydx() As Double, n As Integer, x As Double, htry As Double, eps As Double, yscal() As Double, hdid As Double, hnext As Double)
NM1 = n - 1
Dim ytemp() As Double
Dim yerr() As Double
Dim h As Double
Const Tiny As Double = 10 ^ (-30)
ReDim ytemp(NM1)
ReDim yerr(NM1)
Const Safety As Double = 0.9
Const PGrow As Double = -0.2
Const PShrink As Double = -0.25
Const ErrCon As Double = (5# / Safety) ^ (1# / PGrow)
h = htry
Do
Call rkck(y(), dydx(), n, x, h, ytemp(), yerr())
ErrMax = 0
For I = 0 To NM1
yscal(I) = Abs(y(I)) + Abs(h * dydx(I)) + Tiny
Next I
For I = 0 To n - 1
If Abs(yerr(I) / yscal(I)) > ErrMax Then ErrMax = Abs(yerr(I) / yscal(I))
Next I
ErrMax = ErrMax / eps
If ErrMax > 1 Then
dummy = h
h = Safety * h * (ErrMax ^ PShrink)
If h < 0.1 * dummy Then
h = 0.1 * dummy
End If
xNew = x + h
If xNew = x Then MsgBox "Stepsize underflow in rkqsl", vbExclamation
ContLoop = True
Else
If ErrMax > ErrCon Then
hnext = Safety * h * (ErrMax ^ PGrow)
Else
hnext = 5 * h
End If
hdid = h
x = x + h
For I = 0 To n - 1
y(I) = ytemp(I)
Next I
ContLoop = False
End If
Loop While ContLoop
End Sub
which then calls this subroutine:
Sub rkck(y() As Double, dydx() As Double, n As Integer, x As Double, h As Double, yout() As Double, yerr() As Double)
Dim NM1 As Integer
Dim I As Integer
Dim ak2() As Double
Dim ak3() As Double
Dim ak4() As Double
Dim ak5() As Double
Dim ak6() As Double
Dim ytemp() As Double
NM1 = n - 1
ReDim ak2(NM1)
ReDim ak3(NM1)
ReDim ak4(NM1)
ReDim ak5(NM1)
ReDim ak6(NM1)
ReDim ytemp(NM1)
Const A2 As Double = 1# / 5#
Const A3 As Double = 3# / 10#
Const A4 As Double = 3# / 5#
Const A5 As Double = 1#
Const A6 As Double = 7# / 8#
Const B21 As Double = 1# / 5#
Const B31 As Double = 3# / 40#
Const B32 As Double = 9# / 40#
Const B41 As Double = 3# / 10#
Const B42 As Double = -9# / 10#
Const B43 As Double = 6# / 5#
Const B51 As Double = -11# / 54#
Const B52 As Double = 5# / 2#
Const B53 As Double = -70# / 27#
Const B54 As Double = 35# / 27#
Const B61 As Double = 1631# / 55296#
Const B62 As Double = 175# / 512#
Const B63 As Double = 575# / 13824#
Const B64 As Double = 44275# / 110592#
Const B65 As Double = 253# / 4096#
Const C1 As Double = 37# / 378#
Const C3 As Double = 250# / 621#
Const C4 As Double = 125# / 594#
Const C6 As Double = 512# / 1771#
Const DC1 As Double = C1 - 2825# / 27648#
Const DC3 As Double = C3 - 18575# / 48384#
Const DC4 As Double = C4 - 13525# / 55296#
Const DC5 As Double = -277# / 14336#
Const DC6 As Double = C6 - 1# / 4#
'First Step
For I = 0 To n - 1
ytemp(I) = y(I) + B21 * h * dydx(I)
Next I
'Second Step
Call Derivs(x + A2 * h, ytemp(), ak2())
For I = 0 To n - 1
ytemp(I) = y(I) + h * (B31 * dydx(I) + B32 * ak2(I))
Next I
'Third Step
Call Derivs(x + A3 * h, ytemp(), ak3())
For I = 0 To n - 1
ytemp(I) = y(I) + h * (B41 * dydx(I) + B42 * ak2(I) + B43 * ak3(I))
Next I
'Fourth Step
Call Derivs(x + A4 * h, ytemp(), ak4())
For I = 0 To n - 1
ytemp(I) = y(I) + h * (B51 * dydx(I) + B52 * ak2(I) + B53 * ak3(I) + B54 * ak4(I))
Next I
'Fifth Step
Call Derivs(x + A5 * h, ytemp(), ak5())
For I = 0 To n - 1
ytemp(I) = y(I) + h * (B61 * dydx(I) + B62 * ak2(I) + B63 * ak3(I) + B64 * ak4(I) + B65 * ak5(I))
Next I
'Sixth Step
Call Derivs(x + A6 * h, ytemp(), ak6())
For I = 0 To n - 1
yout(I) = y(I) + h * (C1 * dydx(I) + C3 * k3(I) + C4 * ak4(I) + C6 * ak6(I))
Next I
For I = 0 To n - 1
yerr(I) = h * (DC1 * dydx(I) + DC3 * ak3(I) + DC4 * ak4(I) + DC5 * ak5(I) + DC6 * ak6(I))
Next I
End Sub
It's the Runge Kutta method.
So I debugged each of the three programs separately starting with RKCK, then going into RKQS and then to ODEINT by essentially writing test macros for each that included all the parameters, outputted the calculated values accosted with each program in a message box, and called the following example set of equations:
Sub Derivs1(x As Double, y() As Double, dydx() As Double)
dydx(0) = -2 * x * y(0)
dydx(1) = -3 * y(1) * x ^ 2
End Sub
Each program worked perfectly for this example so I decided to test each test macro with the actual problem statement equations. RKCK worked fine, so did RKQS. Then when I got to the ODEINT, the error message popped up.
Run time error 5 is an "Invalid Procedure call" error.
I can't see how that line could produce an error as long the y array has a value at index 1.
You need to give an example of calling this function similar to the following which runs without any error.
Sub test()
Dim dydx(0 To 1) As Double
Dim y(0 To 1) As Double
dydx(0) = 1
dydx(1) = 2
y(0) = 1
y(1) = 2
Derivs 0.5, y, dydx
End Sub
I've run your edited code and when the error occurs in
Qv = k * (h0 ^ 0.5) * (1 - x) * (y(1) - hstar / h0) ^ 0.5
your variable values are:
y(1) = 0
hstar = 38.3
h0 = 80
This means:
(y(1) - hstar / h0) = -0.478857734838603
As Jean-François Corbett mentioned, the square root of a -ve number isn't supported by vba and results in the run-time error 5.
You're probably taking the square root of a negative.
x ^ 0.5 will give you an "Invalid procedure call or argument" error when x is negative.
Try stepping through your code in debug mode to confirm this.

Why does the getArea((l / 3), (n - 1) return only null value?

Why does the function getArea return NULL/Nothing
Module Module3
Dim noOfTriangles As Integer = (3 / 2)
Sub main()
Dim l As Single
Dim n As Integer
l = Console.ReadLine()
n = Console.ReadLine()
Console.WriteLine(getArea(l, n))
Console.ReadKey()
End Sub
Function getArea(ByVal l As Single, ByVal n As Integer)
Dim area As Single = 1
If n = 0 Then
Return Nothing
Else
noOfTriangles = noOfTriangles * 2
Return (((3 ^ (1 / 2)) / 4) * (l ^ 2) + (noOfTriangles * getArea((l / 3), (n - 1))))
End If
End Function
End Module
Try this and see if it gets you any closer to what you're looking for:
Module Module3
Dim noOfTriangles As double = (3 / 2)
Sub main()
Dim l As Single
Dim n As Integer
l = Single.Parse(Console.ReadLine())
n = Integer.Parse(Console.ReadLine())
Console.WriteLine(getArea(l, n))
Console.ReadKey()
End Sub
Function getArea(ByVal l As Single, ByVal n As Integer) as Double
Dim area As Single = 1
If n = 0 Then
Return Nothing
Else
noOfTriangles = noOfTriangles * 2
Return (((3 ^ (1 / 2)) / 4) * (l ^ 2) + (noOfTriangles * getArea((l / 3), (n - 1))))
End If
End Function
End Module