c ++ float减法舍入误差

时间:2014-08-21 19:17:29

标签: c++ floating-point

我的浮点值介于0和1之间。我需要将其转换为-120到80。 要做到这一点,首先我在120减去后乘以200。 当减去时,我有舍入错误。 让我们看看我的榜样。

    float val = 0.6050f;
    val *= 200.f;

现在val是我预期的121.0。

    val -= 120.0f;    

现在val是0.99999992

我想也许我可以通过乘法和除法来避免这个问题。

    float val = 0.6050f;
    val *= 200.f;
    val *= 100.f;
    val -= 12000.0f;    
    val /= 100.f;

但它没有帮助。我手上还有0.99。

有解决方案吗?

编辑:在详细记录之后,我知道这部分代码没有问题。在我的日志显示“0.605”之前,我有详细的日志后,我看到“0.60499995946884155273437500000000000000000000000000” 问题出在不同的地方。

编辑2:我认为我发现了内疚。初始值为0.5750。

std::string floatToStr(double d)
{
    std::stringstream ss;
    ss << std::fixed << std::setprecision(15) << d;
    return ss.str();
}

int main()
{    
    float val88 = 0.57500000000f;
    std::cout << floatToStr(val88) << std::endl;
}

结果是0.574999988079071

实际上我每次都需要从这个值中添加和减去0.0025。 通常我预计0.575,0.57575,0.5800,0.52525 ......

Edit3:实际上我用双倍尝试了所有这些。这是我的榜样。

std::string doubleToStr(double d)
{
    std::stringstream ss;
    ss << std::fixed << std::setprecision(15) << d;
    return ss.str();
}

int main()
{    
    double val88 = 0.575;
    std::cout << doubleToStr(val88) << std::endl;
    val88 += 0.0025;
    std::cout << doubleToStr(val88) << std::endl;
    val88 += 0.0025;
    std::cout << doubleToStr(val88) << std::endl;
    val88 += 0.0025;
    std::cout << doubleToStr(val88) << std::endl;

    return 0;
}

结果是:

0.575000000000000
0.577500000000000
0.580000000000000
0.582500000000000

但不幸的是,我一定要漂浮。我需要改变很多东西。

感谢大家的帮助。

Edit4:我找到了带字符串的解决方案。我使用ostringstream的舍入并在之后转换为double。我可以有4个精确的正确数字。

std::string doubleToStr(double d, int precision)
{
    std::stringstream ss;
    ss << std::fixed << std::setprecision(precision) << d;
    return ss.str();
}

    double val945 = (double)0.575f;
    std::cout << doubleToStr(val945, 4) << std::endl;
    std::cout << doubleToStr(val945, 15) << std::endl;
    std::cout << atof(doubleToStr(val945, 4).c_str()) << std::endl;

结果是:

0.5750
0.574999988079071
0.575

3 个答案:

答案 0 :(得分:6)

让我们假设您的编译器完全针对floatdouble值和操作实现了IEEE 754 binary32和binary64。

首先,您必须了解0.6050f不代表数学数量6050/10000。它恰好是0.605000019073486328125,最接近float。即使你从那里写出完美的计算,你必须记住这些计算从0.605000019073486328125开始,而不是从0.6050开始。

其次,您可以通过double计算并在最后转换为float来解决几乎所有累积的总体问题:

$ cat t.c
#include <stdio.h>

int main(){
  printf("0.6050f is %.53f\n", 0.6050f);
  printf("%.53f\n", (float)((double)0.605f * 200. - 120.));
}

$ gcc t.c && ./a.out 
0.6050f is 0.60500001907348632812500000000000000000000000000000000
1.00000381469726562500000000000000000000000000000000000

在上面的代码中,所有计算和中间值都是双精度的。

如果你记得你从0.605000019073486328125开始而不是0.6050(它不作为1.0000038…存在),这个float是一个非常好的答案。

答案 1 :(得分:2)

如果您真的关心0.99999992和1.0之间的区别,float对您的应用程序来说不够精确。您至少需要更改为double

如果您需要特定范围内的答案,并且您获得的答案略微超出该范围但在其中一个目标的舍入误差范围内,请将答案替换为适当的范围结束。

答案 2 :(得分:0)

即使具有双精度,您也会遇到以下问题:

200. * .60499999999999992 = 120.99999999999997

看起来您需要某种类型的舍入,以便将0.99999992四舍五入为1.00000000。

如果目标是产生最接近的1/1000倍数的值,请尝试:

#include <math.h>

    val = (float) floor((200000.0f*val)-119999.5f)/1000.0f;

如果目标是产生最接近的1/200的倍数,请尝试:

    val = (float) floor((40000.0f*val)-23999.5f)/200.0f;

如果目标是将值生成为最接近的整数,请尝试:

    val = (float) floor((200.0f*val)-119.5f);