为什么无法从最大的双精度浮点数中减去1

3
#include <iostream>
#include <limits>

int main()
{   
    double d = std::numeric_limits<double>::max();
    std::cout << std::to_string(d) << std::endl;
    std::cout << std::to_string(d - 1) << std::endl;
}
[test@arch_host ~]$ g++ test.cpp 
[test@arch_host ~]$ ./a.out 
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000

为什么第二个数字没有以7结尾?


3
你需要学习浮点数表示法。它们不精确到最后一位数字。我相信维基百科有一个很好的起点。 - Gordon Linoff
2
这肯定是一个重复的。 - Puppy
阅读http://floating-point-gui.de/(并记住该URL) - Basile Starynkevitch
2个回答

6

double使用IEEE 754标准进行表示。与int不同,它没有一个固定的最小步长为1。随着数字变得越来越大,最小步长也会变得越来越大: enter image description here

你的数字太大,其步长比1大得多。为了简单起见,我们假设它是10。因此,如果您尝试减去1,则该值会四舍五入到下一个有效的double。这与之前相同。

或者换句话说:179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858367不是有效的double。


这张图应该是严重夸大了吧?如果我没记错的话,实际的截止点大约在10^23左右。 - Puppy
1
是的,这不准确。我试图简化问题。 - Crigges

2
在C++中,double类型通常采用二进制64位格式符合IEEE 754标准。因此,我们将基于此回答问题。其他浮点格式(例如float(二进制32位)、binary16)也是有效的,它们没有本地的C++类型,甚至包括非IEEE 754浮点格式。由于double由52位的尾数和11位的指数组成,其范围是“动态的”:
如果指数很大:尾数将表示整数,然后,在此之后,它将无法表示例如尾随数字2,因为尾数上的数字数量有限。
如果指数很小:尾数将表示越来越小的负2次幂(1/2、1/4等),并且它将更加精确。
至于问题本身:
当未指定IEEE 754定义的舍入模式之一时,默认值为“最近偶数舍入”,这正是它听起来的样子。
当您使用最大double值进行操作时,它与最后一个可表示的double之间的差距巨大。因此,通过减去1.0,代数上给出maxDouble-1.0,但在硬件中,它不可表示,因为指数太小(它将在第52位之后发生变化),因此您的FPU使用最近偶数舍入模式,并将其舍入到maxDouble。
要解决您的问题,可以使用两个解决方案。如果程序计算的值的范围不太大且计算速度不是太重要,则使用定点算术。或者使用CPU制造商提供的内部机制(通常在头文件中找到),将舍入模式设置为向0舍入或向下舍入。
哦,这里有一个简短的舍入模式列表:最近偶数舍入、向上舍入、向下舍入和向0舍入,它等同于向上或向下舍入,具体取决于操作的符号。
如果您要在浮点算术中使用如此高的值,您应该定期检查您的数字是否已饱和为∞或-∞,因为那样您将无法对它们进行操作。

C++并不要求double必须符合二进制64位IEEE 745标准,尽管这是目前最常见的实现方式。 - Pete Becker
虽然标准并不要求,但几乎所有的CPU都有本地支持它们(除了一些嵌入式系统),所以很容易混淆。我很抱歉。 - Sachiko.Shinozaki
相关问题:https://dev59.com/o1sX5IYBdhLWcg3wDb2L - Ignacio Vazquez-Abrams
只有IEEE 754,没有IEEE 745。 - Crigges
一个打错字了,不好意思。 - Sachiko.Shinozaki

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