Excel VBA error 438 : object doesn't support this property or method - vba

Please help , this is my first try to code something useful by VBA and I am self-learning now. And I got that above error . Please help
Sub Bezier()
Dim C As Double, k As Integer, ansx As Double, ansy As Double, t As Double, n As Integer
n = 3
For i = 0 To 100
t = i * 0.01
ansx = 0
ansy = 0
For k = 0 To n
C = (WorksheetFunction.Fact(n) / WorksheetFunction.Fact(k)) / WorksheetFunction.Fact(n - k)
ansx = ansx + Cells(k + 2, 1).Value * C * WorksheetFunction.Power(t, k) * WorksheetFunction.Power(1 - t, n - k)
ansy = ansy + Cells(k + 2, 2).Value * C * WorksheetFunction.Power(t, k) * WorksheetFunction.Power(1 - t, n - k)
Next
Cells(i + 2, 6).Value = ansx
Cells(i + 2, 7).Value = ansy
Next
End Sub

First of all, you should know, that some of functions, used on the worksheet, have limitations. So my point is avoid of using them in VBA, if it is not necessary.
For example, function POWER() returns error on attempt to raise a zero to zero. An alternative is to use 0 ^ 0 combination, which is exactly doing the same, but looks more simply and operates without such error. But also there is no embedded alternative in VBA to the FACT() function, so you can use it, or simply add your own function factor() - it's uppon your choise.
If you just have started learning VBA, I would recomend you to use Option Explicit. It will help you to find out, which variables are not defined, and sometimes to avoid errors related to variable names missprint.
Here is your code, fixed and a little bit optimized:
Option Explicit' It is an option that turns on check for every used variable to be defined before execution. If this option is not defined, your code below will find undefined variables and define them when they are used. Good practice is to use this option, because it helps you, for example to prevent missprinting errors in variable names.
Sub Bezier()
Dim C as Double , t As Double
Dim k As Long, n As Long, i As Long
n = 3
For i = 0 To 100
t = i * 0.01
Cells(i + 2, 6) = 0
Cells(i + 2, 7) = 0
For k = 0 To n
C = (WorksheetFunction.Fact(n) / WorksheetFunction.Fact(k)) / WorksheetFunction.Fact(n - k)
Cells(i + 2, 6) = Cells(i + 2, 6).Value + Cells(k + 2, 1).Value * C * (t ^ k) * ((1 - t) ^ (n - k))
Cells(i + 2, 7) = Cells(i + 2, 7).Value + Cells(k + 2, 2).Value * C * (t ^ k) * ((1 - t) ^ (n - k))
Next
Next
End Sub
UPDATE
Here are some examples of factorial calculations.
Public Function fnFact(number) ' a simple cycle example of Factorial function
Dim tmp As Long ' new temporary variable to keep the "number" variable unchanged
tmp = number
fnFact = number
While tmp > 1
tmp = tmp - 1
fnFact = fnFact * tmp
Wend
End Function
Public Function fnFactR(number) ' a simple example of recursive function for Factorial calculation
If number > 0 Then
fnFactR = fnFactR(number - 1) * number ' function calls itself to continue calculations
Else
fnFactR = 1 ' function returns {1} when calculations are over
End If
End Function
Sub Factor_test() 'RUN ME TO TEST ALL THE FACTORIAL FUNCTIONS
Dim number As Long
number = 170 ' change me to find Factorial for a different value
MsgBox "Cycle Factorial:" & vbNewLine & number & "!= " & fnFact(number)
MsgBox "WorksheetFunction Factorial:" & vbNewLine & number & "!= " & WorksheetFunction.Fact(number)
MsgBox "Recursive Factorial:" & vbNewLine & number & "!= " & fnFactR(number)
End Sub
All those functions are available to calculate Factorial only for numbers before 170 inclusively, because of large result value.
So for my PC the limitation for WorksheetFunction.Fact() function is also 170.
Let me know, if your PC has different limitation for this function, - it's quite interesting thing. :)
UPDATE2
It is recomended to use Long data type instead of Integer each type when integer (or whole number) variable is needed. Long is slightly faster, it has much wider limitations and costs no additional memory. Here are proof links:
1. MSDN:The Integer, Long, and Byte Data Types
2. ozgrid.com:Long Vs Integer
3. pcreview.co.uk:VBA code optimization - why using long instead of integer?
Thanks for #Ioannis and #chris neilsen for the information about Long data type and proof links!
Good luck in your further VBA actions!

