c ++ float减法舍入误差

Vol*_*maz 0 c++ floating-point

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

    float val = 0.6050f;
    val *= 200.f;
Run Code Online (Sandbox Code Playgroud)

现在val是我预期的121.0.

    val -= 120.0f;    
Run Code Online (Sandbox Code Playgroud)

现在val是0.99999992

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

    float val = 0.6050f;
    val *= 200.f;
    val *= 100.f;
    val -= 12000.0f;    
    val /= 100.f;
Run Code Online (Sandbox Code Playgroud)

但它没有帮助.我手上还有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;
}
Run Code Online (Sandbox Code Playgroud)

结果是0.574999988079071

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

编辑3:其实我用双倍尝试了所有这些.这是我的榜样.

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;
}
Run Code Online (Sandbox Code Playgroud)

结果是:

0.575000000000000
0.577500000000000
0.580000000000000
0.582500000000000
Run Code Online (Sandbox Code Playgroud)

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

感谢大家的帮助.

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;
Run Code Online (Sandbox Code Playgroud)

结果是:

0.5750
0.574999988079071
0.575
Run Code Online (Sandbox Code Playgroud)

Pas*_*uoq 6

让我们假设您的编译器完全实现IEEE 754 binary32和binary64 float以及double值和操作.

首先,你必须明白,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
Run Code Online (Sandbox Code Playgroud)

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

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

  • Downvoter,任何与downvote一起评论的评论?上面的任何事实错误? (3认同)
  • @VolkanOzyilmaz那很棒.我的专长是-1分的答案,实际上帮助了提问者. (2认同)
  • @VolkanOzyilmaz从`float`到`double`的转换通常是无损的("通常"意思是"在与问题相同的假设下").`0.575f`已经是一个实际接近0.574999988079071的值.你不能通过将其转换为"double"来恢复**信息,因为信息已经丢失. (2认同)