为什么 str() 函数会将浮点数四舍五入?

4

内置的Python str()函数在传入许多小数位数的浮点数时会输出一些奇怪的结果。具体如下:

>>> str(19.9999999999999999)
>>> '20.0'

我希望能获得:

>>> '19.9999999999999999'

有人知道为什么吗?也许有解决方法吗?

谢谢!

4个回答

8

不是 str() 函数进行了四舍五入,而是你在首次使用浮点数时就存在精度限制;换句话说,它们是有意设计成不精确的。这适用于所有编程语言。要了解更多关于浮点数的细节,请阅读 "What Every Programmer Should Know About Floating-Point Arithmetic"。

如果你想存储和操作精确数字,请使用 decimal 模块:

>>> from decimal import Decimal
>>> str(Decimal('19.9999999999999999'))
'19.9999999999999999'

-1 Decimal 也有限制精度(默认为28个小数位)。它并不是万能的解决方案。请查看我的更新答案。 - John Machin
没错,十进制类型只能存储精确数字,对它们进行操作将会截断它们。 - intgr
str也会四舍五入(在REPL中比较0.9999999999999999str(0.9999999999999999))。这可以通过使用repr来避免(尽管它无法解决由于浮点数不精确而导致的四舍五入问题)。 - Nathaniel Verhaaren

6
一个浮点数在C语言中有32位。其中一位用于符号,几位用于尾数,几位用于指数。你不能将每个十进制小数的无限位数都放入32位中。因此,浮点数非常依赖于四舍五入。
如果你尝试使用str(19.998),它可能会给出至少接近于19.998的值,因为32位具有足够的精度来估计它,但像19.999999999999999这样的值过于精确,无法用32位来估算,因此会四舍五入到最接近的可能值,这恰好是20。

т«їтЁеТГБуА«№╝їтЈфТў»PythonуџёfloatТў»C double№╝їжђџтИИСИ║64СйЇсђѓ - zwol

3
请注意,这是一个关于理解浮点数(固定长度)的问题。大多数编程语言都与Python做得完全一样(或非常相似)。
Python的floatIEEE 754 64位二进制浮点数。它仅限于53位精度,即略少于16位十进制精度。19.9999999999999999包含18个十进制数字;它无法准确地表示为floatfloat("19.9999999999999999")产生最接近的浮点值,恰好与float("20.0")相同。
>>> float("19.9999999999999999") == float("20.0")
True

如果你说“许多小数位”指的是“小数点后面的许多数字”,请注意,当小数点前面有许多小数位时,同样会出现“奇怪”的结果:

>>> float("199999999999999999")
2e+17

如果你想要完整的 float 精度,不要使用 str(),而是使用 repr():

>>> x = 1. / 3.
>>> str(x)
'0.333333333333'
>>> str(x).count('3')
12
>>> repr(x)
'0.3333333333333333'
>>> repr(x).count('3')
16
>>>

更新 有趣的是,人们经常将decimal描述为解决浮点数问题的灵丹妙药。这通常伴随着简单的例子,比如0.1 + 0.1 + 0.1 != 0.3。但是没有人停下来指出decimal也有其自身的缺陷。

>>> (1.0 / 3.0) * 3.0
1.0
>>> (Decimal('1.0') / Decimal('3.0'))  * Decimal('3.0')
Decimal('0.9999999999999999999999999999')
>>>

确实,float 只有53位二进制数字的精度。默认情况下,decimal 的精度被限制在28个十进制数字。

>>> Decimal(2) / Decimal(3)
Decimal('0.6666666666666666666666666667')
>>>

你可以更改限制,但仍然有有限的精度。如果要有效地使用它而不产生“惊人”的结果,你仍需要了解数字格式的特性,而额外的精度是通过更慢的操作购买的(除非你使用第三方cdecimal模块)。


1
针对任何给定的二进制浮点数,存在一个无限集合的小数分数,当输入时会舍入为该数字。Python 的 str 会尽可能地从这个集合中生成 最短 的十进制小数;有关通用算法,请参阅 GLS 的论文 http://kurtstephens.com/files/p372-steele.pdf(如果我没有记错,它们使用了一种避免在大多数情况下使用任意精度数学的优化)。你恰好输入了一种舍入为 IEEE 双精度浮点数的小数分数,其最短可能的十进制小数与你输入的不同。

2
“分数”并不相关;它也会发生在非常大的整数上。是repr()生成了最短的表示形式,而不是str(),而且这只是最近的事情;repr()曾经使用17个十进制数字的精度。 - John Machin

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