I can't find a pow method on the WorksheetFunction object. There is a Power method though. Perhaps you meant that?

Related

VBA Function where double loop only provides one output

I am new with VBA and trying to do a trinomial option pricing tree with parameters from -n to n defined. How can I rewrite my VBA function with a double loop to provide all output as it seems to only show the value of S. In the end I want to sum all values and discount them but I cannot get past my double loop providing only one output. If I put the MsgBox between k and i loop it returns nothing. Please can someone assist getting this to work as I don't have the hang of VBA yet.
Public Function LoopTest(S As Double, u As Double, n As Integer)
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim St() As Double
ReDim St(2 * n + 1)
For k = 0 To n Step 1
For i = -k To k Step 1
St(i, k) = S * u ^ i
Debug.Print St(i, k)
MsgBox ("DebugToPrint")
' Payoff(i,k) = Application.WorksheetFunction.Max((St(i,k) - 20), 0)
Next i
Next k
' For i from n to 2 step -1
' For j from 1 to n - 2
' Payoff(i - 1, j) = Payoff(i, j) + Payoff(i, j + 1) + Payoff(i, j + 2)
' Next i
' Next k
LoopTest = St()
End Function
How do I get output
k=0 St = S*u^0
k=1 St = S*u^-1 , St = S*u^0 , St = S*u^1
k=2 St = S*u^-2 , St = S*u^-1 , St = S*u^0 , St = S*u^1 , St = S*u^2
etc and then sum everything afterwards as output?
I'm not totally following the example but from the description it sounds like you're interested in a recursive process. It can be tricky and will leave you in an endless loop if you're not careful. Does this previous post help you? VBA recursive "For loops" Permutation?

Label a set of objects with (A->Z,AA->ZZ, AAA->ZZZ) in VBA

I have a set which has an unknown number of objects. I want to associate a label to each one of these objects. Instead of labeling each object with a number I want to label them with letters.
For example the first object would be labeled A the second B and so on.
When I get to Z, the next object would be labeled AA
AZ? then BA, BB, BC.
ZZ? then AAA, AAB, AAC and so on.
I'm working using Mapbasic (similar to VBA), but I can't seem to wrap my head around a dynamic solution. My solution assumes that there will be a max number of objects that the set may or may not exceed.
label = pos1 & pos2
Once pos2 reaches ASCII "Z" then pos1 will be "A" and pos2 will be "A". However, if there is another object after "ZZ" this will fail.
How do I overcome this static solution?
Basically what I needed was a Base 26 Counter. The function takes a parameter like "A" or "AAA" and determines the next letter in the sequence.
Function IncrementAlpha(ByVal alpha As String) As String
Dim N As Integer
Dim num As Integer
Dim str As String
Do While Len(alpha)
num = num * 26 + (Asc(alpha) - Asc("A") + 1)
alpha = Mid$(alpha, 2,1)
Loop
N = num + 1
Do While N > 0
str = Chr$(Asc("A") + (N - 1) Mod 26) & str
N = (N - 1) \ 26
Loop
IncrementAlpha = str
End Function
If we need to convert numbers to a "letter format" where:
1 = A
26 = Z
27 = AA
702 = ZZ
703 = AAA etc
...and it needs to be in Excel VBA, then we're in luck. Excel's columns are "numbered" the same way!
Function numToLetters(num As Integer) As String
numToLetters = Split(Cells(1, num).Address(, 0), "$")(0)
End Function
Pass this function a number between 1 and 16384 and it will return a string between A and XFD.
Edit:
I guess I misread; you're not using Excel. If you're using VBA you should still be able to do this will the help of an reference to an Excel Object Library.
This should get you going in terms of the logic. Haven't tested it completely, but you should be able to work from here.
Public Function GenerateLabel(ByVal Number As Long) As String
Const TOKENS As String = "ZABCDEFGHIJKLMNOPQRSTUVWXY"
Dim i As Long
Dim j As Long
Dim Prev As String
j = 1
Prev = ""
Do While Number > 0
i = (Number Mod 26) + 1
GenerateLabel = Prev & Mid(TOKENS, i, 1)
Number = Number - 26
If j > 0 Then Prev = Mid(TOKENS, j + 1, 1)
j = j + Abs(Number Mod 26 = 0)
Loop
End Function

