双精度 - 小数位

时间:2012-04-03 18:31:36

标签: c++ c precision ieee-754

根据我的阅读,数据类型double的值具有小数点后15位的近似精度。但是,当我使用其十进制表示重复的数字,例如1.0 / 7.0时,我发现该变量的值为0.14285714285714285 - 这是17位(通过调试器)。

我想知道为什么它在内部表示为17个位置,为什么15的精度始终写在〜15?

7 个答案:

答案 0 :(得分:34)

IEEE double有53个有效位(DBL_MANT_DIG中的<cfloat>的值)。那是大约15.95个十进制数字(log10(2 53 ));实现将DBL_DIG设置为15而不是16,因为它必须向下舍入。因此,你有几乎十分精确的十进制数字(超出DBL_DIG==15暗示的那个数字)。

nextafter()函数计算给定数字的最近可表示数字;它可以用来显示给定数字的精确度。

这个程序:

#include <cstdio>
#include <cfloat>
#include <cmath>

int main() {
    double x = 1.0/7.0;
    printf("FLT_RADIX = %d\n", FLT_RADIX);
    printf("DBL_DIG = %d\n", DBL_DIG);
    printf("DBL_MANT_DIG = %d\n", DBL_MANT_DIG);
    printf("%.17g\n%.17g\n%.17g\n", nextafter(x, 0.0), x, nextafter(x, 1.0));
}

在我的系统上给出了这个输出:

FLT_RADIX = 2
DBL_DIG = 15
DBL_MANT_DIG = 53
0.14285714285714282
0.14285714285714285
0.14285714285714288

(您可以将%.17g替换为%.64g,以查看更多数字,但这些数字都不重要。)

如您所见,最后显示的十进制数字随每个连续值的变化而变化3。最后显示的1.0/7.05)数字恰好与数学值匹配的事实在很大程度上是巧合的;这是一个幸运的猜测。正确的舍入数字为6,而不是5。将1.0/7.0替换为1.0/3.0会得到以下结果:

FLT_RADIX = 2
DBL_DIG = 15
DBL_MANT_DIG = 53
0.33333333333333326
0.33333333333333331
0.33333333333333337

显示大约16个十进制数字的精度,正如您所期望的那样。

答案 1 :(得分:15)

它实际上是53个二进制位,转换为15个稳定的小数位,这意味着如果你用一个带有15个小数位的数字开始,将它转换为double,然后围绕{{ 1}}回到15位小数,你会得到相同的数字。要唯一地表示一个double,你需要17个小数位(意味着每个数字有17个小数位,有一个唯一的最接近的double)这就是为什么17个地方出现,但不是全部17个小数数字映射到不同的double值(如其他答案中的示例所示)。

答案 2 :(得分:12)

浮点数的十进制表示有点奇怪。如果你有一个带有15个小数位的数字并将其转换为double,那么将它打印出来的正好是15位小数,你应该得到相同的数字。另一方面,如果您打印出带有15个小数位的任意double并将其转换回double,则不一定会得到相同的值 - 您需要 17 小数位。 15位和17位小数都不足以准确显示任意double的精确十进制等值。一般来说,你需要超过100个小数位才能做到这一点。

请参阅the Wikipedia page for double-precision和此article on floating-point precision

答案 3 :(得分:7)

双精确地保存53个二进制数字,即〜15.9545898十进制数字。调试器可以显示尽可能多的数字,以便更准确地使用二进制值。或者它可能需要更少的数字和二进制,例如0.1在基数10中占1位数,但在基数2中占无限数。

这很奇怪,所以我将展示一个极端的例子。如果我们创建一个超级简单的浮点值,它只保留3个二进制数字的精度,并且没有尾数或符号(因此范围是0-0.875),我们的选项是:

binary - decimal
000    - 0.000
001    - 0.125
010    - 0.250
011    - 0.375
100    - 0.500
101    - 0.625
110    - 0.750
111    - 0.875

但是如果你做了数字,这种格式只能精确到0.903089987十进制数字。甚至1位数都不准确。很容易看到,因为没有以0.4??0.9??开头的值,但是为了显示完整的准确性,我们需要3个十进制数字。

tl; dr:调试器向您显示浮点变量的值到某个任意精度(在您的情况下为19位),这不一定与精度相关浮点格式(在您的情况下为17位)。

答案 4 :(得分:4)

IEEE 754浮点以二进制形式完成。从给定的位数到给定的小数位数没有精确的转换。 3位可以保存0到7之间的值,4位可以保存0到15之间的值.0到9之间的值大致 3.5位,但这也不准确。

IEEE 754双精度数占64位。其中,52位专用于有效数字(其余为符号位和指数)。由于有效数是(通常)归一化的,因此有一个隐含的53 rd 位。

现在,给定53位和每位大约3.5位,简单除法给出了15.1429位精度。但请记住,每个十进制数字3.5位只是一个近似值,而不是一个完全准确的答案。

许多(大多数?)调试器实际上是查看整个寄存器的内容。在x86上,这实际上是一个80位的数字。通常会调整x86浮点单元以执行64位精度的计算 - 但在内部,它实际上使用了几个“保护位”,这基本上意味着内部它使用一些额外的精度进行计算,因此它可以正确地绕过最后一个。当调试器查看整个寄存器时,它通常会找到至少一个相当准确的额外数字 - 尽管由于该数字没有任何保护位,因此可能无法正确舍入。

答案 5 :(得分:3)

这是因为它是从二进制表示转换而来的。仅仅因为它打印了所有那些十进制数字并不意味着它可以表示该精度的所有十进制值。以Python为例:

>>> 0.14285714285714285
0.14285714285714285
>>> 0.14285714285714286
0.14285714285714285

注意我是如何更改最后一位数字的,但无论如何都打印出相同的数字。

答案 6 :(得分:1)

在大多数使用double值的情况下,计算会产生一定的不确定性。 1.33333333333333300和1.33333333333333399之间的差异可能小于计算中存在的不确定量。将“2/3 + 2/3”的值显示为“1.33333333333333”比将其显示为“1.33333333333333319”更有意义,因为后一种显示意味着实际上不存在的精度级别。

然而,在调试器中,重要的是唯一地指示变量保持的值,包括基本上无意义的精度位。如果调试器显示两个变量为持有值“1.333333333333333”,当其中一个实际持有1.33333333333333319而另一个持有1.33333333333333294(意思是,虽然它们看起来相同,但它们不相等),这将是非常令人困惑的。调试器显示的额外精度不易于表示数值正确的计算结果,但表示代码将如何解释变量所持有的值。