3

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

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • 2
    Doubles and floats (variables that are stored in floating point standard) are only approximations. The accuracy will not bind to a precise decimal value. – suspectus Mar 13 '13 at 09:19
  • http://stackoverflow.com/questions/5562492/strange-results-with-c-ceiling-function – uba Mar 13 '13 at 09:26
  • 1
    Lots of details on floating point computations: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html -- There are also nice PDF versions available online. But glglgl's answer already says it. – Arne Mar 13 '13 at 09:29
  • `10.5` is actually `10.4999999999` or so. `cout` prints it pretty, but floor knows that `10.999999999` is not `11`. – Yakk - Adam Nevraumont Mar 13 '13 at 09:36
  • 1
    `10.5` is `10.5`. Machine floating point is capable of representing it exactly. The problem is that he never has `10.5`. – James Kanze Mar 13 '13 at 09:38
  • mistery solved! Thanks! – user2164587 Mar 13 '13 at 09:48

4 Answers4

3

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.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • If OP added `0.125` they would fare better, as `1/8`th can be expressed exactly. – Peter Wood Mar 13 '13 at 09:41
  • 1
    @PeterWood If OP had added `0.125`, it's probable that he'd not have realized that there might be a problem. – James Kanze Mar 13 '13 at 09:52
  • @JamesKanze Sorry, yes, OP needs to understand there's a problem. I meant to help them to think about why some numbers work better than others. – Peter Wood Mar 13 '13 at 09:55
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
teppic
  • 8,039
  • 2
  • 24
  • 37
0

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;
Valeri Atamaniouk
  • 5,125
  • 2
  • 16
  • 18
  • 1
    It's a step in the right direction, but the problem here is that `0.1` cannot be represented exactly. Using `10. + (i / 10.)` should give the correct results, however: all of the numbers involved can be represented exactly, as can the results when `i` is `5`. (When `i` is `1`, the results are still not exactly `10.1`. But for rounding, we're only concerned when the results would be close to `something.5`.) – James Kanze Mar 13 '13 at 09:49
  • It's important to note that the rounding error here is in the **conversion** of `.1` to a floating-point value. A crude analogy would be `int i = 0; i += 3/2; i += 3/2;`. The resulting value of `i` is 2, not 3. – Pete Becker Mar 13 '13 at 12:09
0

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.

James Kanze
  • 150,581
  • 18
  • 184
  • 329