Question

I needed a function to round up a positive double to its closest integer. lurking aorund i found this very elegant way

int x = floor(y + 0.5);

I wrote a simple testing program:

double a = 10.0;

for (int i = 0; i < 10; i++) {
  cout << a << "\t" << a + 0.5 << "\t" << floor(a + 0.5) << endl;
  a += 0.1;
}

but I receive some strange output

10      10.5    10
10.1    10.6    10
10.2    10.7    10
10.3    10.8    10
10.4    10.9    10
10.5    11      10 <--- should be 11!
10.6    11.1    11
10.7    11.2    11
10.8    11.3    11
10.9    11.4    11

whay is that?

thanks regards Luca

Was it helpful?

Solution 2

Here's the output, using printf instead:

printf("%.15f\t%.15f\t%.15f\n", a, a + 0.5, floor(a + 0.5));

The imprecision is clear now:

10.000000000000000  10.500000000000000  10.000000000000000
10.100000000000000  10.600000000000000  10.000000000000000
10.199999999999999  10.699999999999999  10.000000000000000
10.299999999999999  10.799999999999999  10.000000000000000
10.399999999999999  10.899999999999999  10.000000000000000
10.499999999999998  10.999999999999998  10.000000000000000
10.599999999999998  11.099999999999998  11.000000000000000
10.699999999999998  11.199999999999998  11.000000000000000
10.799999999999997  11.299999999999997  11.000000000000000
10.899999999999997  11.399999999999997  11.000000000000000

OTHER TIPS

By adding 0.1, you indeed add a value slightly below 0.1.

So adding 0.1 5 times is not the same as adding 0.5 once; you don't reach that value exactly. And by adding .5 again, you don't get beyond 11, which produces the behaviur you observed.


A C program such as

#include <stdio.h>
#include <math.h>

int main()
{
    double a = 10.0;
    int i;
    for (i = 0; i < 11; i++) {
        printf("%4.19f\t%4.19f\t%4.19f\n", a, a+.5, floor(a + 0.5));
        a += 0.1;
    }
    printf("\n");
    for (i = 0; i < 11; i++) {
        a = 10.0 + i/10.0;
        printf("%4.19f\t%4.19f\t%4.19f\n", a, a+.5, floor(a + 0.5));
    }
}

shows on its output

10.0000000000000000000  10.5000000000000000000  10.0000000000000000000
10.0999999999999996447  10.5999999999999996447  10.0000000000000000000
10.1999999999999992895  10.6999999999999992895  10.0000000000000000000
10.2999999999999989342  10.7999999999999989342  10.0000000000000000000
10.3999999999999985789  10.8999999999999985789  10.0000000000000000000
10.4999999999999982236  10.9999999999999982236  10.0000000000000000000
10.5999999999999978684  11.0999999999999978684  11.0000000000000000000
10.6999999999999975131  11.1999999999999975131  11.0000000000000000000
10.7999999999999971578  11.2999999999999971578  11.0000000000000000000
10.8999999999999968026  11.3999999999999968026  11.0000000000000000000
10.9999999999999964473  11.4999999999999964473  11.0000000000000000000

10.0000000000000000000  10.5000000000000000000  10.0000000000000000000
10.0999999999999996447  10.5999999999999996447  10.0000000000000000000
10.1999999999999992895  10.6999999999999992895  10.0000000000000000000
10.3000000000000007105  10.8000000000000007105  10.0000000000000000000
10.4000000000000003553  10.9000000000000003553  10.0000000000000000000
10.5000000000000000000  11.0000000000000000000  11.0000000000000000000
10.5999999999999996447  11.0999999999999996447  11.0000000000000000000
10.6999999999999992895  11.1999999999999992895  11.0000000000000000000
10.8000000000000007105  11.3000000000000007105  11.0000000000000000000
10.9000000000000003553  11.4000000000000003553  11.0000000000000000000
11.0000000000000000000  11.5000000000000000000  11.0000000000000000000

the difference: the 1st run is your approach with the cumulating error and the step with of 0.0999999999999996447, while the 2nd run recalculates a as close as possible, making it possible to reach 10.5 and 11.0 exactly.

The problem is due to accumulation of rounding errors. Floating-point numbers are represented internally not as integers, and their values are mostly approximate. So you are accumulating rounding error every time you execute a += .1 and a + .5 operation, and the result is what you get.

You can try to take a look not at incremental modification, but at using the following expressions (usually it gives better results on large scale):

a = 10. + .1 * i;

The problem is, as others have pointed out, that you never actually have 10.5; you only have something that is very close to 10.5 (but very slightly smaller).

As a general rule, for this sort of thing, you should not be adding an increment to the floating point value. You should only use the integral increment, and scale it each time to the floating point value:

for ( int i = 0; i != 10; ++ i ) {
    double aa = a + ( i / 10. );
    std::cout << aa << '\t' << aa + 0.5 << '\t' << floor( aa + 0.5 ) << std::endl;
}

This should give you the desired results.

Of course, if your example is only a test... A lot depends on how the value to be rounded is calculated. The actual rounding you are using may be appropriate. Or if you know that the values should be multiples of 0.1, you might try doing the arithmetic scaled by 10, then round the results, then round to a multiple of 10.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top