views:

326

answers:

5

This feels like the kind of code that only fails in-situ, but I will attempt to adapt it into a code snippet that represents what I'm seeing.

float f = myFloat * myConstInt; /* Where myFloat==13.45, and myConstInt==20 */
int i = (int)f;
int i2 = (int)(myFloat * myConstInt);

After stepping through the code, i==269, and i2==268. What's going on here to account for the difference?

+4  A: 

Because floating point variables are not infinitely accurate. Use a decimal if you need that kind of accuracy.

Different rounding modes may also play into this issue, but the accuracy problem is the one you're running into here, AFAIK.

technophile
+1 for the interesting links
Robusto
Um, decimals aren't infinitely accurate either.
Daniel Pryden
@Daniel: decimals aren't infinitely accurate, but they are *consistently* accurate. Decimal arithmetic is actually all done in integers, not in floats, and so it does not have precision based on the quality of the chip.
Eric Lippert
@Eric: I wasn't trying to say otherwise. It's just that I read technophile's answer as implying that one should use a decimal when you need infinite accuracy, and I was merely trying to point out that that's not realistic.
Daniel Pryden
You're correct Daniel, I misstated; I should have been explicit that decimals provide consistent rather than infinite accuracy.
technophile
+15  A: 

Float math can be performed at higher precision than advertised. But as soon as you store it in float f, that extra precision is lost. You're not losing that precision in the second method until, of course, you cast the result down to int.

Edit: See this question http://stackoverflow.com/questions/2491161/why-differs-floating-point-precision-in-c-when-separated-by-parantheses-and-when/2494724#2494724 for a better explanation than I probably provided.

Anthony Pegram
+1 good explanation.
technophile
second result is not correct! so precision is lost there. but where?
Andrey
See this question: http://stackoverflow.com/questions/2491161/why-differs-floating-point-precision-in-c-when-separated-by-parantheses-and-when/2494724#2494724 for a better explanation that I probably provided.
Anthony Pegram
Great answer, thank you :)
izb
Anthony's link is more informative, but the actual multiplication is probably being performed at hardware-level precision. That extra precision is lost for i as soon as you assign the result to f. It's not lost in i2 until you cast to int; the hardware-to-int cast and the hardware-to-float-to-int paths involve different transformations and thus lose different amounts of precision.
technophile
@Andrey: see my answer below -- I think it gives the details you are looking for.
Rick Regan
+1  A: 

Replace with

double f = myFloat * myConstInt;

and see if you get the same answer.

Martin
While it may give the "correct" result in this case, doubles will still exhibit the same problem in some cases. You need an arbitrary precision type to avoid the issue.
technophile
+2  A: 

Floating point has limited accuracy, and is based on binary rather than decimal. The decimal number 13.45 cannot be precisely represented in binary floating point, so rounds down. The multiplication by 20 further exaggerates the loss of precision. At this point you have 268.999... - not 269 - therefore the conversion to integer truncates to 268.

To get rounding to the nearest integer, you could try adding 0.5 before converting back to integer.

For "perfect" arithmetic, you could try using a Decimal or Rational numeric type - I believe C# has libraries for both, but am not certain. These will be slower, however.

EDIT - I have found a "decimal" type so far, but not a rational - I may be wrong about that being available. Decimal floating point is inaccurate, just like binary, but it's the kind of inaccuracy we're used to, so it gives less surprising results.

Steve314
+1  A: 

I'd like to offer a different explanation.

Here's the code, which I've annotated (I looked into memory to dissect the floats):

 float myFloat = 13.45; //In binary is 1101.01110011001100110011
 int myConstInt = 20;
 float f = myFloat * myConstInt; //In binary is exactly 100001101 (269 decimal)
 int i = (int)f; // Turns float 269 into int 269 -- no surprises
 int i2 = (int)(myFloat * myConstInt);//"Extra precision" causes round to 268

Let's look closer at the calculations:

  • f = 1101.01110011001100110011 * 10100 = 100001100.111111111111111 111

    The part after the space is bits 25-27, which cause bit 24 to be rounded up, and hence the whole value to be rounded up to 269

  • int i2 = (int)(myFloat * myConstInt)

    myfloat is extended to double precision for the calculation (0s are appended): 1101.0111001100110011001100000000000000000000000000000

    myfloat * 20 = 100001100.11111111111111111100000000000000000000000000

    Bits 54 and beyond are 0s, so no rounding is done: the cast results in the integer 268.

    (A similar explanation would work if extended precision is used.)

UPDATE: I refined my answer and wrote a full-blown article called When Floats Don’t Behave Like Floats

Rick Regan