Reputation: 1168
I have been getting a very simple yet strange bug. I have a textbox which displays the sum of a certain field in a table of my database. If the sum equals 1, I want to set the the textbox's border to green. If it is greater, I want to make it red. If less, grey. This is my code
Private Sub calcSumRelativeRatios()
Dim val As Double
val = DSum("RelativeRatio", "ASCs")
Me.sumTextBox.Value = val
If val > 1 Then
Me.sumTextBox.BorderColor = vbRed
ElseIf val = 1 Then
Me.sumTextBox.BorderColor = vbGreen
Else
Me.sumTextBox.BorderColor = 16
End If
End Sub
Sometimes, even when the sum is 1 (i.e. val is 1), it enters the first if condition and makes the border red. To me this seems like a VBA bug but maybe it's because I am comparing two different primitive types.
Could someone explain to me why 1 > 1 is True?
Upvotes: 0
Views: 2257
Reputation: 32682
While the answer of Comintern is reasonable for most cases, it has some issues. For one, the delta should be relative to one of the measured values (bigger values have bigger inaccuracies for doubles). For this specific case, it'll do, since you're always comparing to 1.
Also, it's a good plan to move this to a separate function, since you might be comparing in multiple locations in your code, or in SQL.
I personally use this function to compare values that might be doubles. Note that it executes a strict comparison: it returns false if the variable types are inequal.
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 'No typecasting! Both values should have equal type!
Select Case VarType(Value1)
Case vbSingle
DblSafeCompare = Abs(Value1 - Value2) <= (AccuracyLevelSingle * Abs(Value1))
Case vbDouble
DblSafeCompare = Abs(Value1 - Value2) <= (AccuracyLevel * Abs(Value1))
Case vbDate 'Dates are really doubles
DblSafeCompare = Abs(CDbl(Value1) - CDbl(Value2)) <= (AccuracyLevel * Abs(CDbl(Value1)))
Case vbNull 'Note: you might want to set it to false here. I like Null = Null leading to Tru
DblSafeCompare = True
Case Else
DblSafeCompare = Value1 = Value2
End Select
End Function
Implement it:
Private Sub calcSumRelativeRatios()
Dim val As Double
val = DSum("RelativeRatio", "ASCs")
Me.sumTextBox.Value = val
If val > 1 And Not DblSafeCompare(val, 1#) Then 'Greater than 1 and not equal to 1
Me.sumTextBox.BorderColor = vbRed
ElseIf DblSafeCompare(val, 1#) Then '# = constant double, required
Me.sumTextBox.BorderColor = vbGreen
Else
Me.sumTextBox.BorderColor = 16
End If
End Sub
Upvotes: 2
Reputation: 55951
While these explanations are correct, they don't address the main cause for your trouble which is, that you have chosen the wrong data type for the purpose.
The simple solution is:
Dim val As Currency
and your original and plain code will work as expected.
Upvotes: 0
Reputation: 22205
Without going too far into the details, floating point numbers are vulnerable to precision errors. Wikipedia has a pretty good explanation of how and why these occur. A good way to account for this error is to subtract the target values and test to see if they fall under a "delta" value, or acceptable precision for the equity check.
For example, if you're good with assuming that differences of .00000001 should be treated as equal, your test would look something like this:
Private Sub calcSumRelativeRatios()
Const delta As Double = 0.00000001
Dim val As Double
val = DSum("RelativeRatio", "ASCs")
Me.sumTextBox.Value = val
If Abs(1 - val) < delta Then
Me.sumTextBox.BorderColor = vbGreen
ElseIf val > 1 Then
Me.sumTextBox.BorderColor = vbRed
Else
Me.sumTextBox.BorderColor = 16
End If
End Sub
Upvotes: 2