浮点数

11
我在阅读一本名为《Learning Python Fourth Edition》的PDF教程,现在我遇到了一个问题,因为我在Python方面基本上是初学者。我指的是这部分内容:enter image description here 现在我不理解第一个示例的解释。它确实说: It turns out that there are two ways to print every object: with full precision(as in the first result shown here), 但这个“with full precision”是什么意思呢?它可能在文本中为Python程序员提供非常简单的解释,但我似乎不太明白。

3
请参阅 Python 文档中的 浮点数算术:“在运行 Python 的典型机器上,Python 浮点数可用 53 位精度”。 - Jörg Mäder
2
了解“精确度”和“准确度”之间的区别是有帮助的。IEEE浮点数不能准确地表示每个数字,但您可以精确地打印出它们所代表的内容。 - Wooble
你能否评论一下我的新答案?你想要针对你的悬赏问题得到什么具体的回答? - Antti Haapala -- Слава Україні
@AnttiHaapala,我并不需要得到具体的答案。我只是想在这个问题上设置赏金,以便引起更多人对这个问题的关注。 - Loko
7个回答

14

这不是Python的问题,而是浮点数本质上的问题。事实证明,计算机在表示数字方面表现得很差。谁知道呢?

如果您有时间,我建议阅读《计算机科学家应该了解的浮点运算知识》

现在,关于Python的实际问题,每个对象都有一个名为__str__和一个名为__repr__的方法。这些方法应该产生要在各种情况下显示的字符串。如果您在任何对象上使用内置的reprstr函数,或者在字符串格式化中使用"%r""%s"格式,您将看到这些字符串。当您在交互式提示符下评估某些内容时,默认会获得repr。当您将某些内容传递给print时,默认会获得str

浮点数对象的__repr__被定义为以最大精度(至少在十进制下)表示它们,而它们的__str__被定义为以更接近用户所需的形式显示。用户不想知道浮点数不是实数,因此不会向他们显示额外的精度。


4
“str”和“repr”的区别以及“完全精度”是什么意思的答案,取决于Python版本。

repr(f)的行为在3.1和2.7中发生了变化。

  • 在2.7之前(包括Python 3.0),repr(f)会给出最多17个有效数字,就像使用%17g格式化一样。 IEEE-754浮点值具有53个有效二进制位,大约相当于16个十进制位。 17个有效数字保证每个二进制值产生不同的十进制值。

  • 在Python 2.73.1中,repr(f)变得更加人性化,同时保持精度:

float类型的xrepr()在很多情况下更短:它现在基于最短的十进制字符串,该字符串保证四舍五入后回到x。与Python之前的版本一样,保证float(repr(x))可以恢复x


str(f)在Python 3.2中的行为发生了变化:

  • 在2.x,3.0和3.1中:str(f)会给出十进制值,舍入到仅有12个有效数字,就像使用%12g格式化一样;精度由Objects/floatobject.h中的PyFloat_STR_PRECISION宏控制。

  • 在3.2+中,str(f)的行为与repr(f)完全相同——自从3.1以来,repr输出更加人性化,而且由于str(f)失去了精度,因此决定从Python 3.2开始,str(f)应该与repr(f)相同


以下示例演示了repr行为的更改。旧行为如下:
Python 2.6.8 (unknown, Jan 26 2013, 14:35:25) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 3.1415 * 2
6.2830000000000004
>>> 

而新行为是:

Python 2.7.3 (default, Mar 13 2014, 11:03:55) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 3.1415 * 2
6.283

在 Python 3.2 之前,str 的旧行为是将值四舍五入到 12 个有效数字,导致信息丢失:
Python 2.7.3 (default, Mar 13 2014, 11:03:55) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> str(0.1000000000000999)
'0.1'
>>> 0.1 == 0.1000000000000999
False
>>> repr(0.1000000000000999)
'0.1000000000000999'

自 Python 3.2 起的新行为是像 repr 一样运作:
Python 3.2.3 (default, Feb 20 2013, 14:44:27) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> str(0.1000000000000999)
'0.1000000000000999'
>>> repr(0.1000000000000999)
'0.1000000000000999'

