打印Python和C++双精度浮点数时的精度差异

15

我现在对此感到惊叹:

C++ 11

#include <iostream>
#include <iomanip>
#include <limits>

int main()
{
  double d = 1.305195828773568;
  std::cout << std::setprecision(std::numeric_limits<double>::max_digits10) << d << std::endl;
  // Prints  1.3051958287735681
}

Python

>>> repr(1.305195828773568)
'1.305195828773568'

发生了什么事,为什么在C++中多出了1?

到目前为止,我认为C++和Python在底层使用相同的64位IEEE双精度浮点数;两种格式化函数都应该打印全精度。


1
请注意,repr 的属性是 eval(repr(x)) == x,而不是将数字打印为所有小数位。如果您想要 k 个小数位的精度,则应使用适当的格式化函数。 - Bakuriu
3个回答

10

你可以强制 Python 输出 1(以及后面许多数字):

print('{:.16f}'.format(1.305195828773568))
# -> 1.3051958287735681

摘自https://docs.python.org/2/tutorial/floatingpoint.html

>>> 7205759403792794 * 10**30 // 2**56
100000000000000005551115123125L
在Python 2.7和Python 3.1之前的版本中,Python会将该值四舍五入到17个有效数字,显示为“0.10000000000000001”。在当前版本中,Python基于能够正确舍入回真实二进制值的最短十进制分数来显示值,结果为简单的“0.1”。 “打印完整精度”很难做到:什么是完整精度?浮点数的表示是二进制的;只有2的幂的分数可以被准确地表示(以完整精度);大多数小数在基数为2时无法被准确地表示。 但是Python和C++中的内存中的浮点数是相同的;只是字符串表示不同。

@n0rd 正确。唯一的缺点是它可能会是一个相当长的表示,并且可能不代表实际输入,从中获取二进制浮点数。 - Eugene Ryabtsev
2
最接近IEEE 64位二进制浮点数的确切值为1.3051958287735681008001620284630917012691497802734375 - 绝对不代表实际输入的值。 - Patricia Shanahan
1
实际上,浮点数的精确十进制表示需要的位数最坏情况下与尾数长度加一相同,因此对于双精度浮点数来说需要53位数字。 - P-Gn
@Rick,你说得对。当指数为零时,53是最坏的情况。每个负指数增加一个。因此,在1e-20的情况下,您在二进制基数中得到一个-67的指数,因此应该是最多67 + 53 = 120位数字... 这与您的示例一致,实际上只有“仅”100位有效数字(从前导9开始)。 - P-Gn
@user1735003:谢谢,我(们)数错了。它有99个有效数字(和20个无效的前导0)。 - Rick Regan
显示剩余3条评论

3
当使用定点表示法时,precision()指定小数位数。由于您的示例中有一个额外的非小数位数,比可以安全地表示的多一个,因此会使用一个固定的表示法。
当使用科学计数法时,计算总位数并将得到与原始数字相同的数字(当然还有指数)。对于格式化浮点数,C和C ++选项实际上相当糟糕。特别是,没有选项让格式化程序决定适当的数字数量,虽然底层算法实际上可以确定这些数字数量。

2
这个问题的答案中获取:

IEEE 754浮点数在二进制中进行。从给定位数到给定十进制位数没有精确的转换。3位可以容纳0到7的值,4位可以容纳0到15的值。0到9的值大约需要3.5位,但这也不是精确的。

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

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

你提供的15位数字后面的奇怪的.1429可能是添加了1的罪魁祸首。

值得一提的是,Python 在其网站上写有:

历史上,Python 提示符和内置的repr()函数会选择具有17个有效数字的数字,即0.10000000000000001。从 Python 3.1 开始,Python(在大多数系统上)现在可以选择这些数字中最短的数字,并简单地显示0.1。


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