Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected results after optimizing switch case in Visual Studio with C#8.0

Tags:

Today while coding, visual studio notified me that my switch case could be optimized. But the code that I had vs the code that visual studio generated from my switch case does not result in the same outcome.

The Enum I Used:

public enum State
{
    ExampleA,
    ExampleB,
    ExampleC
};

After the following code runs the value is equal to 2147483647.

State stateExample = State.ExampleB;
double value;

switch (stateExample)
{
    case State.ExampleA:
        value = BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0);
        break;
    case State.ExampleB:
        value = BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0);
        break;
    case State.ExampleC:
        value = BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0);
        break;
    default:
        value = 0;
        break;
}

But when visual studio optimized the switch case, the value becomes 2147483648.

State stateExample = State.ExampleB;
double value = stateExample switch
{
    State.ExampleA => BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0), //Commenting this line results in correct value
    State.ExampleB => BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0),
    State.ExampleC => BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0),
    _ => throw new InvalidOperationException()
};

This is just the code with information that reproduced the erroneous output and not actual code that is run in production. What I found weird was that if I comment out the line State.ExampleA in the last code block the correct value is written.

My Question is: Is this a bug? Or am I missing something here?

like image 393
Jucko Thirteen Avatar asked Aug 03 '20 09:08

Jucko Thirteen


People also ask

What are some common problems with switch statements?

The biggest problem with switch statements, in general, is that they can be a code smell. Switch overuse might be a sign that you're failing to properly employ polymorphism in your code.

Is switch case available in C?

Important points to C Switch CaseOnly Integral values are allowed inside the case label, and the expression inside the switch must evaluate to a single integral constant. Variables are not allowed inside the case label, but integer/character variables are allowed for switch expression. Break and default are optional.

How many cases can be taken in C switch?

In a switch statement, the “case value” can be of “char” and “int” type. Following are some of the rules while using the switch statement: 1. There can be one or N numbers of cases.

What is default in switch case in C?

The default statement is executed if no case constant-expression value is equal to the value of expression . If there's no default statement, and no case match is found, none of the statements in the switch body get executed.


2 Answers

This highlights the difference between a statement and an expression. The switch you had before, was a switch statement and this was the assignment that got run.

value = BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0);

Here you are converting an uint (right hand side) to a double (left hand side). You were actually doing a different conversion in each branch of your switch statement, and this was fine, because well, they are separate assignment statements.

Compare that to what you are doing after the optimisation: the switch statement became a switch expression. And expressions have a single type. What is type of this expression?

stateExample switch
{
    State.ExampleA => BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0), //Commenting this line results in correct value
    State.ExampleB => BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0),
    State.ExampleC => BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0),
    _ => throw new InvalidOperationException()
}

Each branch of the switch returns a different type - float, uint and short respectively. So C# needs to find a type to which all three of these can be implicitly converted. And float is found. C# can't just "figure out" what the switch returns at runtime and work out the conversion to perform "dynamically".

The things returned in each branch has to be first converted to a float. Therefore, the type of the whole expression is float. Finally, you assign the float to value, which is a double.

So the overall conversion is uint -> float -> double, causing a loss of precision.

like image 184
Sweeper Avatar answered Sep 22 '22 13:09

Sweeper


This will work:

double value1 = stateExample switch
    {
        State.ExampleA => (double)BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0), //Commenting this line results in correct value
        State.ExampleB => BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0),
        State.ExampleC => BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0),
        _ => throw new InvalidOperationException()
    };

BitConverter.ToSingle returns float so compiler infers float (between float, uint and short) as output type for the switch expression (and casting uint and short to it) and then casts it's result to double which results in precision loss for ExampleB case.

like image 39
Guru Stron Avatar answered Sep 23 '22 13:09

Guru Stron