C# Float expression: strange behavior when casting the result float to int
Solution 1
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
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
- Jon Skeet's DoubleConverter class
- Assert.AreEqual() with System.Double getting really confusing
- What Every Computer Scientist Should Know About Floating-Point Arithmetic
Solution 3
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
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
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
Baalrukh
Updated on June 16, 2022Comments
-
Baalrukh almost 2 years
I have the following simple code :
int speed1 = (int)(6.2f * 10); float tmp = 6.2f * 10; int speed2 = (int)tmp;
speed1
andspeed2
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.
-
ken2k over 12 years
Int.Parse
takes a string as parameter. -
vc 74 over 12 yearsYou can only parse strings, I guess you mean why don't you use System.Convert
-
Jim D'Angelo over 12 yearsCasting to an
int
truncates the decimal value, it doesn't round. -
InBetween over 12 years@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.
-
Jim D'Angelo over 12 yearsno problem, it's just symantics but can cause trouble if somebody starts thinking
float
->int
involves rounding. =D -
ken2k over 12 yearsI'm not sure this actually answers the question. Why is
(int)(6.2f * 10)
taking thedouble
value, asf
specifies it's afloat
? I think the main point (still unanswered) is here. -
George Duckett over 12 yearsI 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)
-
Raymond Chen over 12 yearsGood point. The type of
6.2f * 10
is actuallyfloat
, notdouble
. I think the compiler is optimizing the intermediate, as permitted by the last paragraph of 11.1.6. -
ken2k over 12 years@GeorgeDuckett I think your assumption might be the real answer to the OP's question.
-
Baalrukh over 12 years6.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
-
Raymond Chen over 12 yearsIt 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. -
Baalrukh over 12 yearsAs 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
-
Eric Lippert over 12 yearsRaymond'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 stackoverflow.com/questions/8795550/… for a recent example.
-
Baalrukh over 12 yearsthank you for the link, I was not aware of this behavior. It explains a lot !
-
Tim Long about 8 yearsUnderstanding 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 ;-)