【问题标题】:unexpected difference between types results类型结果之间的意外差异
【发布时间】:2016-03-16 07:40:51
【问题描述】:
#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 类型是错误的?

【问题讨论】:

  • 因为pow 计算的是浮点结果,在转换为int 时会被截断。
  • 下次说一下为什么你会期望输出不同,不过我会赞成,因为你还是新来的。
  • "为什么这里的 Int 类型有误?" -- 它永远不会错。该程序完全按照您告诉它的方式执行。如果结果不是你所期望的,你没有告诉它你认为你做了什么。 (在编译器或标准库中发现错误非常不太可能,尤其是对于初学者而言。);-)

标签: c++ floating-point int precision


【解决方案1】:

这不是一致的行为。在某些平台上,即使在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 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);
}

【讨论】:

  • 或使用round - 但我会简单地“累积”一个整数乘数,从 1 开始,每次乘以 10 - 很可能比调用 pow 更快 [即使是浮点值也是如此] .
  • 不是round返回一个float吗?但是,另一种解决方案更好,但情况可能并不总是关于 pow
  • 你可能想提到 std::lround。是的,它更好。
  • round 返回一个四舍五入到最接近整数的浮点数。因此,假设pow 实际上返回的值相当接近正确值,它会给出正确的值作为整数结果[并且它应该适用于任何有效的整数和浮点值,一旦你离开float 范围它可能会出错]。
【解决方案2】:

尽管我无法重新生成您使用 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(浮点数,浮点数);

因此,pow() 返回一个float,然后将其转换为int,这可能会导致一些准确性的损失。

【讨论】:

    【解决方案3】:

    问题在于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 会得到同样好的答案 - 但无论哪种方式,它都会变成一个浮点值,所以我想我'd 显示两种变体]

    对于int 的有效范围和float 的有效范围,这应该会给出更准确的结果。当然,float 中的整数值在开始被截断之前被限制为 23 位,因为浮点格式缺乏可用的精度。因此,任何高于 830 万的值都会丢失最低有效位。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-17
      • 2011-04-21
      • 1970-01-01
      相关资源
      最近更新 更多