How come VBA excel keeps running the statement inside a conditional statement even if it is false?

I am trying to create an automatic filling of the payroll spreadsheet I created. However, no matter how much I try it the value of z = 1 all the time even if the logic returns FALSE values (I validated this using MsgBox).
My goal in this code is to check whether there is already a record in another sheet. If there isn't it will automatically add the record with the appropriate details based on the available data.
Below is the full VBA code (Note code is incomplete so it is a bit unpolished still):
Option Explicit
Public p As Long
Sub test()
Dim Total_rows_PR As Long
Dim Total_rows_DTR As Long
Total_rows_PR = Worksheets("Payroll - Regular").Range("B" & Rows.Count).End(xlUp).Row
Total_rows_DTR = Worksheets("DTR").Range("B" & Rows.Count).End(xlUp).Row
Dim q As Long
Dim j As Long
Dim z As Long
For j = 1 To Total_rows_DTR - 1
For q = 1 To Total_rows_PR + p - 2
If Worksheets("DTR").Cells(1 + j, 33) = Worksheets("Payroll - Regular").Cells(2 + q, 1) Then
If Worksheets("DTR").Cells(1 + j, 34) = Worksheets("Payroll - Regular").Cells(2 + q, 2) Then
If Worksheets("DTR").Cells(1 + j, 2) = Worksheets("Payroll - Regular").Cells(2 + q, 3) Then
z = 1
Exit For
End If
End If
End If
Next q
' Below is where the assignment should happen but only returns a blank cell
If z = 0 Then Worksheets("Payroll - Regular").Cells(Total_rows_PR + 1 + p, 1) = Worksheets("DTR").Cells(1 + j, 33)
If z = 0 Then Worksheets("Payroll - Regular").Cells(Total_rows_PR + 1 + p, 2) = Worksheets("DTR").Cells(1 + j, 34)
If z = 0 Then Worksheets("Payroll - Regular").Cells(Total_rows_PR + 1 + p, 3) = Worksheets("DTR").Cells(1 + j, 2)
If z = 0 Then p = p + 1
z = 0
Next j
End Sub
Update: I realized that even if the conditions are not being a met in the first portion of If-Then loops, the value of z is set to 1 for no reason. This is the reason why it won't assign values. However, I do not see why it keeps assigning to 1.
Update#2: #ShaiRado
So the first image is where data is encoded (not shown in image because it is in the leftmost part of the spreadsheet, but basically it inputs the name of the person, date, and the daily time record (DTR) of the person). When the data is encoded, it will automatically indicate what month and year it is based on the helper column AG month and column AH for year. Somewhere in the start of the same worksheet at column B is where the name of the person is. All of these 3 will be used.
This second image is where the summaries are computed. If there is an entry for a specific person at a certain month and year and it is not located in this worksheet, it will automatically fill in that person's name as well as the month and year. Basically that's what the code i'm trying to create does.
The output is a fully automated spreadsheet that only requires data entry in the DTR sheet. All computations already have their corresponding formulas.
First: You have a really strange way of writing your if-statements.
I think what you mean is
For q = 1 To Total_rows_PR + p
If Worksheets("DTR").Cells(1 + j, 33) = Worksheets("Payroll - Regular").Cells(2 + q, 1) _
And Worksheets("DTR").Cells(1 + j, 34) = Worksheets("Payroll - Regular").Cells(2 + q, 2) _
And Worksheets("DTR").Cells(1 + j, 2) = Worksheets("Payroll - Regular").Cells(2 + q, 3) Then
z = 1
Exit For ' Once found, z stays 1 so you don't have to continue the inner loop.
End If
Next q
Second: I am not sure what exactly you want to achieve, but as far as I understand, your problem is that you are looping to far. At the last iteration of the outer loop, your accessing row 1 + j of sheet DTR which is empty at that time, and you are accessing row 2 + q (which is the same as 2 + Total_rows_PR + p) - also empty (and comparing the two emtpy lines sets z to 1).
A variable is never set for no reason. Is is maybe set and you don't understand the reason.
Debug your code step by step, watch where it behaves different as you expect and find the reason why it does what is does.

