Why is this basic number comparison producing an illogical result? - vb.net

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?

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

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

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

Related

Wrong result if variables are not declared to 0 in calculator

everyone. I'm trying to make a basic calculator and as of now I only have the addition, sqrt, and % working. What confuses me is that, if I don't declare the first number to 0 after performing an operation, it gives me the wrong result.
For example:
num2 += Val(displayPanel.Text)
result = num1 + num2
displayPanel.Text = result
num1 = 0
num2 = 0
This certain code will give me correct results while
num2 += Val(displayPanel.Text)
result = num1 + num2
displayPanel.Text = result
will give me the answer '5' if I put 1+1 = 2 + 1. Same goes for my other classes where:
num1 += Val(displayPanel.Text)
result = Math.Sqrt(num1)
displayPanel.Text = result
num1 = 0
gives me the right result after doing sqrt of 9 + 1 which returns 4. Whereas if I removed the num1 = 0, doing sqrt of 9 + 1 will be 13. It seems that it's ignoring the operand and instead joins the two numbers as one string for the sqrt method.
My program is working fine now but can someone explain to me how not declaring num1 or num2 to 0 gives me an incorrect calculation?
Public Class Form1
Dim num1 As Single
Dim num2 As Single
Dim result As Single
addBtn_Click(sender As Object, e As EventArgs) Handles addBtn.Click
num1 += Val(displayPanel.Text)
displayPanel.Text = ""
equalBtn_Click(sender As Object, e As EventArgs) Handles equalBtn.Click
num2 += Val(displayPanel.Text)
result = num1 + num2
displayPanel.Text = result
num1 = 0
num2 = 0
sqrt_Click(sender As Object, e As EventArgs) Handles sqrt.Click
num1 += Val(displayPanel.Text)
result = Math.Sqrt(num1)
displayPanel.Text = result
num1 = 0
I think you might have problem with +=. Maybe change it to a simple =.
It's a bit hard to know everything that is going on here since I don't see all the code. Assuming num1 is equal to 0 at the beginning of the problem.
Also, let's convert += to something equivalent but easier to understand.
num1 = num1 + Val(displayPanel.Text)
result = Math.Sqrt(num1)
displayPanel.Text = result
If I write 9 in the textbox, num1 would be equal to 9. But if I execute it a second time, num1 would be equal to 18 since it's adding itself to the current value.
Your question "explain to me how not declaring num1 or num2 to 0 gives me an incorrect calculation?"
You have declared num1 and num2 at the class level (Form level) They will hold there assigned values until the entire form goes out of scope. You need them to hold there value so you can refer to these values in more than one method.
When you use the += you are asking the compiler to take the existing value and add a new value to it. with a simple assignment = you overwrite the value contained in the varaible.
Since += adds to the existing value instead of overwriting it completely you need to reset it to zero after each calculation.
If you used = you would not need to reset to zero because it would be overwritten.
The Val function in Vb.net is not operating as you are expecting it to.
Val attempts to convert textual information to a double or an integer.
See the documentation for it here.
Note that it explicitly states this:
(The Val function) stops converting at the first character that cannot be interpreted as a numeric digit, numeric modifier, numeric punctuation, or white space.
That is why it completely skips the = operator, and should skip parts like sqrt of; they are not a numeric modifiers, punctuation, digits, or whitespace.
Also of note, you may be seeing incorrect values because of multiple test cases in succession. Since your num1 and num2 variables have values that are not cleared, each additional evaluation will reuse the variables as they are.

Unconventional rounding of numbers - not all numbers work

