哪个更准确,x**.5还是math.sqrt(x)?

22

我最近发现在Python中,x ** 0.5math.sqrt(x)并不总是产生相同的结果:

Python 2.6.1 (r261:67517, Dec 4 2008, 16:51:00) [MSC v.1500 32 bit (Intel)]
on win32
>>> 8885558**.5 - math.sqrt(8885558)
-4.5474735088646412e-13

检查小于10**7的所有整数,这两种方法在近乎0.1%的样本中产生不同的结果,随着数字越来越大,误差的大小(缓慢地)增加。

因此问题是,哪种方法更准确?


你能解释一下“错误率”是什么意思吗? - Greg Hewgill
计算平方根的两种方法在小于10**7的10,103个数字上产生的结果并不完全相同。(约占0.1%) - Ben Blank
2
我明白了,这正是我猜测你可能意思的。你可能会发现,在两种计算方法之间存在差异时,差异将在浮点表示的最不重要的一位或可能是两位中出现。这被认为是正常的,并且是计算结果使用不同算法的后果。 - Greg Hewgill
3
迄今为止,浏览答案,没有人谈到每个算法的实际实现。如果有人知道,我会很感兴趣阅读相关内容。 - saffsd
1
@saffsd,很可能它们都使用类似于牛顿法的收敛迭代序列。如果它们选择不同的起始点,一个在x轴交点左侧,一个在x轴交点右侧,那么这可能解释了差异。 - Unknown
9个回答

26

两者都不更准确,它们都同样偏离实际答案:

>>> (8885558**0.5)**2
8885557.9999999981
>>> sqrt(8885558)**2
8885558.0000000019

>>> 2**1023.99999999999
1.7976931348498497e+308

>>> (sqrt(2**1023.99999999999))**2
1.7976931348498495e+308
>>> ((2**1023.99999999999)**0.5)**2
1.7976931348498499e+308

>>> ((2**1023.99999999999)**0.5)**2 - 2**1023.99999999999
1.9958403095347198e+292
>>> (sqrt(2**1023.99999999999))**2 - 2**1023.99999999999
-1.9958403095347198e+292

http://mail.python.org/pipermail/python-list/2003-November/238546.html

math模块包装了平台上与其同名的C数学库函数。如果需要(或者只是想要)与调用C的pow()扩展高度兼容,则math.pow()最有用。

__builtin__.pow()实现了Python中的中缀**操作符,还处理复数、无限整数幂和模幂运算等(C pow()不处理这些)。

**更为完整。math.sqrt可能只是sqrt的C实现,而其可能与pow有关。


9
sqrt 是IEEE 754定义的基本运算之一。应该直接实现,而不是调用 pow,并且作为一种基本运算,它应该具有正确的舍入(通常的 pow 实现在这方面远远不够准确)。 - Pascal Cuoq
5
以与幂/平方根相同的浮点精度计算(8885558 ** 0.5)sqrt(8885558)的平方,期望该计算可以说明哪个结果更准确是天真的。结论“它们都与实际答案偏离相等”是可笑的:它们在浮点数中计算出的平方与8885558.0每个的差异为1ULP。 - Pascal Cuoq
链接已移动:https://mail.python.org/pipermail/python-list/2003-November/205890.html - Antonis Christofides

11

pow函数和math.sqrt()函数都可以计算比默认浮点类型存储更精确的结果。我认为你看到的误差是由于浮点数学的限制而不是函数的不准确性造成的。此外,自从什么时候一个7位数字的平方根的差异约为10^(-13)成为问题了?即使是最精确的物理计算也很少需要那么多有效数字...

使用math.sqrt()的另一个原因是它更易于阅读和理解,这通常是采用某种方式的好理由。


这个问题的答案似乎与您关于速度的评论相矛盾:https://dev59.com/YXRC5IYBdhLWcg3wVvnL - Ben Blank
我的一个朋友今天早些时候实际上对此进行了基准测试,并得出结论,即math.sqrt()更快。这是在项目欧拉计算的背景下。我相信他的话而没有检查他的方法,所以那个断言可能是不正确的。尽管如此,似乎极不可能math.sqrt()会更慢。如果是这样的话,为什么他们不直接用pow()来实现呢? - Emil H
就 pow() 函数而言……我有点累了。 :) - Emil H
1
我承认我手头没有需要那种精度的案例 - 我更多地考虑最佳实践。实际上,我的主要关注点是Project Euler以及它向我抛出的越来越大的数字。;-) - Ben Blank
1
这是一个很好的理由。PE非常上瘾。:D 看一下decimal:http://docs.python.org/library/decimal.html 它提供了你所需要的全部精度。 - Emil H
显示剩余3条评论

7