run time error 5 in VBA excel when working with array

I use vba on excel 2007, OS: windows vista, to make calculation using kinematic wave equation in finite difference scheme. But, when it runs the run-time 5 (invalid procedure call or arguments) message appears. I really don't what is going wrong. Anyone can help?
Sub kwave()
Dim u(500, 500), yy(500, 500), alpha, dt, dx, m, n, so, r, f, X, L, K As Single
Dim i, j As Integer
dx = 0.1
dt = 0.01
L = 10
m = 5 / 3
r = 1
f = 0.5
n = 0.025
so = 0.1 'this is slope
alpha = 1 / n * so ^ 0.5
X = 0
For i = 0 To 100
Cells(i + 1, 1) = X
u(i, 1) = L - so * X
X = X + dx
Cells(i + 1, 2) = u(i, 1)
Next i
For j = 0 To 100
For i = 1 To 100
'predictor step
u(i, j + 1) = u(i, j) - alpha * dt / dx * (u(i + 1, j) ^ m - u(i, j) ^ m) + (r - f) * dt
'corrector step
K = u(i, j + 1) ^ m - u(i - 1, j + 1) ^ m '<<<<----- RUNTIME ERROR 5 HAPPENS AT THIS LINE
yy(i, j + 1) = 0.5 * ((yy(i, j) + u(i, j + 1)) - alpha * dt / dx * K + (r - f) * dt)
Next i
Next j
End Sub
You are declaring the variables wrong- the array should store a double/single but it is defaulting to a variant. See this article.
http://www.cpearson.com/excel/declaringvariables.aspx -
"Pay Attention To Variables Declared With One Dim Statement
VBA allows declaring more than one variable with a single Dim
statement. I don't like this for stylistic reasons, but others do
prefer it. However, it is important to remember how variables will be
typed. Consider the following code:
Dim J, K, L As Long You may think that all three variables are
declared as Long types. This is not the case. Only L is typed as a
Long. The variables J and K are typed as Variant. This declaration is
functionally equivalent to the following:
Dim J As Variant, K As Variant, L As Long You should use the As Type
modifier for each variable declared with the Dim statement:
Dim J As Long, K As Long, L As Long "
Additionally, when i = 99 and j = 10, u(99,11), which is j+1, produces a negative number. Note that this does not fully cause the problem though, because you can raise negative numbers to exponents. Ex, -5^3 = -125

Sorting Arrays or collections

