类型结果之间的意外差异

unexpected difference between types results

本文关键字:意外 结果 之间 类型      更新时间:2023-10-16
#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 类型在那里是错误的?

这不是一致的行为。即使在某些平台上int,它也可能会返回正确的数字。问题在于pow(10,i)此函数返回一个float数字,当将其添加到其他int(或char)时,它将返回另一个float数字。然后,您将整数添加到int变量中,这意味着需要从 float 转换为 int。您获得的号码可能是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彼得森提到的,你可以使用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);
}

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

printf("%dn", pow(10, i));
printf("%fn", 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("%dn", pow(10, i));
                           ^
gsamaras@pythagoras:~$ ./a.out 
1076101120
100.000000
1076101120
10.000000
1076101120
1.000000
Int value: 951
Float value: 951

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

浮点

数 POW(浮点基数,浮点指数);

因此,pow()返回一个float,然后被转换为int,这可能会导致一些精度的损失。

问题是pow不是将值计算为整数值(也许pow(x, y) exp(ln(x)*y)计算 - 但它不必完全是这样),而是作为浮点值,并且它会有一个小的舍入误差。这可能是"低于"或"高于"正确的值,并且由于从 floatint 的转换是通过截断进行的,因此950.999996被转换为 950 而不是 951 。在这种情况下,您可以通过将 pow 的结果舍入到最接近的值而不是截断 [假设结果与无穷精确值相差小于 +/-0.5] 来解决这个问题。

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

我将完全消除代码中使用(通常很麻烦的)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的有效范围,这应该给出更准确的结果。当然,float中的整数值在开始被斩波之前被限制为 23 位,因为缺乏浮点格式的精度。因此,任何高于 830 万左右的值都将缺少最低有效位。