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

wbgh16ku  于 2022-12-05  发布在  其他
关注(0)|答案(3)|浏览(125)

我需要输出小数点后两位的浮点数。另外,我还需要对数字进行四舍五入。但是,有时我得不到我需要的结果。下面是一个例子。

#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。
编辑:现在我明白了,这种行为可能是由于二分之一到偶数的舍入。但我如何才能避免这种情况呢?也就是说,如果结果是精确的一半,它应该四舍五入。

fcy6dtqo

fcy6dtqo1#

是的,你是对的--这与以2为基数的表示有关,而且有时以2为基数的值会比以10为基数的值高,有时会比以10为基数的值低,但绝不会高很多!
如果你想得到更符合预期的结果,你可以做两个阶段的舍入。(总计,包括小数点左边的数字)。第一次舍入将为第二阶段的舍入留下更稳定的数字。任何舍入都不会与十进制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;
}

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

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

x8diyxa72#

您可以定义own的**round_with_precision()方法,该方法将调用 tgmath.h 提供的round()**方法传递修改后的值,然后返回除以相同因子后的值。

#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
}
mrzz3bfm

mrzz3bfm3#

这个问题是由于C语言中的二进制浮点表示和浮点常量造成的。事实上,1.7和1.1并不能精确地用二进制表示。ISO C标准说(我想这在C++中也是类似的):“浮点常量被转换为内部格式,就像在转换时一样。”这意味着活动的舍入模式(由fesetround设置)对常量没有任何影响(它可能对运行时发生的舍入有影响)。
除以20将引入另一个舍入错误。根据完整的代码和编译器选项,它可能在编译时完成,也可能不在编译时完成,因此活动的舍入模式可能被忽略。在任何情况下,如果您期望精确的0.085和0.055,这是不可能的,因为这些值不能精确地用二进制表示。
因此,即使您有完美的代码将double值四舍五入为2个十进制数字,这也可能无法按您希望的方式工作,因为之前发生过舍入错误,而且要以在所有情况下都有效的方式恢复信息也为时已晚。
如果希望能够精确处理“中点”值(如0.085),则需要使用可以精确表示它们的数字系统,如十进制算术(但在其他类型的运算中仍可能会出现舍入误差)。您可能还希望使用按10的幂缩放的整数。没有通用的答案,因为这实际上取决于应用程序,因为任何解决方法都有缺点。
有关更多信息,请参阅所有关于浮点和Goldberg's articlePDF version)的一般性文章。

相关问题