C# Float expression: strange behavior when casting the result float to int

C#CastingFloating PointIntExpression

C# Problem Overview


I have the following simple code :

int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;

speed1 and speed2 should have the same value, but in fact, I have :

speed1 = 61
speed2 = 62

I know I should probably use Math.Round instead of casting, but I'd like to understand why the values are different.

I looked at the generated bytecode, but except a store and a load, the opcodes are the same.

I also tried the same code in java, and I correctly obtain 62 and 62.

Can someone explain this ?

Edit : In the real code, it's not directly 6.2f * 10 but a function call * a constant. I have the following bytecode :

for speed1 :

IL_01b3:  ldloc.s    V_8
IL_01b5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ba:  ldc.r4     10.
IL_01bf:  mul
IL_01c0:  conv.i4
IL_01c1:  stloc.s    V_9

for speed2 :

IL_01c3:  ldloc.s    V_8
IL_01c5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ca:  ldc.r4     10.
IL_01cf:  mul
IL_01d0:  stloc.s    V_10
IL_01d2:  ldloc.s    V_10
IL_01d4:  conv.i4
IL_01d5:  stloc.s    V_11

we can see that operands are floats and that the only difference is the stloc/ldloc.

As for the virtual machine, I tried with Mono/Win7, Mono/MacOS, and .NET/Windows, with the same results.

C# Solutions


Solution 1 - C#

First of all, I assume that you know that 6.2f * 10 is not exactly 62 due to floating point rounding (it's actually the value 61.99999809265137 when expressed as a double) and that your question is only about why two seemingly identical computations result in the wrong value.

The answer is that in the case of (int)(6.2f * 10), you are taking the double value 61.99999809265137 and truncating it to an integer, which yields 61.

In the case of float f = 6.2f * 10, you are taking the double value 61.99999809265137 and rounding to the nearest float, which is 62. You then truncate that float to an integer, and the result is 62.

Exercise: Explain the results of the following sequence of operations.

double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2

Update: As noted in the comments, the expression 6.2f * 10 is formally a float since the second parameter has an implicit conversion to float which is better than the implicit conversion to double.

The actual issue is that the compiler is permitted (but not required) to use an intermediate which is higher precision than the formal type (section 11.2.2). That's why you see different behavior on different systems: In the expression (int)(6.2f * 10), the compiler has the option of keeping the value 6.2f * 10 in a high precision intermediate form before converting to int. If it does, then the result is 61. If it does not, then the result is 62.

In the second example, the explicit assignment to float forces the rounding to take place before the conversion to integer.

Solution 2 - C#

###Description

Floating numbers a rarely exact. 6.2f is something like 6.1999998.... If you cast this to an int it will truncate it and this * 10 results in 61.

Check out Jon Skeets DoubleConverter class. With this class you can really visualize the value of a floating number as string. Double and float are both floating numbers, decimal is not (it is a fixed point number).

###Sample

DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875

###More Information

Solution 3 - C#

Look at the IL:

IL_0000:  ldc.i4.s    3D              // speed1 = 61
IL_0002:  stloc.0
IL_0003:  ldc.r4      00 00 78 42     // tmp = 62.0f
IL_0008:  stloc.1
IL_0009:  ldloc.1
IL_000A:  conv.i4
IL_000B:  stloc.2

The compiler reduces compile-time constant expressions to their constant value, and I think it makes a wrong approximation at some point when it converts the constant to int. In the case of speed2, this conversion is made not by the compiler, but by the CLR, and they seem to apply different rules...

Solution 4 - C#

My guess is that 6.2f real representation with float precision is 6.1999999 while 62f is probably something similar to 62.00000001. (int) casting always truncates the decimal value so that is why you get that behavior.

EDIT: According to comments I have rephrased the behavior of int casting to a much more precise definition.

Solution 5 - C#

I compiled and disassembled this code (on Win7/.NET 4.0). I guess that compiler evaluates floating constant expression as double.

int speed1 = (int)(6.2f * 10);
   mov         dword ptr [rbp+8],3Dh       //result is precalculated (61)

float tmp = 6.2f * 10;
   movss       xmm0,dword ptr [000004E8h]  //precalculated (float format, xmm0=0x42780000 (62.0))
   movss       dword ptr [rbp+0Ch],xmm0 

int speed2 = (int)tmp;
   cvttss2si   eax,dword ptr [rbp+0Ch]     //instrunction converts float to Int32 (eax=62)
   mov         dword ptr [rbp+10h],eax 

Solution 6 - C#

Single mantains only 7 digits and when casting it to a Int32 the compiler truncate all the floating point digits. During conversion one or more significant digits could be lost.

Int32 speed0 = (Int32)(6.2f * 100000000); 

gives the result of 619999980 so (Int32)(6.2f * 10) gives 61.

It's different when two Single are multiplied, in that case there is no truncate operation but only approximation.

See http://msdn.microsoft.com/en-us/library/system.single.aspx

Solution 7 - C#

Is there a reason you are type casting to int instead of parsing?

int speed1 = (int)(6.2f * 10)

would then read

int speed1 = Int.Parse((6.2f * 10).ToString()); 

The difference is probably to do with rounding: if you cast to double you will probably get something like 61.78426.

Please note the following output

int speed1 = (int)(6.2f * 10);//61
double speed2 = (6.2f * 10);//61.9999980926514

That is why you are getting different values!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBaalrukhView Question on Stackoverflow
Solution 1 - C#Raymond ChenView Answer on Stackoverflow
Solution 2 - C#dknaackView Answer on Stackoverflow
Solution 3 - C#Thomas LevesqueView Answer on Stackoverflow
Solution 4 - C#InBetweenView Answer on Stackoverflow
Solution 5 - C#RodjiView Answer on Stackoverflow
Solution 6 - C#Massimo ZerbiniView Answer on Stackoverflow
Solution 7 - C#NeoView Answer on Stackoverflow