类型结果之间的意外差异

时间:2016-03-16 07:40:52

标签: c++ floating-point int precision

#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;
}

输出:

Int value: 950
Float value: 951

为什么此代码返回不同的值?为什么Int类型有错?

3 个答案:

答案 0 :(得分:2)

这不是一致的行为。即使在int某些平台上,它也可能返回正确的数字。问题出在pow(10,i)此函数返回float个数字,当将其添加到其他int(或char)时,它会返回另一个float个数字。然后,您将整个数字添加到int变量,这意味着必须从floatint进行投射。您在float中获得的号码可能是951950.99999(或类似内容)。如果将950.99999投放到int,那么它将是950。这解释了结果。

如果你想出于某种原因(似乎不存在)坚持int变量,你应该做一些类似的事情:

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;
}

或者 @Mats Petersson 提到你可以使用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);
}

答案 1 :(得分:1)

即使我无法使用gcc 4.9.2重新生成您所看到的内容,我在循环中添加了一些printfs,如下所示:

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

得到了:

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

我怀疑您看到此行为是因为pow()具有此签名(最接近int):

  

float pow(float base,float exponent);

结果,pow()返回float,然后转换为int,这可能会导致某些准确性丢失。

答案 2 :(得分:1)

问题在于pow计算的值不是整数值(可能pow(x, y)计算exp(ln(x)*y) - 但它不一定要那样),但是一个浮点值,它将有一个小的舍入误差。这可能是正确值的“低于”或“高于”,并且由于从floatint的转换是截断,950.999996会转换为950而不是{{} 1}}。在这种情况下,您可以通过将951的结果四舍五入到最近而不是截断[假设结果与无限精确值相差小于+/- 0.5]来解决这个问题。

计算浮点值的方式始终具有一定的精度。整数计算总是精确到计算本身的最小整数值,但是对于courwse,C和C ++的规则是如果一边是浮点,则计算是以浮点执行的。

我会完全消除代码中使用(通常很麻烦)pow的问题。用于计算正确值和速度的更好方法是使用每次循环迭代更新的乘数值。像这样:

pow

[我显示的是整数和浮点乘数,不是严格要求的,你在第二个循环中使用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; } 得到的答案一样好 - 但无论哪种方式都变成浮点值,所以我以为我会显示两种变体]

对于imult的有效范围和int的有效范围,这应该会给出更准确的结果。当然,float中的整数值在开始被切断之前被限制为23位,因为浮点格式缺乏可用的精度。因此,任何超过830万的价值都将缺少最重要的一点。