I have the following code as a sub in Excel 2010:
i = 2
For j = 1 To num_scenarios
Dim probdiff As Double
Dim OCS_Spend As Double
n = 0
For k = 1 To num_yrs
' These are the calculations and potentially not relevant to my question but here for context
For Each cell In rng
x = Rnd()
'Debug.Print Format(x, "0.00000%")
If cell.Value >= x Then
'Populate the result sheet
Sheets("Event Occurs").Cells(i, 1) = mywksht.Cells(cell.Row, 1)
Sheets("Event Occurs").Cells(i, 2) = mywksht.Cells(cell.Row, 2)
Sheets("Event Occurs").Cells(i, 3) = mywksht.Cells(cell.Row, 3)
Sheets("Event Occurs").Cells(i, 4) = mywksht.Cells(cell.Row, 4)
Sheets("Event Occurs").Cells(i, 5) = mywksht.Cells(cell.Row, 5)
Sheets("Event Occurs").Cells(i, 6) = mywksht.Cells(cell.Row, 6)
Sheets("Event Occurs").Cells(i, 10) = "Event Occurs"
Sheets("Event Occurs").Cells(i, 11) = mywksht.Cells(cell.Row, 11)
Sheets("Event Occurs").Cells(i, 9) = x
Sheets("Event Occurs").Cells(i, 7) = k
Sheets("Event Occurs").Cells(i, 8) = j
Sheets("Event Occurs").Cells(i, 14) = (cell.Value - x) ^ (2)
event_max = Sheets("Event Occurs").Cells(i, 11)
probdiff = probdiff + (cell.Value - x) ^ (2)
If Round(cell / x, 0) >= event_max Then
Sheets("Event Occurs").Cells(i, 12) = event_max
Else
Sheets("Event Occurs").Cells(i, 12) = Round(cell / x, 0)
End If
Duration = Sheets("Event Occurs").Cells(i, 4)
Num_Event = Sheets("Event Occurs").Cells(i, 12)
Spend = Sheets("Event Occurs").Cells(i, 5)
Sheets("Event Occurs").Cells(i, 13) = Num_Event * Spend / Duration
OCS_Spend = OCS_Spend + Num_Event * Spend / Duration
n = n + 1
i = i + 1
End If
Next cell
' End calculations
Next k
Debug.Print j, probdiff / n
probdiff = 0
OCS_Spend = 0
Next j
The output to the immediate window looks like this:
J: MSE:
1 0.194236476623154
2 0.157939130921924
3 0.19825548826238
4 0.384990330451172
5 0.267128221022187
The first column is j (the outer for loop) and represents a scenario. The second column is the mean square error of the data generated by each iteration of the outer j loop. So 1 is the first time the loop runs,2 is the second etc.. The smaller the number in column MSE, the more likely the scenario is to occur.
I want people to be able to limit the number of scenarios (j's) they see to only the most likely in the event they want to run 100 scenarios. So I need a way of sorting the table above to something like this
j: MSE
2 0.157939130921924
1 0.194236476623154
3 0.19825548826238
5 0.267128221022187
4 0.384990330451172
And if someone wanted to see only the top three results, it would be this:
j: MSE
2 0.157939130921924
1 0.194236476623154
3 0.19825548826238
So basically the three most likely out of 5 possible scenarios. I have tried collections and arrays but not dicitonaries (I am still learning how to use these and not sure if they exists in Excel VBA).
Chip Pearson provides a number of very useful functions which can sort arrays, collections, and dictionaries, which are available here:
http://www.cpearson.com/Excel/SortingArrays.aspx
There is too much code there to reproduce here. What I typically do when the need arises is to create a separate module in my VBProject which contains these array helper functions. I have used these extensively in PowerPoint and they worked in that environment with minimal modifications. For Excel, they should work out-of-the-box.
Once you have put the data in an array (I don't see any arrays in your code, so let's assume you have something like Dim MyArray As Variant), and sorted it using those functions, you can do something like this to cut the array down to include only the first x results:
'where "x" is a long/integer represents some user-input or _
limit to the number of results:
ReDim Preserve MyArray(x - 1)
I would use arrays rather than collections or dictionaries.
Why not Collections? Collections are useful and would arguably do the job, here. However, whereas we can "resize" the array in a single ReDim Preserve statement, you cannot do that with a Collection object; you would instead have to use iteration. While this is not overly complicated, it does seem a bit clunkier. (You could of course do some tests on performance, but unless you are dealing with very large sets of data, I would not expect a noticeable gain either way).
Sub testCollection()
Dim coll As New Collection
Dim i As Integer
For i = 1 To 10
coll.Add i
Next
Dim x As Integer 'The maximum number of results you want to return:
x = 4
Do Until coll.Count = x
coll.Remove (coll.Count)
Loop
End Sub
Why not dictionaries? While a dictionary's .Keys returns a one-dimensional array of values, in order to avoid iteration (like in the collection object) you would still need to transfer these to an array:
MyArray = dict.Keys()
ReDim Preserve MyArray(x-1)
Further, the dictionary object holds unique key values, so these are not good to use if you anticipate that there may be duplicate values that you need to store.
One option is to use a System.Collections.ArrayList since this object directly supports a Sort method. The Object is "borrowed" from VB.NET.
EDIT#1
Here is a sample:
Sub SortDemo()
s = Array("Larry", "Moe", "Curley", "Manny", "Zack", "Jack")
L = LBound(s)
U = UBound(s)
With CreateObject("System.Collections.ArrayList")
For k = L To U
.Add s(k)
Next k
.Sort
s = .toarray
End With
msg = ""
For k = L To U
msg = msg & s(k) & vbCrLf
Next k
MsgBox msg
End Sub
and here are the references in place:
For more information see:
Ozgrid Material