将双精度浮点数加到无符号64位整数上会产生奇怪的结果。

3
int main(int argc, char *argv[])
{
    uint64_t length = 0x4f56aa5d4b2d8a80;
    uint64_t new_length = 0;

    new_length = length + 119.000000;

    printf("new length  0x%"PRIx64"\n",new_length);

    new_length = length + 238.000000;

    printf("new length  0x%"PRIx64"\n",new_length);

    return 0;
}

使用上述代码,我将两个不同的双精度值添加到一个无符号64位整数中。在这两种情况下,我得到了完全相同的结果。程序的输出如下所示:

$./a.out
new length  0x4f56aa5d4b2d8c00
new length  0x4f56aa5d4b2d8c00

我本来期望得到两个不同的结果,但事实并非如此。我也尝试将 uint64_t 值强制转换为 double,代码如下:

new_length = (double)length + 119.000000;

但这似乎也没有帮助。有什么想法,可能是问题出在哪里?

可能是为什么这是真的?的重复问题。 - In silico
2个回答

7

由于你添加了一个浮点操作数,所以两个操作数会被隐式转换为双精度浮点类型,并且使用浮点运算进行加法计算。

然而,double类型没有足够的精度来准确保存以下任一值:

0x4f56aa5d4b2d8a80 + 119.0  (requires 63 bits of precision)

0100111101010110101010100101110101001011001011011000101011110111
 <-------------------63 bits of precision---------------------->


0x4f56aa5d4b2d8a80 + 238.0  (requires 62 bits of precision)

0100111101010110101010100101110101001011001011011000101101101110
 <-------------------62 bits of precision--------------------->

标准的IEEE双精度仅具有53位有效数字

结果是它们都会舍入为同一个最终值:

0x4f56aa5d4b2d8c00  (53 bits of precision)

0100111101010110101010100101110101001011001011011000110000000000
 <-----------------53 bits of precision-------------->

如果你想避免这种舍入,你应该通过将操作数转换为整数来完全避免浮点运算。(或者直接使用 119238


3

浮点运算不是精确的。随着数字变得越来越大,低位数字的准确性会降低。

0x4f56aa5d4b2d8a80 是一个非常大的数字。

在发生什么?

new_length = length + 119.000000;

这里的length + 119.000000被强制转换为double类型,以进行加法运算。由于数值非常大,因此该double类型会被截断,然后再次强制转换为整型uint64_t并赋值给new_length

当您调用时:

new_length = length + 238.000000; 

有时候四舍五入的结果会变成相同的数值。

你真正想要做的是:

new_length = length + (uint64_t)238.0; 

这将给你想要的答案。它会将double类型初始转换为整数类型,然后精确地相加。


浮点数精确的。您可以一遍又一遍地重复相同的实验并获得相同的结果。这些结果可能与您对实数的经验预期不符,但它们肯定是精确的。 - Stephen Canon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接