15
(int)((float)10.9 * 10)

is evaluated to 108. Why?

IMO the (int)-cast should be evaluated after the multiplication.

Tho Mai
  • 835
  • 5
  • 25
  • 5
    You run into floating point calculation precision issue. – MarcinJuraszek Nov 04 '15 at 06:31
  • Could you please clarify what you've learn so far about floating point so we know where to start explanation (obviously you've searched for something like "C# what programer should know about flaoting point" - i.e. http://blog.reverberate.org/2014/09/what-every-computer-programmer-should.html - so feel free to add results of your research to the post). – Alexei Levenkov Nov 04 '15 at 06:31
  • 2
    @Joey you should undlete your answer, the answer to Sriam's comment on your answer is "it is because when you use a single statement the compiler actually evaluates it as `double f =((float)10.9 * 10);int i = (int)f;` it will use a double for the intermediate value." – Scott Chamberlain Nov 04 '15 at 06:41
  • Either use `Math.Round` when converting to `int`, or switch to `decimal` if you are dealing with currencies or other values with fit into the base-10 system naturally. – vgru Nov 04 '15 at 06:41
  • @Scott: shouldn't `var f` make the variable a `float`? I think the difference is that in the first case, entire expression is evaluated at compile time and replaced by an integer literal in MSIL. The other expression has two variables, so casting is done at runtime, hence the difference. But Joey's answer *is* correct, it's just that you can't rely on this behavior to be consistent. – vgru Nov 04 '15 at 06:43
  • Argh floating-point arithmetic.. I'll _probably_ never fully understand you. – Soner Gönül Nov 04 '15 at 06:46
  • 1
    @SonerGönül - I agree with you. It really seems like `short` and `float` are just significantly more dangerous than their longer counterparts. – Enigmativity Nov 04 '15 at 06:51
  • @ScottChamberlain: It appears someone else did that for me already. Quite confusing to come to work and see a bunch of comments to an answer you thought you deleted earlier ... – Joey Nov 04 '15 at 08:27
  • @Scott Chamberlain that's it. Casting `float` to `double` will just add meaningless digits, and `double` is like 'hey, look at how precise I am'. – Kay Zed Nov 04 '15 at 09:48
  • Possible duplicate of [(.1f+.2f==.3f) != (.1f+.2f).Equals(.3f) Why?](http://stackoverflow.com/questions/15117037/1f-2f-3f-1f-2f-equals-3f-why) – Deduplicator Nov 04 '15 at 13:58

3 Answers3

11

Amusingly enough, the issue here is that the expression is calculated at compile time, apparently using single precision math as expected. This happens in both debug and release builds:

    // this replaces the whole operation
    IL_0001: ldc.i4.s 108
    IL_0003: stloc.0
    IL_0004: ldloc.0
    IL_0005: call void [mscorlib]System.Console::WriteLine(int32)

While the case var f =((float)10.9 * 10);int i = (int)f; is still optimized for the multiplication, but using double precision. Because the cast is done in a separate step I guess it confuses the compiler (??):

    IL_000b: ldc.r4 109      // 109 result for the first part
    IL_0010: stloc.1
    IL_0011: ldloc.1
    IL_0012: conv.i4         // separate conversion to int
    IL_0013: stloc.2
    // rest is printing it
    IL_0014: ldloc.1
    IL_0015: box [mscorlib]System.Single
    IL_001a: ldstr " "
    IL_001f: ldloc.2
    IL_0020: box [mscorlib]System.Int32
    IL_0025: call string [mscorlib]System.String::Concat(object, object, object)
    IL_002a: call void [mscorlib]System.Console::WriteLine(string)

Honestly I'd say the compiler is generating wrong code in the second case, which is probably why C++ avoids optimizing floating point code like the plague. Luckily, this is only an issue for this kind of test code which can be done fully at compile time, actual variable multiplication would be fine.

I actually tried this too:

Console.WriteLine((int)(float.Parse(Console.ReadLine()) * int.Parse(Console.ReadLine())));

and gave it 10.9 and 10, and the result was 108 as expected.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • *...and the result was 108 as expected*. You have to love floating point. – vgru Nov 04 '15 at 07:58
  • I don't think that was ever in question, it's the fact that when broken in steps it gives 109 that's the weird thing. The compiler does the calculation internally using the wrong (better, but still wrong) type. – Blindy Nov 04 '15 at 13:47
  • However, `var z = 10; var f = (10.9f * z); int i = (int)f;` does do the multiplication (in IL) but still evaluates to 109. I feel like the jitter must be doing something fun. – zinglon Nov 04 '15 at 18:54
  • @EricLippert, I'd be curious to hear your thoughts on this subject. There's a lot of funky stuff going on here. – Blindy Nov 05 '15 at 04:55
10

That is exactly the case. It's just that 10.9 is slightly less than 10.9 due to the way floating point numbers are represented (10.9 cannot be represented exactly, so you get an approximation, which in this case is something like 10.89999999...). The cast then truncates any digits following the decimal point. So you get 108.

Exact values are as follows here (obtained with Jon Skeet's DoubleConverter class):

10.9         -> 10.9000000000000003552713678800500929355621337890625
(float) 10.9 -> 10.8999996185302734375

Multiplying that float by 10 and then cutting off all decimal places will obviously result in 108.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • 3
    Umm I don't understand then why splitting it into two statements works expected. `var f =((float)10.9 * 10);int i = (int)f;` gives 109 and 109 – Sriram Sakthivel Nov 04 '15 at 06:33
  • But why do i get `109` instead of `108.9999...` when I do the following: `((float)10.9 * 10).ToString()` – Tho Mai Nov 04 '15 at 07:02
  • Because `ToString` rounds, whereas casting to int truncates. – Lasse V. Karlsen Nov 04 '15 at 07:17
  • @Lasse: Why should ToString() round, when not given a format string? – Tho Mai Nov 04 '15 at 07:23
  • It just does. I don't know why they decided to do it like that. – Lasse V. Karlsen Nov 04 '15 at 07:26
  • @Lasse: Then why evaluates `((float)10.9*10).ToString("0.000")` to `109.000` instead of `108.999` ? – Tho Mai Nov 04 '15 at 07:41
  • 1
    @thomai: because the value is larger than `108.999`. Just like you would expect `108.0009` to be rounded to `108.001`. – vgru Nov 04 '15 at 08:02
  • It does rounding. The problem here is that everyone is trying to make sense of why floating point calculations does or does not make sense at this level of precision. The problem is that **they don't**. Smart people have tried to "fix" this problem for decades but it isn't possible because we have a finite number of bits available to store numbers that cannot be exactly represented in the way we're representing them, with bits. If you need to be able to reason about the behavior, you need to use `decimal`. Otherwise, there **will be** inaccuracies. – Lasse V. Karlsen Nov 04 '15 at 08:05
  • Personally I think this question here should be closed and linked to a duplicate just like "What is a null reference exception and how do I fix it" because the answers are invariably the same. Accept the inaccuracies, deal with them the best you can, and if you can't accept it, switch to a different data type that gives you the level of precision you can accept. – Lasse V. Karlsen Nov 04 '15 at 08:06
  • @Groo: Thanks, that makes sense. Is there a way to display the exact value of a float? Because that's what I want to archieve with the ToString(). – Tho Mai Nov 04 '15 at 08:51
  • @thomai That question doesn't make sense. What do you mean by "exact value of a float"? What's the exact value of `10 / 3`? – Luaan Nov 04 '15 at 09:51
  • In the `10 / 3`-example I want to find out what's the actual, internal value of the float. Probably something like `3.33333412`. The method should take the internal bits of the float and convert them to decimal without rounding (like ToString() does) – Tho Mai Nov 04 '15 at 10:03
  • 3
    @thomai: You can use [Jon Skeet](http://stackoverflow.com/users/22656/jon-skeet)'s [DoubleConverter class](http://www.yoda.arachsys.com/csharp/DoubleConverter.cs) which has a `ToExactString` method. The code could easily be adapted to `float` instead, but `float` works exactly the same, just with fewer decimal digits. – Joey Nov 04 '15 at 11:34
3

Your float result, before casting to int, is 108.9999 because 109.0000 can't be represented in floating point. (As mentioned in comments, that's wrong; 109 can be represented just fine, but somehow the expression results in 108.9999 -- the following still applies)

The int value will be 108. Why? Casting from floating point (double or float) to int truncates the decimal part. It doesn't round. So it cuts off the .9999 and you get 108.

To round, replace the cast by Math.Round() or Convert.ToInt32().

Convert.ToInt32((float) 10.9 * 10);
Kay Zed
  • 1,304
  • 2
  • 21
  • 31
  • http://blog.reverberate.org/2014/09/what-every-computer-programmer-should.html sais, that "Mathematical operations like these will give you exact results as long as all of the values are integers smaller than 2^24". 109 is way smaller than 2^24... – Tho Mai Nov 04 '15 at 07:46
  • 2
    Yeah, Kay is wrong, 109 can be represented fine. It's the multiplication itself that isn't precise enough. – Blindy Nov 04 '15 at 07:48
  • 1
    Although @Blindy is correct, +1 for mentioning `Convert.ToInt32`. I didn't know it rounds, that's a good thing. – vgru Nov 04 '15 at 08:05
  • @thomai: that is only true if you are dealing solely with integers. The paragraph starts with `0.1 + 0.2 != 0.3`. – vgru Nov 04 '15 at 08:07
  • It's the 10.9 which can't be represented, so it is replaced with 10.8999999. Multilying it with 10 afterwards won't help there any longer. – glglgl Nov 04 '15 at 11:53