views:

318

answers:

6

How come ceil() rounds up an even floating with no fractional parts?
When I try to do this:

double x = 2.22;  
x *= 100; //which becomes 222.00...  
printf("%lf", ceil(x)); //prints 223.00... (?)  

But when I change the value of 2.22 to 2.21

x *= 100; //which becomes 221.00...  
printf("%lf", ceil(x)); //prints 221.00... as expected  

I tried to do it another way like this using modf() and encountered another strange thing:

double x = 2.22 * 100;  
double num, fraction;  
fraction = modf(x, &num);  
if(fraction > 0)  
    num += 1; //goes inside here even when fraction is 0.00...  

So what happens is 0.000... is greater than 0?
Can anyone explain why both of these situations are happening? Also I'm compiling using cc version 4.1.2 in RedHat.

+11  A: 

The basic answer is that the floating point number you get with:

double x = 2.22;

is actually a tiny bit larger than the value 2.22, and the value you get with

double x = 2.21;

is a tiny bit smaller than 2.21.

Daniel Martin
+13  A: 

This is normal as numbers are stored using binary. While your numbers can be written in finite numbers of digits using decimal, this does not hold for binary.

You should read Goldberg's report called What Every Computer Scientist Should Know About Floating-Point Arithmetic.

Goran Rakic
Everyone always points to that article, but it is not great. You're better off reading wikipedia
BlueRaja - Danny Pflughoeft
+2  A: 

That's because 2.22 is not exactly 2.22 and thus 2.22 * 100 is not 222 but 222.000000000x

double x = 2.22;  
printf("%.20lf\n", x); //prints 2.22000000000000019540
x *= 100; 
printf("%.20lf\n", x); //prints 222.00000000000002842171

If you need integer precision (e.g. to compute money related stuff) use integral arithmetic (i.e. compute cents instead of dollars).

diciu
It is a common error to suggest integer or fixed point arithmetic for financial applications, in practice decimal floating point (as opposed to binary floating point) is used. For percentage calculations (interest or tax for example), calculating in cents leads to a Superman III/Office Space scenario! ;-)
Clifford
@Clifford: do you have a pointer as to how using decimal floating point fixes the Superman III/Office Space scenarios? (I'm not saying it doesn't - I'm honestly curious).
Michael Burr
@Clifford - AFAIK GnuCash is using fixed point arithmetic so I doubt using fixed point correctly is impossible.
diciu
@Micheal Burr: No I just wanted to get the film reference in there somewhere. Since both are works of fiction, it is probably tenuous. ;-). The real problem is working in units of one cent (or one penny or whatever your smallest currency unit may be).
Clifford
@diciu: Not impossible, but you need higher precision that mere cents. And then you need sufficient range as well.
Clifford
+7  A: 

Not all floating-point values can be be correctly represented by float and double C types - most likely what you think is 222 actually something like 222.00000000000000001.

There is a standard, but little-known way to get around it - use nextafter() C99 function:

printf("%lf", ceil(nextafter(x, 0)));

See man nextafter for details.

qrdl
Interesting! See also 'nearbyint' and 'rint'.
Jonathan Leffler
@Jonathan `nearbyint()` rounds to closes integer, `nextafter()` allows you to specify the direction - `INFINITY`, `0`, `-INFINITY` or any arbitrary number.
qrdl
Yes, I recognize that. But 'nextafter()' produces the floating point value with the least significant bit of the mantissa adjusted by one: 'a value corresponding to the value of the least significant bit in the mantissa is added or subtracted, depending on the direction'. The 'nextafter()' function will work as long as the error is not more than 1 in the least significant bit - which is likely to be the case here. I was not suggesting that either 'nearbyint' or 'rint' was a replacement for 'nextafter' (or 'nexttoward'); just that they were also interesting, and possibly of relevance to the OP.
Jonathan Leffler
@Jonathan I got your point.
qrdl
See `nextafter` documentation: http://www.opengroup.org/onlinepubs/9699919799/functions/nextafter.html
Danilo Piazzalunga
+1  A: 

You may want to consider using decimal floating point types to get the results you'd expect from decimal arithmetic. The hardware on typical desktop PC processors supports only binary floating point, so cannot be expected to produce the same results as a decimal calculation. Of course, if not supported in hardware, decimal floating point is slower.

Note that decimal floating point is not necessarily more precise (for example 1/3 cannot be represented precisely, just as there are values in binary FP that cannot be represented), it will just produce the expected result, as if you performed the calculation long-hand in decimal.

Clifford
A: 

If you were to write:

const int x = 1.2;

in a C program, what would happen? The literal 1.2 isn't representable as an integer value, so the compiler would convert according to the usual rules to an integer, and x would be assigned the value 1.

The same thing is happening here.

You wrote:

double x = 2.22;

2.22 is not representable as a double-precision number, so the compiler converts it according to the rules in the C standard. You get the closest representable double-precision number, which is exactly:

2.220000000000000195399252334027551114559173583984375

When this value is multiplied by 100 in double, the result is:

222.000000000000028421709430404007434844970703125

and when you call ceil( ) with that value as an argument, the math library correctly returns 223.0.

Stephen Canon