浮点数在Python中以IEEE-754双精度形式表示,其中一个数字占用64位,其中1位为符号,10位为指数,53位为尾数(实际数字),这就是四舍五入的原因。
许多值,如π或1/3无法准确地表示为IEEE-754二进制浮点值。即使是像0.01这样常见的数字也不能完全表示。
Python 3的float类型具有hex()方法,可以将数字转换为十六进制表示,这可以用于轻松查看问题。
>>> (0.01).hex()
'0x1.47ae147ae147bp-7'

因此,作为十六进制,数字0.01在二进制中的近似值为1.47AE147AE147A4147AE... · 2-7;在53位有效数字中四舍五入得到最接近的数字,表示为1.47AE147AE147B · 2-7

我在回答当f是浮点数时,repr(f),str(f),print(f)的精度问题时,详细说明了repr在2.7和3.1中的工作原理。


3
基本上,计算机的浮点运算会存在舍入误差。因此,如果您执行 1.*1000./1000.,可能会得到 1.0000004 或类似的结果。这是计算机存储在内存中的内容。但是,你可能不想看到那个计算结果显示为 1.0000004。所以当你打印结果时,计算机会进行四舍五入,并显示简单的 1。但您必须知道,这不是计算机内存中实际的数值,而是您实际浮点数的一种舒适的可视化。

2

你正在阅读的书籍不够精确。如果你真的想要看到一个浮点数的完整精度,请使用decimal模块:

>>> import decimal
>>> decimal.Decimal(3.1415 * 2)
Decimal('6.28300000000000036237679523765109479427337646484375')

每个(有限的)二进制浮点数都可以准确地表示为(有限的)十进制浮点数。反之不成立 - 事实上,大多数十进制浮点数不能被准确地表示为(有限的)二进制浮点数。
对于旧版本的CPython,repr(a_float)生成了17位有效数字的十进制数。虽然证明这一点很困难,但事实证明,17位有效数字足以使IEEE-754“双精度”二进制浮点数(现在几乎所有机器都使用)实现的浮点数始终满足eval(repr(a_float)) == a_float,而16位有效数字则不够。17位有效数字不是“完全精度”,而是“足够精度,以便轮换总是起作用”。
在当前版本的CPython中,repr(a_float)生成最短的十进制字符串,使得eval(repr(a_float)) == a_float。这要困难得多。对于“随机”的浮点数,它可能仍然会产生17位小数,但对于人们倾向于手动输入的“简单浮点数”,它可能会产生与您输入的相同的字符串。

你读过那本书吗?我在考虑要不要读另一版,但是听说这本书质量很差? - Loko

2

全精度是指数字存储时保留所有小数位。由于计算机以二进制方式存储数字,这通常不会是100%准确的。


是的,因为我们使用十进制系统而计算机使用二进制系统。在一个系统中具有有限小数位数的浮点数,在另一个系统中可能具有无限数量的小数位数。 - Jörg Mäder
是的,这是正确的。这意味着,当使用固定位数表示十进制浮点值时,通常会得到与原始值不完全匹配的结果。这代表了一些“信息损失”,当您要求Python再次以十进制形式打印数字时,它将显示为与原始值的差异。 - Peter
谢谢您的回答,但我认为leeladam的回答解释得更好。 - Loko
好的,我现在还有一个问题。这个:print(0.1+0.1+0.1-0.3) 给我的结果是5.55111512313e-17,这是同样的东西吗? - Loko
1
是的,同样的事情。如果你执行print(0.1 + 0.1 + 0.1),你会发现它是0.300000....4。所以当你减去0.3时,你得到一个非常接近于零但不完全为零的小数。 - M4rtini
显示剩余2条评论

1
这可能是一个相当令人困惑的问题!数学上,3.1415 * 2 = 6.283,但在浮点运算中,由于四舍五入而引入了小误差。大多数显示此类计算结果的系统会自动纠正这一点,并给出您期望的结果。在Python中,当您print一个数字时,就会发生这种情况。另一方面,repr则按原样显示它,包括微小的误差。通常,误差非常小,不值得担心,但如果您在高精度环境中工作,则可能更喜欢避免误差的decimal模块。

0
Python类应该定义两个特殊方法:__repr____str__。 当您想要对象本身的“精确”表示时,将调用第一个方法。这意味着如果您复制/粘贴__repr__的输出,您将获得完全相同的对象。但是,这种表示并不总是人类可读的(特别是在处理更复杂的对象时),因此有__str__。它的功能是提供对象值的文本表示,这是人类友好的。

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