Python中的不准确对数

16

我在公司每天使用Python 2.4进行工作。我使用标准math库中的通用对数函数"log",当我输入log(2**31, 2)时,它返回了31.000000000000004,这让我感到有点奇怪。

我用其他2的幂运行了相同的操作,它完美地运行了。我运行了'log10(2**31) / log10(2)',得到了一个整数31.0。

我尝试在Python 3.0.1中运行相同的原始函数,认为这个问题会在更高级的版本中得到修复。

为什么会出现这种情况?是否可能在Python的数学函数中存在一些不准确性?


这是关于浮点数错误的经典问题的重复,但我找不到最好的重复问题来发布,或许其他人可以帮忙。 - Jason S
我应该指出的是,Python 3没有修复浮点错误。相反,打印输出使用智能算法显示预期的浮点值,而不是松散的值。 - Xavier Ho
9个回答

53

由于计算机算术遵循特定规则,例如IEEE 754,因此这是可以预期的。这些规则可能与您在学校学习的数学不同。

如果这真的很重要,请使用Python的十进制类型(decimal type)

示例:

from decimal import Decimal, Context
ctx = Context(prec=20)
two = Decimal(2)
ctx.divide(ctx.power(two, Decimal(31)).ln(ctx), two.ln(ctx))

28
感谢您的好回答,尤其是其中的"If this actually matters."这句话。甚至探测器飞往土星的精度都不如此。 - dwc
确实。斜体字是答案中最重要的部分。 - Matthew Flaschen
7
如果原文中的OP在log函数的结果上取ceil,那么错误会变得非常大。在我的一个程序中,我执行了这个操作:a = floor(log(x,b)),后面几行代码就会崩溃,因为floor(log(243,3))的结果是4。 - Rushil Paul

21

2
非常感谢。这是一个非常有用的参考资料。 - Avihu Turzion

18

始终假定浮点运算会存在一些误差,并且在检查等式时考虑到这种误差(可以使用百分比值,如0.00001%,也可以使用固定值,如0.00000000001)。由于不是所有十进制数字都能用具有固定精度的二进制表示,所以这种不准确性是确定的。

如果 Python 使用 IEEE754,则单精度甚至可以轻松表示31,因此您的特定情况并非它们中的一个。然而,在计算log2231的许多步骤之一中,它可能会失去精度,仅因为它没有代码来检测像直接的2的幂这样的特殊情况。


非常有趣。我写代码已经很长时间了,但这是我第一次遇到这种现象。但在这里得到的回应之后,我现在开始看到它发生的更大背景。 - Avihu Turzion

6

浮点运算永远不是精确的。它们返回一个相对误差可接受的结果,适用于语言/硬件基础架构。

一般来说,假设浮点运算是精确的是非常错误的,特别是在单精度下。可以参考维基百科浮点数文章中的“精度问题”部分 :)


3

IEEE双精度浮点数具有52位精度。由于10^15 < 2^52 < 10^16,因此双精度浮点数具有15到16个有效数字。结果31.000000000000004在16个有效数字范围内是正确的,因此它是可以接受的。


2

这是正常的。我希望log10比log(x, y)更准确,因为它知道对数的底数是什么,此外可能还有一些硬件支持计算以10为底数的对数。


2
浮点数是不精确的。
我不认同这个观点,因为在大多数平台上,精确的二次幂可以被准确地表示(使用底层IEEE 754浮点数)。
因此,如果我们真的希望一个精确的2的对数是准确的,我们就可以做到。
我将在Squeak Smalltalk中演示它,因为在该语言中更改基本系统很容易,但语言并不重要,浮点计算是通用的,Python对象模型与Smalltalk相差不远。
对于以n为底数取对数,Number中定义了log:函数,它天真地使用自然对数ln:
log: aNumber 
    "Answer the log base aNumber of the receiver."
    ^self ln / aNumber ln

self ln(对接收者取自然对数),aNumber ln/是三个将结果四舍五入到最近的Float的操作,这些舍入误差可能会累积...因此,天真的实现会受到您观察到的舍入误差的影响,我猜想Python的log函数实现也不会有太大的区别。

((2 raisedTo: 31) log: 2) = 31.000000000000004

但是如果我像这样改变定义:

log: aNumber 
    "Answer the log base aNumber of the receiver."
    aNumber = 2 ifTrue: [^self log2].
    ^self ln / aNumber ln

提供一个通用的 Number 类中的 log2:
log2
    "Answer the base-2 log of the receiver."
    ^self asFloat log2

还有 Float 类中的这个改进:

log2
    "Answer the base 2 logarithm of the receiver.
    Care to answer exact result for exact power of two."
    ^self significand ln / Ln2 + self exponent asFloat

Ln2是一个常数(2 ln),因此对于二的幂次方,我可以获得精确的log2,因为这种数字的有效数字=1.0(包括Squeak指数/有效数字定义的子规范),而1.0 ln = 0.0

实现非常简单,并且应该在Python中很容易转换(可能在VM中);运行时成本非常便宜,因此只是一个我们认为这个功能有多重要或不重要的问题。

正如我经常说的那样,浮点运算结果舍入为最接近的(或任何舍入方向)可表示值并不意味着我们可以浪费ulp。精确性具有成本,无论是运行时惩罚还是实现复杂性,都需要权衡。


1
在 Python 中,数字的 repr(即 float.__repr__)表示尝试在转换回来时返回尽可能接近实际值的数字字符串,因为 IEEE-754 算术精确到一定的限度。无论如何,如果您将结果 print 出来的话,您都不会注意到这一点:
>>> from math import log
>>> log(2**31,2)
31.000000000000004
>>> print log(2**31,2)
31.0

print 将其参数转换为字符串(在这种情况下,通过 float.__str__ 方法),该方法通过显示较少的数字来适应不精确性:

>>> log(1000000,2)
19.931568569324174
>>> print log(1000000,2)
19.9315685693
>>> 1.0/10
0.10000000000000001
>>> print 1.0/10
0.1

usuallyuseless的回答实际上非常有用 :)


0
如果您想计算数字'n'中'k'的最高次幂,那么下面的代码可能会有所帮助:
import math
answer = math.ceil(math.log(n,k))
while k**answer>n:
    answer-=1

注意:在编程中,不应该使用“if”代替“while”,因为这样会导致某些情况下得出错误的结果,例如当n=2**51-1且k=2时。在使用“if”的情况下,答案是51,而使用“while”则得到正确的答案50。


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