I'm adding two decimal numbers. Whenever the fractional part gets to 0.60 it should be rounded up, for example 20.60 is rounded up to 21.00.
I've been able to do that, and the application is mostly working, but whenever the number before the decimal point exceeds the hundreds column, lets say 999.40, and it gets to the thousands column, the thousands separator doesn't work. For example, if I want to add two numbers, say 600.20 and 600.30, instead of my answer being 1,200.50 I get 1.00.
This is my code so far:
Private Sub Calculate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Calculate.Click
'ans for txtbox5.tetx
Try
If Convert.ToInt32(Microsoft.VisualBasic.Right((Val(Label41.Text).ToString("N2")), 2)) + Convert.ToInt32(Microsoft.VisualBasic.Right((Val(Label42.Text).ToString("N2")), 2)) > 99 Then
MessageBox.Show("invalid entry")
Else
Label41.Text = Val(TxtBox2.Text)
Label42.Text = Val(TxtBox34.Text)
'sum of numbers in two txtbox
'TxtBox5.Text = Val(TxtBox2.Text) + Val(TxtBox34.Text)
Label43.Text = (Val(Label41.Text) + Val(Label42.Text)).ToString("N2")
'strip last 2 decimals :
ln = Convert.ToInt32(Microsoft.VisualBasic.Right((Val(Label43.Text).ToString("N2")), 2))
Label44.Text = ln.ToString
'form decimal from 2 decimals
Label45.Text = "0." & Label44.Text
'subtract new decimal from 1st answer
Label46.Text = (Val(Label43.Text) - Val(Label45.Text)).ToString("N2")
'checks if striped decimal is btw 100 and 59
If (Val(Label44.Text)) < 100 And (Val(Label44.Text)) > 59 Then
runup = runup + 1
newans = (Val(Label44.Text) - Val(60))
Label45.Text = (Val(Label45.Text) - Val(0.6)).ToString("N2")
Try
'check if decimal is between 100 and 59
If (Val(newans)) < 100 And (Val(newans)) > 59 Then
runup = runup + 1
newans = (Val(newans) - Val(60))
Label45.Text = (Val(Label45.Text) - Val(0.6)).ToString("N2")
Label47.Text = (Val(runup) + Val(Label46.Text)) + Val(Label45.Text).ToString("N2")
runup = 0
'check if new decimal is between 60 and 0
ElseIf (Val(newans)) < 60 And (Val(newans)) >= 0 Then
Label47.Text = ((Val(runup) + Val(Label46.Text)) + Val(Label45.Text)).ToString("N2")
runup = 0
End If
Catch ex As Exception
MsgBox(ex.Message)
End Try
'check if striped decimal is btw 60 and 0
ElseIf (Val(Label44.Text)) < 60 And (Val(Label44.Text)) >= 0 Then
Label47.Text = ((Val(runup) + Val(Label46.Text)) + Val(Label45.Text)).ToString("N2")
runup = 0
End If
End If
Catch ex As Exception
End Try
TxtBox5.Text = Label47.Text
What is causing the problem with the larger numbers?
It looks like you are making your code harder to understand by using UI elements (e.g. labels) as if they were variables. This is not helped by the names of those labels - how are you meant to figure out what, say, "Label44" represents?
You can take the input data from the textboxes and parse that into a suitable data type - I recommend Decimal for this particular case - and using suitable variable names will make it easier to see what the code is doing.
As an example, I put three textboxes on a form and named them "tbNum1", "tbNum2", and "tbSum", added a button named "bnCalculate", and used this code:
Private Sub bnCalculate_Click(sender As Object, e As EventArgs) Handles bnCalculate.Click
Dim num1 As Decimal = Decimal.Parse(tbNum1.Text)
Dim num2 As Decimal = Decimal.Parse(tbNum2.Text)
Dim sum = num1 + num2
Dim frac = sum - Math.Floor(sum)
If frac >= 0.6 Then
sum = Math.Ceiling(sum)
End If
tbSum.Text = sum.ToString("N2")
End Sub
The code would need to be modified to work correctly for negative numbers.
I could not discern what some of your code was intended to do, e.g. is the first check intended to restrict the number of decimal places entered? There are cleaner ways to do that.
N.B. Do not use Try...Catch with an empty Catch part because it will hide problems from you.

VB.Net Double comparison after some additions

I've faced a weird case with a double variable after adding some values to it.
The problem occurs when adding (0.2) to a double variable more than one time -I think it only happens with (0.2)- for example: consider this code:
Dim i As Double = 2
i = i + 0.2
MsgBox(i) '2.2
MsgBox(i > 2.2) 'False >> No problem
But if I add (0.2) more than one time:
Dim i As Double = 2
i = i + 0.2
i = i + 0.2
MsgBox(i) '2.4
Msgbox(i > 2.4) 'True >> !!!!
Also
Dim i As Double = 2
For x As Integer = 1 to 5
i = i + 0.2
Next
MsgBox(i) '3
Msgbox(i > 3) 'True >> !!!!
I tried the same code with other values, I don't get this issue:
Dim i As Double = 2
i = i + 0.5
i = i + 0.5
MsgBox(i) '3
Msgbox(i > 3) 'False >> No problem
Anyone has an explanation for this??
Thank you
If you take the example 3 you would see that the result is actually 3.0000000000000009.
The problem is in the rounding of a double.
If you change the data type decimal the problem is fixed:
Sub Main()
Dim i As Decimal = 2
For x As Integer = 1 To 5
i = i + 0.2
Next
MsgBox(i) '3
MsgBox(i > 3) 'False >> No problem
End Sub
This is about C# but, I guess, it the same thing for vb.net.
This issue known as "Accuracy Problems (Wikipedia Link)"
The fact that floating-point numbers cannot precisely represent all
real numbers, and that floating-point operations can not precisely
represent true arithmetic operations, leads to many surprising
situations. This is related to the finite precision with which
computers generally represent numbers.

