Having trouble getting if statements to work in VBA - excel

My first question is whether excel VBA will recognize an if statement with two constraints, i.e.
IF Range(somecell).value > 0 AND Range(anothercell).value < 100 Then:
execute code here
Because I am having a problem with getting the code enclosed in an if statement to trigger when I know that both constraints are satisfied in a script I'm running. Maybe its a problem with my logic.
I've included the code, please see if you can point out any errors in my logic or VBA.
Background Information (I also included some in the code):
There are two levers that change cell F71(D40 and D41). The requirements are that F71 be greater than 0 and it must be less than the current value for F71 (Saved in variable currentValueAdd).
So I loop through both layers iterating through all the possible combinations trying to find the optimal combination that satisfies the above conditions. Sometimes I open excel and it works fine, other times it doesn't work at all. The results are very erratic.
Private Sub OptimizeFI_Click()
Dim waiveLoop As Integer
Dim comissionLoop As Integer
Dim finalWaive As Integer
Dim finalCommission As Integer
Dim currentValueAdd As Double
Dim F71 As Range, D41 As Range
currentValueAdd = Range("$F$71").Value ' <-- This is the cell I am trying to optimize.
For waiveLoop = 0 To 7
Range("$D$40").Value = waiveLoop ' <-- one of the levers in changing cell F71
For comissionLoop = 0 To 7
Range("$D$41").Value = comissionLoop ' <-- a second lever in changing cell F71
If Range("$F$71").Value > 0 And Range("$F$71").Value < currentValueAdd Then
finalWaive = Range("$D$40").Value
finalComission = Range("$D$41").Value
Range("$E$27").Value = finalWaive * 0.05
Range("$E$28").Value = finalComission * 0.05
currentValueAdd = Range("$F$71").Value
End If
Next comissionLoop
Next waiveLoop
Range("$D$40").Value = Range("$E$27") / 0.05
Range("$D$41").Value = Range("$E$28") / 0.05
Range("$F$8").Value = currentValueAdd
End Sub

My first question is whether excel VBA will recognize an if statement with two constraints
Of. Course.
BTW, there is no "Excel VBA", there is just VBA. And it is virtually equivalent to VB.
Maybe its a problem with my logic.
Very probably. There is no immediate problem to see in your code, though.

If im not mistaken then the second half of your if condition is never true right? By this i mean that "Range("$F$71").Value" is never less than "currentValueAdd".
If this is the case then i you need to relook into your logic of
currentValueAdd = Range("$F$71").Value
as this will always send the value of Range("$F$71") to currentValueAdd and when you are checking the condition
Range("$F$71").Value < currentValueAdd
the value in cell F71 has not changed since you transferred it to the variable and so your value in the variable is the same as the value in F71 thus your second condition will never be true.
Hope it has been of some help.

Related

Excel VBA get cell value after recalculation

I have a worksheet that calculates various values based on a random value and would like to use the law of large numbers to converge to an average for each calculation.
I am thinking of using VBA to execute the calculation 1000's of times and store the values in a list for averaging at the end. My current testing code only stores the original value after each iteration. ie Safety1 does not change even though the value in R36 changes.
Dim Safety1(0 To 10) As Long
For i = 0 To 10
Safety1(i) = Sheet34.Range("R36").Value
Debug.Print Safety1(i)
Next i
myAverage = Application.WorksheetFunction.Average(Safety1)
myAverage should be the converging average.
R36 contains the sum of other ranges, which contain values based on rand()
If there is a better way to do this, i am happy to listen.
Thanks in advance.
This post resolved the problem. I needed to wait until the calculation process had completed before storing the value
Please do sheet calculate like this:
Dim Safety1(0 To 10) As Long
Application.ScreenUpdating = False
For i = 0 To 10
Worksheets("Sheet34").Calculate
Safety1(i) = Sheet34.Range("R36").Value
Debug.Print Safety1(i)
Next i
Application.ScreenUpdating = True
myAverage = Application.WorksheetFunction.Average(Safety1)

How to get non-negative value using Do until or if statement?

