Strange precission problem with pow

I have this strange precission problem. When using unsigned long for integers with many digits, I have a strange problem with pow(). I use a cast of unsigned long, but it does not perform well. When using double, it gets perfect. That wouldn't be strange if I were crossing the limits of definition of unsigned long, but the example shows that this is not the case. I would expect the precission problem to be evident in floating point precission (decimals), not in the integers of a simple summatory. I include the results, because different compilers I guess will get different results, I use CodeBlocks in a Windows OS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <math.h>

using namespace std;

int main()
{
    double sum = 0;
    unsigned long upper_limit;

    for(int i=0;i<9;i++){
        sum += 1*(unsigned long)pow(10, i);
    } // for
    cout << sum << endl;
    cout << "Sum minus 111111111:" << endl << (sum - 111111111) << endl;
    // returns -4 instead of 0. When changed unsigned long in the cast by double, it works!!

    upper_limit = 211111111;
    cout << (upper_limit - 3);
    // evidence that the limits are not crossed
    return 0;
}
pow returns floating point values with some degree of precision. It does not return exact number, but the difference is usually not sees as not many people neew dwole precision and output is generally rounded so it is not apparent.

For example pow(10, 2) can return 99.99999999999999 It would be rounded to 100 in output and generally precision loss when summing will not accumulate enough to change visible results (you can see it by manually increasing output precision).

Casting value to integral type truncates it: 99.99 becomes 99 and slight deviations from actual value not worth mention becomes a huge problem. Use proper rounding before casting. Or just eschew casts altogether and simply sum doubles. Or drop your double datatype and use only integral types, replacing std::pow() with integer version.
Either write your own integer pow function (best) or apply std::llround() on the result of std::pow().
http://en.cppreference.com/w/cpp/numeric/math/round

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <cmath>
#include <cfenv>

// **** note: does not check for integer overflow
long long ipow( int base, unsigned int exp )
{
    if( exp == 0 ) return 1 ;
    else if( exp == 1 ) return base ;
    else return ipow( base, exp/2 ) * ipow( base, (exp+1)/2 ) ;
}

int main()
{
    unsigned long long sum = 0 ;
    const unsigned long long upper_limit = 211111111 ;

    {
        for( int i = 0 ; i < 9 ; ++i )
        {
            sum += std::llround( std::pow( 10, i ) ) ;

            // this is just to whet your curiosity; you may ignore this for the present
            if( std::fetestexcept(FE_INVALID) ) { std::cerr << "*** error: out of representable range\n" ; return 1 ; }
        }
        std::cout << sum << ' ' << upper_limit - 3 << '\n' ;
    }

    {
        sum = 0 ;
        for( int i = 0 ; i < 9 ; ++i ) sum += ipow( 10, i ) ;
        std::cout << sum << ' ' << upper_limit - 3 << '\n' ;
    }
}

http://coliru.stacked-crooked.com/a/d75c3bdb463e9f64
Topic archived. No new replies allowed.