使用 decimal 可以找到更精确的平方根:

>>> import decimal
>>> decimal.getcontext().prec = 60
>>> decimal.Decimal(8885558).sqrt()
Decimal("2980.86531061032678789963529280900544861029083861907705317042")

4

在编程语言中,当你需要在两个内置函数之间做出选择时,更具体的函数几乎总是等于或优于通用函数(因为如果更差,程序员就会使用通用函数来实现)。Sqrt比通用指数更具体,因此您可以期望它是更好的选择。至少在速度方面是这样的。就精度而言,您的数字没有足够的精度来判断。

注意:要澄清的是,在Python 3.0中sqrt更快。在旧版本的Python中则较慢。请参见J.F.Sebastians在Which is faster in Python: x**.5 or math.sqrt(x)? 的测量结果。


你能为我澄清一下最后那句话吗? - Ben Blank
你关于速度的观点是错误的。0.5实际上比sqrt更快。 - Unknown
@Unknown 我对此并不确定。import timeit; from random import random as unit; from math import sqrt; from operator import pow as pow_; assert timeit.timeit(lambda: sqrt(unit())) < timeit.timeit(lambda: pow_(unit(), 0.5)) 在 Python 3.9.10 上可靠地通过了我的测试。这个答案和它链接到的答案是正确的。(然而,值得注意的是 math 函数无法处理负数。) - JamesTheAwesomeDude

3

这似乎是某种平台特有的问题,因为我得到了不同的结果:

Python 2.5.1 (r251:54863, Jan 13 2009, 10:26:13) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
>>> 8885558**.5 - math.sqrt(8885558)
0.0

你使用的是哪个版本的Python和什么操作系统?
我猜测这可能与提升和强制类型转换有关。换句话说,由于你正在执行8885558**.5,因此必须将8885558提升为浮点数。所有这些都会根据操作系统、处理器和Python版本而有所不同。欢迎来到奇妙的浮点算术世界。 :-)

Python 2.6.1(r261:67517,2008年12月4日,16:51:00)[MSC v.1500 32位(Intel)]在win32上。 - Ben Blank
有趣。也许这是32位与64位的某种问题?我和另一个得到不同结果的人都使用64位操作系统。或者这可能是BSD的问题。我正在使用Mac OS X。 - Jason Baker
我在32位Linux上也得到了0.0(Python 2.5.2)。 - David Z

3
我在Win XP Python 2.5.1上遇到了和你一样的问题,但是在32位Gentoo Python 2.5.4上没有。这是C库实现的问题。
现在,在Win上,math.sqrt(8885558)**2会得到8885558.0000000019,而(8885558**.5)**2会得到8885557.9999999981,看起来它们差不多有相同的ε。
我认为我们不能真正说哪一个选项更好。

1

我没有得到同样的行为,也许错误是特定于平台的? 在amd64上,我得到了以下结果:

Python 2.5.2 (r252:60911, Mar 10 2008, 15:14:55)
[GCC 3.3.5 (propolice)] on openbsd4
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> math.sqrt(8885558) - (8885558**.5)
0.0
>>> (8885558**.5) - math.sqrt(8885558)
0.0

我担心这可能是这种情况 - 我在某个地方读到过math.sqrt直接传递给C实现,但我现在找不到参考资料了。 - Ben Blank
Ben:我找不到它的文档,但至少对于Python 2.5是正确的:http://tinyurl.com/omvudo - Ken

1

在理論上,math.sqrt 應該比 math.pow 更高精度。參見牛頓法求解平方根的方法[0]。然而,Python float(或 C double)的十進制位數限制可能會掩蓋這種差異。

[0] http://en.wikipedia.org/wiki/Integer_square_root


0

我进行了相同的测试并得到了相同的结果,10000000个数据中有10103个差异。这是在Windows下使用Python 2.7完成的。

差异在于四舍五入。我认为当两个结果不同时,它们之间只有一个ULP,这是浮点数可能存在的最小差异。真正的答案在两者之间,但float无法精确表示它,必须进行四舍五入。

如另一个答案所述,decimal模块可用于获得比float更高的精度。我利用它来更好地了解真实误差,在所有情况下,sqrt**0.5更接近。虽然差别不大!

>>> s1 = sqrt(8885558)
>>> s2 = 8885558**0.5
>>> s3 = decimal.Decimal(8885558).sqrt()
>>> s1, s2, s3
(2980.865310610327, 2980.8653106103266, Decimal('2980.865310610326787899635293'))
>>> s3 - decimal.Decimal(s1)
Decimal('-2.268290468226740188598632812E-13')
>>> s3 - decimal.Decimal(s2)
Decimal('2.2791830406379010009765625E-13')

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