2

Take the following code:

float a = 100.0f;
float b = 0.05f;

If I want to convert the result of division between a and b to an integer, I can do it with this code:

float c = a / (b * 1000.0f); // c = 2f
int res = (int)c; // res = 2

But I want to reduce the number of code lines, so I prefer to use this code instead:

int res = (int)(a / (b * 1000.0f)); // Here we are --> res = 1 !

Why with the last code I obtain res = 1 instead of res = 2 ?

Nicola
  • 621
  • 10
  • 22
  • 1
    I copy/pasted your code and get `2` for both. – Rufus L Apr 22 '19 at 23:09
  • 1
    @RufusL https://dotnetfiddle.net/ – TheGeneral Apr 22 '19 at 23:10
  • Thanks for your comment! I have tried my code in Unity3D and in https://dotnetfiddle.net/ and I get `1`, but now I have just tried to run my code in https://try.dot.net/ and I get `2`. Maybe a bug? – Nicola Apr 22 '19 at 23:12
  • Yeah, I get different results on dotnetfiddle as well. – Rufus L Apr 22 '19 at 23:16
  • 1
    The different results could be as simple as the optimisations from debug to release and bitness, however there is underlying compiler trickery going on here – TheGeneral Apr 22 '19 at 23:17
  • 1
    Possible duplicate of [Strange behavior when casting a float to int in C#](https://stackoverflow.com/questions/8911440/strange-behavior-when-casting-a-float-to-int-in-c-sharp) – Gianfrancesco Aurecchia Apr 22 '19 at 23:20
  • With https://dotnetfiddle.net/ : I have just tried the code `int res = (int)(2 * a / (b * 1000.0f))` and `res = 3` (`res` must be 4), it's very strange. – Nicola Apr 22 '19 at 23:27
  • 3
    Floating Point Arithmetic is performed using Base 2 so 100/50 is giving a results of 1.999999999999. Then when you cast to an integer it is rounding down to one. So you should use (int)Math.Round(float); You will also get better results using double instead of float. – jdweng Apr 22 '19 at 23:50
  • To be clear, when you use `(int)`, you're saying "take this float value, and just give me the integer part". The integer part of 1.999999999999 isn't 2, it's 1. This cast causes truncation, not rounding. – christophos Apr 23 '19 at 00:03
  • 1
    None of this explains what the jitter is doing though or why – TheGeneral Apr 23 '19 at 00:07
  • 2
    as pointed by @GianfrancescoAurecchia this exact question is already explained clearly at other places. This is just an instance of compiler/jitter choosing to use a higher precision for intermediate results – peeyush singh Apr 23 '19 at 01:24
  • 1
    Bottom line: this usually happens because x64 floating-point and x86 floating-point do not use the same instructions and don't yield the same results. On x86, the "classic" 8087 instructions are used, which are well known for using an extended intermediate precision internally, whereas x64 uses SSE, which does not. To further complicate matters, some JIT compilers do not take care to produce the same results under optimization, when the expression is calculated internally and emitted as a constant. Neither result is strictly speaking wrong, since `0.05` has no exact representation. – Jeroen Mostert Apr 23 '19 at 10:29
  • Note that `0.05 * 1000` and `1 / 0.05` *do* have exact representations, so there are ways the above calculation can be written that don't suffer from this problem and will produce `2` in all circumstances. (Of course, this assumes these exact values are used as constants.) – Jeroen Mostert Apr 23 '19 at 10:40

1 Answers1

3

The compiler uses extra precision when computing some expressions. In the C# language specification, clause 9.3.7 allows an implementation to use more precision in a floating-point expression than the result type:

Floating-point operations may be performed with higher precision than the result type of the operation.

Note that the value of .05f is 0.0500000007450580596923828125. When .05f * 1000.0f is computed with float precision, the result is 50, due to rounding. However, when it is computed with double or greater precision, the result is 50.0000007450580596923828125. Then dividing 100 by that with double precision produces 1.999999970197678056393897350062616169452667236328125. When this is converted to int, the result is 1.

In float c = a / (b * 1000.0f);, the result of the division is converted to float. Even if the division is computed with double precision and produces 1.999999970197678056393897350062616169452667236328125, this value becomes 2 when rounded to float, so c is set to 2.

In int res = (int)(a / (b * 1000.0f));, the result of the division is not converted to float. If the compiler computes it with double precision, the result is 1.999999970197678056393897350062616169452667236328125, and converting that produces 1.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312