浮点数和由于舍入行为导致的不正确结果

Float point numbers and incorrect result due to rounding behavior

本文关键字:结果 不正确 于舍入 舍入 浮点数      更新时间:2023-10-16

我需要输出小数点后有两位数字的浮点数。此外,我还需要把数字四舍五入。然而,有时我得不到我想要的结果。下面是一个例子:

#include <iomanip>
#include <iostream>
using namespace std;
int main(){
    cout << setprecision(2);
    cout << fixed;
    cout<<(1.7/20)<<endl;
    cout<<(1.1/20)<<endl;
}

结果如下:

0.08
0.06

,因为1.7/20=0.085和1.1/20=0.055。理论上我应该得到0.09和0.06。我知道它和浮点数的二进制表达式有关。我的问题是,我怎么能得到正确的结果时,固定小数点后的数字与四舍五入?

编辑:这不是一个重复的问题。使用fesetround(FE_UPWARD)并不能解决问题。fesetround(FE_UPWARD)将(1.0/30)四舍五入到0.04,而正确的结果应该是0.03。此外,fesetround(FE_TONEAREST)也没有帮助。(1.7/20)仍接近0.08。

编辑:现在我明白这种行为可能是由于一半到偶数的四舍五入。但是我怎样才能避免这种情况呢?也就是说,如果结果正好是一半,它应该四舍五入。

是的,你是对的-它与以2为基数的表示有关,事实上,以2为基数的值有时会高于以10为基数的数字,有时会低于。但从来没有多过!

如果您想要更频繁地匹配期望,您可以进行两阶段舍入。double一般精确到至少15位数字(包括小数点左边的数字)。第一次四舍五入会让你得到一个在第二轮四舍五入中更稳定的数字。没有四舍五入会匹配你在十进制中得到的100%的结果,但有可能非常接近。

double round_2digits(double d)
{
    double intermediate = floor(d * 100000000000000.0 + 0.5); // round to 14 digits
    return floor(intermediate / 1000000000000.0 + 0.5) / 100.0;
}

看到它在行动。


对于一种完全不同的方法,您可以简单地确保以2为基数的数字总是大于所需的小数,而不是在一半的时间里大于一半的时间里小于一半的时间。只需在舍入之前用nextafter增加数字的最低有效位。

double round_2digits(double d)
{
    return floor(100.0 * std::nextafter(d, std::numeric_limits<double>::max())) / 100.0;
}

您可以自己定义round_with_precision()方法,如果round()方法传递修改后的值,然后除以相同的因子返回值,则调用tgmath.h

#include <tgmath.h> 
double round_with_precision(double d, const size_t &prec)
{
    d *= pow(10, prec);
    return (std::round(d) / pow(10, prec));
}
int main(){
    const size_t prec = 2;
    cout << round_with_precision(1.7/20, prec) << endl;  //prints 0.09
    cout << round_with_precision(1.1/20, prec) << endl;  //prints 0.06
}

这个问题是由于c中的二进制浮点表示和浮点常量造成的。事实上,1.7和1.1不能精确地用二进制表示。ISO C标准规定(我想这在c++中也是类似的):"浮动常量被转换为内部格式,就像在翻译时一样。"这意味着活动舍入模式(由fesetround设置)将对常量完全没有任何影响(它可能对运行时发生的舍入有影响)。

除以20会产生另一个舍入误差。根据完整的代码和编译器选项,它可能在编译时执行,也可能不执行,因此可以忽略活动的舍入模式。在任何情况下,如果您期望精确地0.085和0.055,这是不可能的,因为这些值不能精确地用二进制表示。

因此,即使您有完美的代码,可以将double值四舍五入为2位十进制数字,这可能无法像您想要的那样工作,因为之前发生了四舍五入错误,并且以一种在所有情况下都有效的方式恢复信息为时已晚。

如果你想处理"中点"对于像0.085这样精确的值,您需要使用能够精确表示它们的数字系统,例如十进制算术(但在其他类型的操作中仍可能出现舍入错误)。您可能还想使用以10为幂的整数。没有通用的答案,因为这实际上取决于应用程序,因为任何变通方法都会有缺点。

有关更多信息,请参阅有关浮点数的所有一般文章和Goldberg的文章(PDF版本)。