15/4/2019, 13:04

When redundant type casts matter

In the last episode from the rabbit hole of broken dreams and despair that is trying to make deterministic floating point operations in .NET, we saw how a seemingly innocent series of operations would lead to behaviour that would depend both of target architecture and compiler optimization settings.

This time around, we'll see that even Microsoft's own main C# editor Visual Studio doesn't quite get it right, and how an -- according to Visual Studio -- redundant operation in C# can cause results to change.


C# has statically-typed variables: Compile-time verification helps ensure that algorithms behave correctly, for example by ensuring that one can not use a string in a function that expects an integer.

Moreover, under certain conditions, it is possible to convert the type of a variable to some other type. For example, if int a = 60, it is possible to convert int, the type of the variable, to long, either implicitly through long b = a or explicitly through long c = (long)a (try it).

Not all conversions are possible – for instance, you can not convert string to int – but the mathematically minded may appreciate the fact that the specification explicitly spells out that identity conversions are always possible. That is, any type T can be converted to T. And this is where things get a bit weird.

The example

Let us return to our example from the previous post, changed ever so slightly:

float f1 = 0.00000000002f;
float f2 = 1 / f1;
double d1 = f2;
double d2 = (float) f2;

Here, we perform some simple operation of single-precision floating-point numbers, convert the result to a double-precision in two different ways, and print the result. The thing to pay attention to is the explicit cast (float) f2 in which we perform the identity conversion on f2, which is a float, converting its type to float. One would expect that this does nothing, and that d1 and d2 would contain the same value, yet by building the program with an x86 target architecture in release mode, and running it, one gets the following output:


This is rather surprising. So much so that Visual Studio itself stumbles and claims that the cast is redundant:

The explanation

We reported this behaviour as a possible bug in the Visual Studio code analyzer, and the subsequent discussion reveals what is going on here.

In the previous post, we saw how for local variables, float really means "single-point precision or higher", and digging into the ECMA specification of C#, one finds that the explicit cast effectively changes the meaning of float to "exactly single-point precision". From §11.1.2:

In most cases, an identity conversion has no effect at runtime. However, since floating point operations may be performed at higher precision than prescribed by their type (§9.3.7), assignment of their results may result in a loss of precision, and explicit casts are guaranteed to reduce precision to what is prescribed by the type.

Somewhat curiously, the Microsoft version of the specification contains the exact same text as the ECMA version, except it leaves out the above crucial point on identity conversions of floating-point types.


Ingen kommentarer endnu.

Tilføj kommentar

For at undgå for meget spam på siden skal du logge ind for at tilfæje en kommentar. Det kan du gøre nedenfor, eller du kan lave en bruger, hvis du ikke har en allerede. Du kan også bruge dit fotologin her.

Kodeord:  Glemt din kode?

[ Nyt billede ]