Reputation: 429
While hunting a weird bug in my VB.NET application, I tracked it down to a shockingly puzzling detail. Here is a simple test code:
If 0.01 > 0.12 - 0.11 Then Debug.Print("what the hell")
0.12-0.11 is 0.01... Which is equal to the left side of the comparison. However, when I run this, the debug prints "what the hell"... Because seriously, what the hell. These numbers are equal.
Additionally, if I have a cycle like this:
Dim count As Integer = 0
For i As Double = 0.11 to 0.12 Step 0.01
count += 1
Next
Debug.Print(count)
It prints 1, meaning the cycle is executed only once, while it should execute twice.
Surprisingly, if I change 0.11, 0.12 and 0.01 in the above examples to 0.1, 0.2 and 0.1, then first example doesn't print anything, and the second example prints 2, as it should.
What is going on here? Am I missing something incredibly obvious, or is this some kind of floating point error or something?
Upvotes: 1
Views: 101
Reputation: 11216
If you can't use Decimal
for your calculations, then you must write your code to account for the fact that binary floating point types can't represent some fractional values exactly. So rather than checking if the numbers are equal, you check if they are nearly equals.
You can use code like the following (taken from this article by Michael Borgwardt:
This is the VB translation, but not tested extensively.
Public Shared Function NearlyEqual(a As Double, b As Double, epsilon As Double) As Boolean
Dim absA As Double = Math.Abs(a)
Dim absB As Double = Math.Abs(b)
Dim diff As Double = Math.Abs(a - b)
If (a = b) Then
'shortcut, handles infinities
Return True
Else
If (a = 0 OrElse b = 0 OrElse diff < Double.Epsilon)
'a or b is zero or both are extremely close to it
'relative error is less meaningful here
Return diff < epsilon
Else
'use relative error
Return diff / (absA + absB) < epsilon
End If
End If
End Function
Upvotes: 0
Reputation: 15091
How about Integer arithmetic?
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim count As Integer = 0
For i As Integer = CInt(0.11 * 100) To CInt(0.12 * 100) Step CInt(0.01 * 100)
count += 1
Next
Debug.Print(count.ToString)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If CInt(0.01 * 100) > CInt(0.12 * 100) - CInt(0.11 * 100) Then
Debug.Print("what the hell")
Else
Debug.Print("It's Ok")
End If
End Sub
Upvotes: 0
Reputation: 2089
You're getting these problems because you're using floating-point types, which use base 2, and base 2 can't represent some fractional values exactly.
That's why fixed-point types like Decimal were devised. If your example code is reworked for fixed-point (using Decimal), it gives the expected results.
' Specify Decimal constants and this will worked as anticipated.
If 0.01D > 0.12D - 0.11D Then Debug.Print("what the hell")
' 0.12-0.11 Is 0.01... Which Is equal to the left side of the comparison.
' However, when I run this, the debug prints "what the hell"... Because
' seriously, what the hell. These numbers are equal.
' Additionally, If I have a cycle Like this
Dim count As Integer = 0
For i As Decimal = 0.11D To 0.12D Step 0.01D
count += 1
Next
Debug.Print(count) ' Prints 2
Upvotes: 1