I have a polynomial equation that i want to solve: L^3-4043L-60647=0 using goal seek in the vba.
This equation gives 3 roots : L1=70.06, L2, -54.04 and L3=-16.02 according to my calculator. But i only want my L in my excel cell to show the first positive root as my answer.
However when i do the goalseek using vba, it only gives me -16.02. How do i tell in my code to only solve for positive value?
I already tried using Do until and if statement. However Do until statement kept crashing and If statement is giving me wrong values.
Sub GoalSeek()
'GoalSeek Macro
Dim Length As Double
Dim i As Long
Range("Length") = i
If i > 0 Then
Application.CutCopyMode = False
Application.CutCopyMode = False
Range("GS").GoalSeek Goal:=0.1, ChangingCell:=Range("Length")
Else
End If
End Sub
I tried using this if statement. However my L or "Length" comes up only to be 0. I am very very beginner level in VBA. I don't know what i am doing wrong.
GoalSeek gets the nearest solutions to the starting value.
You can use the following code:
Sub GoalSeek()
Dim i As Double
'Set the initial value to a very high number
Range("Result").Value = 9999
'Ask GoalSeek to get the neares solution to that high value
Range("Formula").GoalSeek Goal:=0, ChangingCell:=Range("Result")
If Range("Result").Value > 0 Then
'If the value is positive, we need to make sure that it is the first positive solution
i = -1
Do
i = i + 1
'Set a new inital value. This time, a small one (starting from 0)
Range("Result").Value = i
'Ask GoalSeek to get the neares solution to the small initial value
Range("Formula").GoalSeek Goal:=0, ChangingCell:=Range("Result")
'If the result is negative, loop (increase the initial value and try again till you find the first positive one
Loop While Range("Result").Value < 0
Else 'If the nearest result to the high value is negative, keep it & show a message box.
MsgBox "No +ve solution found"
End If
End Sub
In your example, you have three solutions 70.06, -54.04 & -16.02
The nearest to 0 is -16.02, to 9999 is 70.6 and to -9999 is -54.04
What if the solutions are -5, 7 & 12?
The nearest to 9999 is 12, but you want 7, right?
So we ask for the nearest to 0 (-5) then, we keep increasing the initial value till the nearest solution becomes 7.
Please note that this assumes that you have an idea about what the results would be.
For example, if the solutions are -1 & 1,000,000, this code will not work because -1 is nearer to 9999 than 1,000,000.
In this case, you will need to change the initial high value more.
AND if you set it to a too high value that exceeds the limit of double data type 1.79E+308 or even to a value that makes the result of the formula exceed it, you will get an error.

MS Excel if statement

