131

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.

Jignesh Joisar
  • 13,720
  • 5
  • 57
  • 57
Baalrukh
  • 1,330
  • 2
  • 9
  • 11
  • 9
    My guess is that one of the operations got done in single-precision while the other got done in double-precision. One of them returned a values slightly less than 62, hence yielding 61 when truncating to an integer. – Gabe Jan 18 '12 at 14:09
  • 2
    These are typical Float point precision issues. – TJHeuvel Jan 18 '12 at 14:09
  • And for good measure `(int)(6.2d * 10)` also returns `62`, which would support (somehow) what @Gabe suggested. – Christian.K Jan 18 '12 at 14:14
  • 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); the difference is probably to do with rounding, if you cast to double you will probably get something like 61.78426 – Neo Jan 18 '12 at 14:14
  • `Convert.ToInt32(6.2f * 10)` is also returning 62. – ken2k Jan 18 '12 at 14:14
  • `(int)(6.2d * 10)` returns 62. – Shai Jan 18 '12 at 14:15
  • 3
    Trying this on .Net/WinXP, .Net/Win7, Mono/Ubuntu and Mono/OSX gives your results for both Windows versions, but 62 for speed1 and speed2 in both Mono versions. Thanks @BoltClock – Eugen Rieck Jan 18 '12 at 14:16
  • @Ken2k possibly, because (6.2f * 10) is still float, thus invoking `Convert.ToInt32(float)` which (probably) exhibits the same behavior as using a temporary. – Christian.K Jan 18 '12 at 14:16
  • 6
    Mr Lippert... you around?? – vc 74 Jan 18 '12 at 14:18
  • 6
    The compiler's constant expression evaluator isn't winning any prizes here. Clearly it is truncating 6.2f in the first expression, it doesn't have an exact representation in base 2 so ends up as 6.199999. But does not do so in the 2nd expression, probably by managing to keep it in double precision somehow. This is otherwise par for the course, floating point consistency is never not a problem. This isn't going to get fixed, you know the workaround. – Hans Passant Jan 18 '12 at 14:38
  • See also http://stackoverflow.com/questions/6683059/are-floating-point-numbers-consistent-in-c-can-they-be – BlueRaja - Danny Pflughoeft Jan 18 '12 at 17:09

8 Answers8

173

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.

peeyush singh
  • 1,337
  • 1
  • 12
  • 23
Raymond Chen
  • 44,448
  • 11
  • 96
  • 135
  • 7
    I'm not sure this actually answers the question. Why is `(int)(6.2f * 10)` taking the `double` value, as `f` specifies it's a `float`? I think the main point (still unanswered) is here. – ken2k Jan 18 '12 at 14:26
  • 1
    I think it's the compiler that's doing that, since it's float literal * int literal the compiler has decided it's free to use the best numerical type, and to save precision it's gone for double (maybe). (also would explain IL being the same) – George Duckett Jan 18 '12 at 14:29
  • 5
    Good point. The type of `6.2f * 10` is actually `float`, not `double`. I think the compiler is optimizing the intermediate, as permitted by the last paragraph of [11.1.6](http://en.csharp-online.net/ECMA-334%3A_11.1.6_Floating_point_types). – Raymond Chen Jan 18 '12 at 14:37
  • @GeorgeDuckett I think your assumption might be the **real** answer to the OP's question. – ken2k Jan 18 '12 at 14:39
  • 6.2f * 10 should always result in the same value (and a float as both operands are floats), if you cast it to int or store it somewhere, it should be the same. the store should only copy the value, not "truncate it" to the nearest int – Baalrukh Jan 18 '12 at 14:48
  • 3
    It does have the same value (the value is 61.99999809265137). The difference is the path that value takes on the way to becoming an integer. In one case, it goes directly to an integer, and in another it goes through a `float` conversion first. – Raymond Chen Jan 18 '12 at 14:51
  • As far as I can tell, the multiplication between 2 floats should be a float itself, so there shouldn't be any conversion to float. Your explaination work if the result is a double, but as there is no explicit cast from double to float in the bytecode (and the store is a store.s) it seems to be a float result – Baalrukh Jan 18 '12 at 14:55
  • 38
    Raymond's answer here is of course completely correct. I note that the C# compiler and the jit compiler are both allowed to use more precision *at any time*, and to do so *inconsistently*. And in fact, they do just that. This question has come up dozens of times on StackOverflow; see http://stackoverflow.com/questions/8795550/casting-result-to-float-in-method-returning-float-changes-result/8795656#8795656 for a recent example. – Eric Lippert Jan 18 '12 at 15:02
  • 1
    thank you for the link, I was not aware of this behavior. It explains a lot ! – Baalrukh Jan 18 '12 at 15:19
  • Understanding this has got to be a right of passage for any programmer. I remember the first time it happened to me, circa 1978. I had to write my program out in pencil on a coding sheet and wait for the results (a deck of cards and a printout) to come back a week later, only to get the "wrong" answer. It was a lightbulb moment ;-) – Tim Long Mar 13 '16 at 16:00
12

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

Community
  • 1
  • 1
dknaack
  • 60,192
  • 27
  • 155
  • 202
5

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...

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
1

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 
Rodji
  • 33
  • 3
1

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.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • Casting to an `int` truncates the decimal value, it doesn't round. – Jim D'Angelo Jan 18 '12 at 14:20
  • @James D'Angelo: Sorry english isn't my primary language. Didn't know the exact word so I defined the behavior as "rounding downards when dealing with positive numbers" which basically describes the same behavior. But yes, point taken, *truncate* is the exact word for it. – InBetween Jan 18 '12 at 14:23
  • no problem, it's just symantics but can cause trouble if somebody starts thinking `float`->`int` involves rounding. =D – Jim D'Angelo Jan 18 '12 at 14:25
0

The short answer is that the two numbers go through different sequences of conversion. A more explicit way to get the same result would be:

    var speed1 = (int)((double)6.2f * 10);        // = 61 
    var speed2 = (int)(float)((double)6.2f * 10); // = 62

The result of the multiplication is a double slightly less than 62, due to the inherent lack of precision in floating-point representation. Converting this to a float will round to the nearest possible float value which happens to be slightly larger than 62. Converting to an int discards the fraction part, so this yields 61 and 62 respectively.

Your question is probably why these conversions happen differently for the two variables in your example. Because looking at the source code and even the IL it looks like they go through exactly the same calculations and conversions.

But the .net type system is sneaky. While it ostensibly supports a large set of numeric types, arithmetic operations are only supported for three different types: int, long and double. Operands of any of the other numeric types (like short, byte, float and the various unsigned types) are converted into one of these three supertypes before the operation.

At the CLR level, what happens is that the stack only support these three supertypes. A load instruction will read a value from memory, extend it to one of these three types and place it on the stack. A store operation will take a value on the stack and truncate it to the size of the target memory slot when storing it.

The operation ldc.r4 means - load this constant a of single-precision float type onto the stack. But when placed on the stack it is expanded into a double-precision value. So the mul operation happen on two doubles, and the result is a double. Now you can see what happens: For speed1, the resulting double is directly converted into an int. For speed2, the double on the stack is stored in memory as a float which result it in being trucated to half the precision. It is then loaded back unto the stack and thereby expanded to a a double again. But the truncation to float resulted in a loss of precision, which means the cast to an int will yield a different result.

JacquesB
  • 41,662
  • 13
  • 71
  • 86
0

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

Massimo Zerbini
  • 3,125
  • 22
  • 22
-5

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!

Samuel Slade
  • 8,405
  • 6
  • 33
  • 55
Neo
  • 2,305
  • 4
  • 36
  • 70