1

I am currently calculating the amount that the consumer needs to pay their electric bill per month, but right now, the equation inside the select case statement is outputting negative value if the consumer enters a number from 3 to 9.

For example:

March: 3

Output: -$8.34

These are the codes in my program...

Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Dim amt As Single
        Dim mar As String = TextBox1.Text

        Select Case mar
            Case 1 To 200
                amt = mar * 0.109
                TextBox2.Text = amt.ToString("C2")
            Case 201 To 300
                amt = 200 * 0.109 + (mar - 200) * 0.153
                TextBox2.Text = amt.ToString("C2")
            Case 301 To 600
                amt = 200 * 0.109 + 100 * 0.153 + (mar - 300) * 0.172
                TextBox2.Text = amt.ToString("C2")
        End Select
    End Sub
End Class

The right output I need:

The right output I need

Negative output:

Negative output

I would also appreciate answers that may help simplify these lines of code, thank you :)

  • The ["C2" format specifier](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-currency-c-format-specifier) can use parentheses to show negative values, if that helps you see where the problem is. – Andrew Morton Oct 03 '20 at 11:51
  • 1
    What you described can't be reproduced with the code currently in the question. Please [edit] the question and provide a [repro] – 41686d6564 stands w. Palestine Oct 03 '20 at 11:54
  • @CoreViSional See [What do Option Strict and Option Explicit do?](https://stackoverflow.com/q/2454552/8967612) and [Can I set Option Explicit and Option Strict on a Project/Solution level?](https://stackoverflow.com/q/5076851/8967612) – 41686d6564 stands w. Palestine Oct 03 '20 at 12:11
  • I've enabled it, very useful, but it still doesn't explain why only numbers from 3 to 9 returns negative values... –  Oct 03 '20 at 16:13
  • @CoreViSional The solution is to use `Option Strict On` **and** fix all the compiler errors you get as a result. As to your _"why do I get those values"_ question, the answer is... well, a little complicated. But since you want to know the reason, let me post an answer. – 41686d6564 stands w. Palestine Oct 03 '20 at 18:22

2 Answers2

1

Your problem is that you are comparing apples with oranges. You are testing a string value, not an integer. To fix this, you need to convert your text value to an integer. It is preferable to do this via TryParse, so your code should look like this:

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim amt As Single

    Dim mar As Integer

    If Integer.TryParse(TextBox1.Text, mar) Then

        Select Case mar
            Case 1 To 200
                amt = mar * 0.109
                TextBox2.Text = amt.ToString("C2")
            Case 201 To 300
                amt = 200 * 0.109 + (mar - 200) * 0.153
                TextBox2.Text = amt.ToString("C2")
            Case 301 To 600
                amt = 200 * 0.109 + 100 * 0.153 + (mar - 300) * 0.172
                TextBox2.Text = amt.ToString("C2")
        End Select
    Else
        MsgBox("Please enter a valid integer")
    End If

End Sub

If you had put a break-point on your code, you would have seen that it enters the case statement 201 to 300, which is not what you intended.

Jonathan Willcock
  • 5,012
  • 3
  • 20
  • 31
  • Oh my god... I wasted so much time on something that could've been resolved in seconds, thank you so much @Jonathan. Whenever I assign a variable that is for textbox, I tend to ignore the type of input since I know I'll use .Text frequently, so the data type should be a String... –  Oct 03 '20 at 17:33
1

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.

  • oh man...I only noticed this error after assigning 1 to 200 in the select case, I guess it happened because I only had one block of code inside the Select Case statement, and that is 301 to 600. I've only encountered this issue today because I would usually assign lots of variables just to avoid the System.InvalidCastException or incorrect values I am receiving. Thank you so much for these detailed answers and useful information @41686d6564 –  Oct 03 '20 at 19:03
  • May I know why it only slips to the second else if? How did String.CompareOrdinal(mar, "300") returning -1 when String.CompareOrdinal(mar, "200") didn't? Shouldn't the second condition of the first else if also returns -1? –  Oct 03 '20 at 19:28
  • @CoreViSional Actually those numbers are the result of `Operators.CompareString()`, not `String.CompareOrdinal()` (see the annotation section at the end of the answer). I initially used `Operators.CompareString` in the code but then changed it to make things simpler but I forgot to change the numbers. I just updated the answer now. As you can see `String.CompareOrdinal(3, "300")` returns `-48`, not `-1` (which still satisfies the condition)... – 41686d6564 stands w. Palestine Oct 03 '20 at 19:53
  • Note that `CompareOrdinal` compares based on the numeric values of the `Char`s in each string. You may refer to [the documantation](https://learn.microsoft.com/en-us/dotnet/api/system.string.compareordinal) for more info. – 41686d6564 stands w. Palestine Oct 03 '20 at 19:54
  • Thank you for providing me the documentation of `CompareOrdinal`, I will try to understand it because this is beyond my understanding of vb net. So, string "mar" is 3, and "1" is the range, right? But the range is <= 200, and 3 is of course <= 200, did it slip through the second 'else if' because of the numeric values of the chars in each string? –  Oct 03 '20 at 21:15
  • 1
    @Core _"But the range is <= 200, and 3 is of course <= 200"_ Hmm, I'm afraid that sentence means that you probably didn't get the main point of the answer. The whole point is that this is _string comparison_, not _numeric_ comparison. So, it's not `200`; it's `"200"` (big difference). The former is a number but the latter is a string that happens to have digit characters. Something like `3 < 200` is a numeric comparison. Something like `String.CompareOrdinal("3", "200")` is a string comparison just like `String.CompareOrdinal("a", "b")`. – 41686d6564 stands w. Palestine Oct 03 '20 at 21:22
  • 1
    Even though your original code used `200` (not `"200"`), it got converted to a string (i.e., `"200"`) because `mar` was string, so the compiler tries to _guess_ what type of comparison is needed for the `Select Case`. That's why `Option Strict Off` is evil; It forces the compiler to try and guess the programmer's intention, and the compiler sometimes, like in this case, gets it wrong. `Option Strict On`, on the other hand, forces the programmer to be explicit so that the compiler doesn't have to do any guesswork. – 41686d6564 stands w. Palestine Oct 03 '20 at 21:27
  • I see now what you meant by it is not only true for values from 3 to 9, because the compiler is guessing the type of comparison due to incorrect use of data type when I assign those variables. I have enabled the option strict on, so this will be very handy for any of my future projects. Again, thank you so much @4168 for elaborating your answers in detail to a beginner like me to understand :D –  Oct 03 '20 at 21:48
  • Since this involves money, `Decimal` is probably the correct numeric type (and note that this also means that the decimal type bug should be applied to the numeric constants so that intermediate calculations are also done as `Decimal`). – Craig Oct 05 '20 at 14:11