This may be really easy for someone who used excel allot in the past but i just can't get the right formula.
I have a large spreadsheet which i need to add a mark up onto but the mark up which adds on is dependent on variables. Unfortunately this result has to go on the end of a already big formula, the ######## represents where the IF statement will be placed.
IF >55 and is square = 15
IF >55 and is not square = 15
IF <55 and is Square = 25
IF <55 and is not square = 30
Cell D2 = has the numerical value
Cell G2 = Has the shapes in it.
=(((100-V2)/100)*U2*D2)+((((100-V2)/100)*U2*D2)/100*##########)
I really recommend you break the formula into cells, but if it's needed to be in one single cell, the formula can be done as well...
IF(D2>55;IF(G2="square";15;15);IF(G2="square";25;30))
So it's gonna be like:
=(((100-V2)/100)*U2*D2)+((((100-V2)/100)*U2*D2)/100*IF(D2>55;IF(G2="square";15;15);IF(G2="square";25;30)))
A little shorter (addresses D2=55):
=15+15*(D2<55)-5*(D2<55)*(G2="Square")
I am not a big fan of the nested ifs in Excel, especially when they contain and/or conditions. The typical formula would look something like:
=if(and(c1,c2),x1,if(and(c3,c4),x2,...
YUCK!
I'm definitely a VBA minimalist, but this does appear to be a good application of it. Make a custom function like:
Function FooBoo(Val As Integer, Shape As String) As Integer
Dim result As Integer
If Val > 55 And Shape = "square" Then
result = 15
ElseIf Val > 55 Then
result = 15
ElseIf Val < 55 And Shape = "square" Then
result = 25
ElseIf Val < 55 Then
result = 30
End If
FooBoo = result
End Function
And then you can call it from within Excel:
=(((100-V2)/100)*U2*D2)+((((100-V2)/100)*U2*D2)/100*FooBoo(val,shape))
If it's part of the workbook itself, it's no less portable than any other Excel formula, and it's more transparent and easier to maintain.
Also, unrelated, but I did note some things in your logic:
15 seems to be the result for values > 55, whether or not it's a square. You really could limit your first condition to values > 55
You haven't defined what happens if the value is exactly 55. Was one of those conditions >= or <=?

Excel vba: Is it possible to access the value of a cell before the UDF called from that cell executes?

I have a UDF which reads data from other sheets indirectly, adding the same cell across a number of sheets, ie:
Function myFunction( StrArgs As String ) As Long
....
End Function
I'm calling this function from cell A1, which right now has a value of 100:
=myFunction( ... )
Calculation is set to manual, and the sheet is refreshed when needed.
Since the arguments StrArgs define the sheets to be queried, I'm including some error-checking inside myFunction in case either the specified sheets don't exist, or there's an error in the specification of StrArgs.
This works fine, however what I'm struggling with is the following: when an error is found within myFunction, I want to return (keep) the existing value of the calling cell, instead of a zero or error value.
What I want to do at the beginning of myFunction is:
existingCellValue = Application.Caller.Text 'or Application.Caller.Value
Then perform the calculations, and when an error is encountered:
myFunction = existingCellValue
However, I find that this returns zero. In the debugger I see that, as soon as myFunction starts to execute, the cell value is already set to zero.
My question is - is there a way to access the existing value of the calling cell, before executing the UDF ?
-- edit - more complete code, as an example, which seems to work fine: --
Function GETNUMBER(Col As String, Row As Integer) As Double
Dim LookStr As String
Dim TheAnswer As Double
Dim CellVal As Variant
On Error GoTo errHandler
CellVal = Application.Caller.Text
LookStr = "=" & Col & Row
TheAnswer = Application.Evaluate(LookStr)
GETNUMBER = TheAnswer
On Error GoTo 0
Exit Function
errHandler:
GETNUMBER = CellVal
End Function
With the above code in a module, I enter the following in my workbook:
Row
1: | D | 1 | =GETNUMBER(A1,B1) | 10
2: | D | 2 | =GETNUMBER(A2,B2) | 20
3: | D | 3 | =GETNUMBER(A3,B3) | 30
This returns the values of 10, 20 and 30 from column D.
Now I change one of the cells in column B to zero, to invoke the errHandler, and return CellVal, stored at the start.
This seems to work, and both Application.Caller.Text and Application.ThisCell.Text give the correct result.
Thanks to both Charles Watson and KazJaw, both of whom answered the question.
There are several possible ways, but they all have disadvantages. The simplest way is to use Application.Caller.Text but it returns the formatted value rather than the actual value.
See my blog post on the subject for more discussion http://fastexcel.wordpress.com/2012/01/08/writing-efficient-vba-udfs-part-8-getting-the-previously-calculated-value-from-the-calling-cells/
You can do it with Application.ThisCell property. See quite simple example below:
Public Function MultipleAB(a, b)
Debug.Print Application.ThisCell.Text 'here you get current value
MultipleAB = a * b 'here you get new value
End Function
Important!! Application.ThisCell is valid only for UDFs used in cells. When used in other VBA Subs can return errors.
The picture below presents how this solution works (for randomly changed values in cells C1 & C2):

How to make match() work with date in excel vba?

I'm having problem making the match() work in excel VBA. The code is:
x = Application.Match("Sep 2008", Range("F1:F1"), 0)
The value in cell F1 is 9/1/2008.
Even if I changed Sep 2008 to 9/1/2008, it still doesn't return any value.
Any idea how to fix it?
The reason why Even if I changed Sep 2008 to 9/1/2008, it still doesn't return any value.
Is because when there is a Date in excel, Excel automatically converts that date to a numeric value, What you really want to search for is:
39692
This number is the number of days between 9/1/2008 and excel default of 1/1/1900
every date in excel is stored with a value like this. So the easiest way to handle this would be to convert what you see as a date to what excel sees as a date using CDate().
This by itself will give you an unuseful error that vba can't get the property.
That is because the Lookup_value can be a value (number, text, or logical value) or a cell reference to a number, text, or logical value. Not a date so simply convert the now date value to a number to search for the matching number in the list using CLng()
Give this a shot it will also be much faster then using the Find alternative:
x = WorksheetFunction.Match(CLng(CDate("Sep 2008")), Range("F1:F1"), 0)
This should give you the result expected
To handle when no match is found try this Sub:
Sub MatchDate()
Dim myvalue As Double
Dim LastRow As Long
LastRow = Cells(Rows.Count, "F").End(xlUp)
On Error GoTo NotFound
myvalue = WorksheetFunction.Match(CLng(CDate("Sep 2008")), Range("F1:F" & LastRow), 0)
MsgBox (myvalue)
End
NotFound:
MsgBox ("No Match Was Found")
End
End:
End Sub
Your best bet is to use .Find(). This will return a range if found or nothing if not.
Set x = Range("F1:F1").Find(CDate("Sept 2008"), , , xlWhole)
If you wanted the column number:
x = Range("F1:F1").Find(CDate("Sept 2008"), , , xlWhole).Column
With capture of not found
Sub test()
Dim y As Date, x As Variant, c As Long
y = CDate("Sep 2008")
Set x = Range("1:1").Find(y, , , xlWhole)
If Not x Is Nothing Then
c = x.Column '<~~found
Else
Exit Sub 'not found
End If
End Sub
Bottom line:
use WorksheetFunction.Match(CDbl(date), range, 0)
Alternatively, use a Date cell's Value2 property (which will also be a Double) instead of Value for the search key.
CLng suggested in other answers would discard the time part of the date.
The same problem exists for the Currency data type but you can't use CDbl for it (see below for options).
Range.Value2 Property (Excel) article suggests that Date and Currency types are "special" in that they have an "internal representation" that's in stark contrast with displayed value. Indeed:
Date is internally represented as IEEE 64-bit (8-byte) floating-point numbers where the integer part is the date and fractional part is the time
Currency is also 8-byte but is treated as a fixed-point number with 4 fractional digits (an integer scaled by 10'000)
Apparently, Match compares these internal values for performance reasons. So, we must ensure that they, rather than the readable representations, match exactly.
Since Date is already floating-point internally, CDbl(date) doesn't actually change the data.
For the Currency type, CDbl does change data, so it's out of question. So either
use the exact representation of the key (to 4 fractional digits) this way or another if you require exact match, or
make the cells in the range actually be formulas with Round) if the value to compare with comes from elsewhere and/or you only require equality to 2 fractional digits
This way it works using this method:
Nbr,L, C as Integer
Datedeb as date
nbr = WorksheetFunction.Match(CLng(CDate(Datedeb)), Range(Cells(L, C), Cells(L + 100, C)), 0)
I think I can safely assume that the value in F1 is a date. In you code "Sep 2008" is a string. You will never be able to get a successful match as long as your datatypes are inconsistent.
If you are looking for a date, then make sure that the first parameter is a date.
Dim dSearchSDate As Date
dSearchSDate = "01/Sept/2008"
x = Application.Match(dSearchSDate, Range("F1:F1"), 0)
Here is another possible approach.
Sub temp()
Dim x
Dim dSearchSDate As Date
dSearchSDate = "01/Sept/2008"
If ThisWorkbook.Worksheets(1).Range("F1:F1").Value = dSearchSDate Then
Debug.Print "Found it!"
Else
Debug.Print "Doh!!"
End If
End Sub
I know this post is old, but I had the same issue, and did find the answer.
To make it work, you first need to make VBA see the same data formatting as it appears in your excel spreadsheet :
YourVar = Format("YourDate","mmm-yyyy")
YourResult = Application.match(Clng(Cdate(YourVar)), YourRange, 0)
Regards
Gilles

Resources