浮点舍入误差

On a float rounding error

本文关键字:舍入误差      更新时间:2023-10-16

我不理解以下程序的输出:

int main()
{
    float  x     = 14.567729f;
    float  sqr   = x * x;
    float  diff1 = sqr - x * x;
    double diff2 = double(sqr) - double(x) * double(x);
    std::cout << diff1 << std::endl;
    std::cout << diff2 << std::endl;
    return 0;
}

输出:

6.63225e-006
6.63225e-006

我使用的是VS2010,x86编译器

我希望得到不同的输出

0
6.63225e-006

为什么diff1不等于0?为了计算sqr - x * x,编译器将浮点精度提高到两倍。为什么?

浮点寄存器为80位(在大多数现代CPU上)

在表达式中,结果是一个80位的值。只有当它被分配到内存中的某个位置时,它才会被截断为32(浮点)或64(双精度)。如果您将所有内容都保存在寄存器中(尝试使用-O3进行编译),您可能会看到不同的结果。

编译者:-03:

> ./a.out
0
6.63225e-06
float  diff1 = sqr - x * x;
double diff2 = double(sqr) - double(x) * double(x);

为什么diff1不等于0?

因为您已经缓存了sqr = x*x并强制其表示为float

为了计算sqr-x*x编译器将浮点精度提高到两倍。为什么?

因为在C标准出现之前,C就是这样做的。我认为现代编译器不受这种约定的约束,但许多编译器仍然遵循这种约定。如果是这样的话,diff1diff2的计算右侧将是相同的。唯一的区别是,在计算了float diff1 = ...的右侧之后,二重结果被转换回浮点值。

显然,标准允许在这样的表达式中自动将浮点提升为双精度。参见此处

在该页面上查找"自动升级",并查看第一段中的短语。

根据我的理解,如果我们按照这段话,你的sqr=x*x最初也被视为一个二重,但一旦它被存储,它就会被四舍五入为浮点。然后,在diff1=sqr-x*x中,x*x再次被视为二重,sqr也是如此,尽管它已经四舍五入了。因此,它产生的结果与将它们全部铸造为双精度相同:sqr是双精度,但已经四舍五入到浮点精度,x*x也是双精度。

在x86/x64体系结构上,编译器通常会将所有32位浮点运算提升为64位双精度运算;检查输出组件,看看这两种变体是否产生相同的指令。这两种类型之间的唯一区别是存储