TL;DR:
Because the variables that hold your numeric values are of type String
, the results can be very unexpected. To fix this, you should enable Option Strict
and fix the compiler errors that show up.
Now, let me answer your two questions in detail...
How to fix this?
As a first step, you should enable Option Strict
so that the compiler prevents you from doing those kinds of mistakes. You can read more about Option Strict
here and here. You can enable it by adding Option Strict On
at the top of your file. However, that is not good enough, in my opinion. You should really enable it for the entire project and for any new projects as well. To learn how to do that, see: Can I set Option Explicit and Option Strict on a Project/Solution level?.
Once it's enabled, you'll get compiler errors complaining about the fact that you're using strings and numeric types (e.g., Integer
, Single
, Double
) interchangeably. To fix that, here are some key points:
All the variables that will hold numeric values should be declared as numeric types.
- If you're going to store whole numbers only, you should use
Ineger
.
- To store decimal numbers, use
Single
, Double
, or Decimal
. To learn about the difference, check this article.
Any string values that you need to use for arithmetic operations need to be converted to the appropriate numeric type first. I will use Integer
as an example here:
- If you're absolutely sure that the string value is actually a number, you may use
Convert.ToInteger()
or Integer.Parse()
.
- If the string value can be anything as it's usually the case with user inputs (e.g.,
TextBox.Text
), you should use Integer.TryParse()
instead.
Now, to the "why" question...
why only numbers from 3 to 9 returns negative values
Well, the reason is a little complicated but it comes down to the fact that your Select Case
statement actually does a string comparison rather than a numeric one. Essentially, the Select Case
in your example gets converted to something like this:*
If String.CompareOrdinal(mar, "0") = 0 Then
marTotalAmt = 3.0F
ElseIf String.CompareOrdinal(mar, "1") >= 0 AndAlso
String.CompareOrdinal(mar, "200") <= 0 Then
marTotalAmt = CSng(CDbl(mar) * 0.109)
ElseIf String.CompareOrdinal(mar, "201") >= 0 AndAlso
String.CompareOrdinal(mar, "300") <= 0 Then
marTotalAmt = CSng(21.8 + (CDbl(mar) - 200.0) * 0.153)
ElseIf String.CompareOrdinal(mar, "301") >= 0 AndAlso
String.CompareOrdinal(mar, "600") <= 0 Then
marTotalAmt = CSng(37.1 + (CDbl(mar) - 300.0) * 0.172)
End If
Now, let's see what happens when the value of mar
is "3"
:
In the first If
condition: String.CompareOrdinal(mar, "0")
returns 3
, so the condition returns false.
In the first ElseIf
condition: String.CompareOrdinal(mar, "1")
returns 2
(which satisfies the first part of the ElseIf
condition, but String.CompareOrdinal(mar, "200")
returns 1
(which does not satisfy the second part of the condition), so again, the condition returns false.
In the second ElseIf
condition: String.CompareOrdinal(mar, "201")
returns 1
and String.CompareOrdinal(mar, "300") <= 0
returns -48
, so the condition is met and the following line is what gets executed:
marTotalAmt = CSng(21.8 + (CDbl(mar) - 200.0) * 0.153)
And since mar = 3
, we get 21.8 + (3 - 200) * 0.153
, which equals -8.341, which is the result you get.
As you can see, the results are completely unexpected. That's not only true for values from 3 to 9; you'll get wrong results for most of the values because the code is doing something completely different from what you thought it does. You just happened to notice the results for those specific values because they were negative numbers.
* Well, not exactly. Instead of String.CompareOrdinal(s1, s2)
it uses Microsoft.VisualBasic.CompilerServices.Operators.CompareString(s1, s2, False)
, which basically does the same as CompareOrdinal
, except that it only returns -1, 0, or 1.