DawidChrzest
DawidChrzest

Reputation: 46

unexpected difference between types results

#include <iostream>
#include <cmath>
using namespace std;

string number = "159";
float valueFloat = 0;
int valueInt = 0;
int main()
{
    for(int i = number.length()-1; i >=0; i--)
    {
        valueInt += (number[i]-48) * pow(10,i);
        valueFloat += (number[i]-48) * pow(10,i);
    }
    cout<<"Int value: "<<valueInt<<endl;
    cout<<"Float value: "<<valueFloat<<endl;
    return 0;
}

Output:

Int value: 950
Float value: 951

Why this code is returning different values? Why Int type is wrong there?

Upvotes: 2

Views: 107

Answers (3)

Mats Petersson
Mats Petersson

Reputation: 129524

The problem is that pow calculates the value not as an integer value (perhaps pow(x, y) is calculated exp(ln(x)*y) - but it doesn't HAVE to be exactly that way), but as a floating point value, and it will have a small rounding error. This may be "below" or "above" the correct value, and since conversion from float to int is by truncation, 950.999996 gets converted to 950 rather than 951. You can solve that, in this case, by rounding the result of pow to the nearest rather than truncating [assuming the result is less than +/-0.5 off from the infinitely precise value].

The way floating point values are calculated is ALWAYS with a degree of precision. Integer calculation is always exact to the least integer value of the calculation itself, but of courwse, the rules of C and C++ is that if one side is floating point, the calculation is performed in floating point.

I would completely eliminate the use of (often troublesome) pow from the code. A much better way, both for calculating the correct value and speed, is to use a multiplier value that is updated each loop iteration. Something like this:

int imult = 1;
float fmult = 1.0f;
for(int i = number.length()-1; i >=0; i--)
{
    valueInt += (number[i]-48) * imult;
    imult *= 10;
    valueFloat += (number[i]-48) * fmult;
    fmult *= 10.0f;
}

[I'm showing both integer and float multiplier, not strictly required, you'd get just as good an answer using imult in the second loop - but it becomes a floating point value either way, so I thought I'd show both variants]

For the valid range of int and valid range of float, this should give a more accurate result. Of course, integer values in float are restricted to 23 bits before they start getting chopped because of lack of precision available in floating point format. So any value above around 8.3 million will be missing the least significant bit.

Upvotes: 1

Humam Helfawi
Humam Helfawi

Reputation: 20324

It is not consistent behaviour. It may return the right number even in int on some platforms. The problem is in pow(10,i) this function return a float number and when add it to other int (or char) it would return a another float number. Then you are adding the whole number to an int variable which means that a casting from float to int is necessary. The number that you are getting in float may be 951 or 950.99999 (or something similar). if 950.99999 is casted to int it would be 950. That explains the results.

if you want to stick to an int variable for some reason (which does not seem exist) you should do something like that instead:

for(int i = number.length()-1; i >=0; i--)
{
    float p_float=pow(10,i);
    int p_int=static_cast<int>(p_float);
    p_int=(p_int-p_float)>.5f?p_int+1:p_int;
    valueInt += (number[i]-48) * p_int;
    valueFloat += (number[i]-48) * p_float;
}

Or as @Mats Petersson mentioned you can just use std::lround:

for(int i = number.length()-1; i >=0; i--)
{
    valueInt += (number[i]-48) * std::lround(pow(10,i));
    valueFloat += (number[i]-48) * pow(10,i);
}

Upvotes: 2

gsamaras
gsamaras

Reputation: 73444

Even though I couldn't re-generate what you see with gcc 4.9.2, I added some printfs in the loop, like this:

printf("%d\n", pow(10, i));
printf("%f\n", pow(10, i));

and got:

gsamaras@pythagoras:~$ g++ -Wall pc.cpp 
pc.cpp: In function ‘int main()’:
pc.cpp:14:27: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘__gnu_cxx::__promote_2<int, int, double, double>::__type {aka double}’ [-Wformat=]
  printf("%d\n", pow(10, i));
                           ^
gsamaras@pythagoras:~$ ./a.out 
1076101120
100.000000
1076101120
10.000000
1076101120
1.000000
Int value: 951
Float value: 951

I suspect that you see this behavior because of the pow(), which has this signature (closest to int):

float pow (float base, float exponent);

As a result, pow() returns a float which is then been converted to int, which may result in loss of some accuracy.

Upvotes: 1

Related Questions