"Conversion from string " " to type 'Double' is not valid"

I am doing a project in microsoft visual studio 2012 and i am trying to write an application to determine the module average.
The script is the following:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles confirm.Click
Dim sum As Double
sum = CDbl(test.Text) * 50% + CDbl(project.Text) * 30% + CDbl(quiz.Text) * 30%
Dim modulemark As Double
modulemark = CDbl(CAmarks.Text) * 50% + CDbl(exam.Text) * 50%
Dim Grade As String
If sum < 40 Then
Grade = "F"
ElseIf sum >= 40 And modulemark < 65 And modulemark >= 40 Then
Grade = "C"
ElseIf sum >= 40 And modulemark < 75 And modulemark >= 65 Then
Grade = "B"
Else
Grade = "A"
End If
The script is intended to calculate the marks and give a grade after clicking a button named "Confirm".
However, when i tried to run the coding it said:
An unhandled exception of type 'System.InvalidCastException' occurred in Microsoft.VisualBasic.dll
Additional information: Conversion from string "" to type 'Double' is not valid.
can someone see what is wrong? i am new to Visual studio and i appreciate your help.
P.S. edited recent script.
P.S. Thank you for the user "Tim" for the script but for some unknown reason on the line "Double.TryParse(caMarks.Text, caMarks)" there's a blue squiggly that directs to caMarks that says "'Text' is not a member of 'double'". This is literally pulling my hair off! please help!
It looks like the names of the objects conflicted.
As Idle_Mind said in their answer, Double.TryParse is the way to go. This provides a safe way to attempt to convert a value to a double. If the conversion succeeds, the method returns true, and the resulting double is returned in the out parameter. If the conversion fails, false is returned and the default value of double (which is 0) is returned.
A simple example:
Dim result As Double
Dim score As String = "75"
If Double.TryParse(score, result) Then
' result will be a double with the value of 75
Else
' The conversion attempt failed, and result will have a value of 0
End If
So to apply that to your method (with no validation, though Idle_Mind's answer gives a good approach):
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles confirm.Click
Dim sum As Double
Dim modulemark As Double
Dim testScore As Double
Dim projectScore As Double
Dim quizScore As Double
Dim marks As Double
Dim examScore As Double
Double.TryParse(test.Text, testScore)
Double.TryParse(project.Text, projectScore)
Double.TryParse(quiz.Text, quizScore)
Double.TryParse(CAmarks.Text, marks)
Double.TryParse(exam.Text, examScore)
sum = (testScore * .5) + (projectScore * .3) + (quizScore * .3)
modulemark = (marks * .5) + (examScore * .5)
Dim Grade As String
If sum < 40 Then
Grade = "F"
ElseIf sum >= 40 And modulemark < 65 And modulemark >= 40 Then
Grade = "C"
ElseIf sum >= 40 And modulemark < 75 And modulemark >= 65 Then
Grade = "B"
Else
Grade = "A"
End If
End Sub
Explanation of the above code.
First, 6 Double variables are declared - this necessary because Double.TryParse takes an out parameter as the second argument, and that must be declared before its used. You could use one variable (and reuse it), but for simplicity I chose one for each score.
Once the scores have been parsed (successfully or not) the cumulative, weighted totals are determined. Note that parentheses were used when applying the weight modifier, to ensure operator precedence doesn't give you a result other than expected.
Hopefully this clarifies things for you.
Since you're assigning a letter grade to this variable...
Change Dim Grade As Integer
to Dim Grade As String.
Example of Double.TryParse():
Dim testValue As Double, projectValue As Double, quizValue As Double
If Double.TryParse(test.Text, testValue) Then
If Double.TryParse(project.Text, projectValue) Then
If Double.TryParse(quiz.Text, quizValue) Then
Dim sum As Double
sum = testValue * 0.5 + projectValue * 0.3 + quizValue * 0.3
' ... do something with "sum" ...
Else
MessageBox.Show(quiz.Text, "Invalid Quiz Score")
End If
Else
MessageBox.Show(project.Text, "Invalid Project Score")
End If
Else
MessageBox.Show(test.Text, "Invalid Test Score")
End If

Mod with Doubles

Am I doing something wrong or does the VBA Mod operator actually not work with floating point values like Doubles?
So I've always sort of assumed that the VBA Mod operator would work with Doubles based on the VB documentation, but in trying to figure out why my rounding function doesn't work, I found some unexpected Mod behavior.
Here is my code:
Public Function RoundUp(num As Double, Optional nearest As Double = 1)
RoundUp = ((num \ nearest) - ((num Mod nearest) > 0)) * nearest
End Function
RoundUp(12.34) returns 12 instead of 13 so I dug a little deeper and found that:
12.5 Mod 1 returns 0 with the return type of Long, whereas I had expected 0.5 with a type of Double.
Conclusion
As #ckuhn203 points out in his answer, according to the VBA specification,
The modulus, or remainder, operator divides number1 by number2
(rounding floating-point numbers to integers) and returns only the
remainder as result.
And
Usually, the data type of result is a Byte, Byte variant, Integer,
Integer variant, Long, or Variant containing a Long, regardless of
whether or not result is a whole number. Any fractional portion is
truncated.
For my purposes, I need a floating point modulo and so I have decided to use the following:
Public Function FMod(a As Double, b As Double) As Double
FMod = a - Fix(a / b) * b
'http://en.wikipedia.org/wiki/Machine_epsilon
'Unfortunately, this function can only be accurate when `a / b` is outside [-2.22E-16,+2.22E-16]
'Without this correction, FMod(.66, .06) = 5.55111512312578E-17 when it should be 0
If FMod >= -2 ^ -52 And FMod <= 2 ^ -52 Then '+/- 2.22E-16
FMod = 0
End If
End Function
Here are some examples:
FMod(12.5, 1) = 0.5
FMod(5.3, 2) = 1.3
FMod(18.5, 4.2) = 1.7
Using this in my rounding function solves my particular issue.
According to the VB6/VBA documentation
The modulus, or remainder, operator divides number1 by number2
(rounding floating-point numbers to integers) and returns only the
remainder as result. For example, in the following expression, A
(result) equals 5. A = 19 Mod 6.7 Usually, the data type of result is
a Byte, Byte variant, Integer, Integer variant, Long, or Variant
containing a Long, regardless of whether or not result is a whole
number. Any fractional portion is truncated. However, if any
expression is Null, result is Null. Any expression that is Empty is
treated as 0.
Remember, mod returns the remainder of the division. Any integer mod 1 = 0.
debug.print 12 mod 1
'12/1 = 12 r 0
The real culprit here though is that vba truncates (rounds down) the double to an integer before performing the modulo.
?13 mod 10
'==>3
?12.5 mod 10
'==>2
debug.print 12.5 mod 1
'vba truncates 12.5 to 12
debug.print 12 mod 1
'==> 0
I believe that the Mod operator calculates with long type only. The link that you provided is for VB.Net, which is not the same as the VBA you use in MSAccess.
The operator in VBA appears to accept a double type, but simply converts it to a long internally.
This test yielded a result of 1.
9 Mod 4.5
This test yielded a result of 0.
8 Mod 4.5
As a work around your can do some simple math on the values. To get two decimal of precision just multiply the input values by 100 and then divide the result by 100.
result = (123.45*100 Mod 1*100)/100
result = (12345 Mod 100)/100
result = 0.45
I'm late to the party, but just incase this answer is still helpful to someone.
Try This in VBS:
Option Explicit
Call Main()
Sub Main()
WScript.Echo CStr(Is_Rest_Of_Divide_Equal_To_Zero(506.25, 1.5625))
End Sub
Function Is_Rest_Of_Divide_Equal_To_Zero(Divident, Divisor)
Dim Result
Dim DivideResult
If Divident > Divisor Then
DivideResult = Round(Divident/Divisor, 0)
If (DivideResult * Divisor) > Divident Then
Result = False
ElseIf (DivideResult * Divisor) = Divident Then
Result = True
ElseIf (DivideResult * Divisor) < Divident Then
Result = False
End If
ElseIf Divident = Divisor Then
Result = True
ElseIf Divident < Divisor Then
Result = False
End If
Is_Rest_Of_Divide_Equal_To_Zero = Result
End Function
Public Function Modi(d as double) as double
Modi = d - Int(d)
End Function
Dim myDoule as Double
myDoule = 1.99999
Debug.Print Modi(myDoule)
0.99999