Python中的指数运算:x**y与math.pow(x, y)的区别

92

使用 math.pow 还是 ** 运算符更有效率?我应该在什么情况下使用其中之一?

目前为止,我知道 x**y 可以返回一个整数或浮点数(如果使用小数)。而函数 pow 则会返回一个浮点数。

import math

print( math.pow(10, 2) )

print( 10. ** 2 )

7
为什么不使用timeit来找出答案呢? - mgilson
4
"timeit" 显示在所有情况下,math.pow()** 的速度慢。那么 math.pow() 到底有什么用呢?有人知道它在哪些情况下可能有优势吗? - Alfe
1
@Alfe,你是怎么测量的?还是我误解了你的“在所有情况下”?我看到有些情况下math.pow更快。 - Wolf
1
@Wolf,你能行吗?你的链接指向了powmath.pow的比较,但我想要比较math.pow**操作符。你的比较可以加上第三个版本,然后再次使用**,这样就能打败其他所有选项:import timeit; print timeit.timeit("math.pow(2, 100)",setup='import math'), timeit.timeit("pow(2, 100)"), timeit.timeit("2 ** 100")0.170357942581 1.00546097755 0.013473033905 - Alfe
@Alfe WOW 感谢您指出这个细节。除了参考源代码之外,是否有简单的解释呢?也许这是一个很好的在SO上提问的问题;-)? - Wolf
显示剩余4条评论
7个回答

141

使用幂运算符**将更快,因为它不需要函数调用的开销。如果您反汇编Python代码,您可以看到这一点:

>>> dis.dis('7. ** i')
  1           0 LOAD_CONST               0 (7.0) 
              3 LOAD_NAME                0 (i) 
              6 BINARY_POWER         
              7 RETURN_VALUE         
>>> dis.dis('pow(7., i)')
  1           0 LOAD_NAME                0 (pow) 
              3 LOAD_CONST               0 (7.0) 
              6 LOAD_NAME                1 (i) 
              9 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             12 RETURN_VALUE         
>>> dis.dis('math.pow(7, i)')
  1           0 LOAD_NAME                0 (math) 
              3 LOAD_ATTR                1 (pow) 
              6 LOAD_CONST               0 (7) 
              9 LOAD_NAME                2 (i) 
             12 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             15 RETURN_VALUE         

请注意,我在这里使用变量i作为指数,因为像7. ** 5这样的常量表达式实际上是在编译时计算的。

现实中,这种差异并不太重要,您可以在计时时看到:

>>> from timeit import timeit
>>> timeit('7. ** i', setup='i = 5')
0.2894785532627111
>>> timeit('pow(7., i)', setup='i = 5')
0.41218495570683444
>>> timeit('math.pow(7, i)', setup='import math; i = 5')
0.5655053168791255
因此,尽管powmath.pow的速度要慢大约两倍,但它们仍然足够快,不需要太在意。除非您确实可以将指数运算识别为瓶颈,否则如果清晰度降低,选择一种方法而不是另一种方法就没有理由。这尤其适用于pow为例提供了一个内置的模操作。

Alfe在上面的评论中提出了一个很好的问题:

timeit显示,在所有情况下,math.pow都比**慢。那么math.pow()到底有什么用呢?有人有什么想法吗?

math.pow与内置的pow和幂运算符**最大的区别是它总是使用浮点语义。因此,如果因为某种原因,您想确保得到一个浮点数作为结果,则math.pow将确保这个属性。

让我们考虑一个例子:我们有两个数字,并且不知道它们是浮点数还是整数。但是我们想要一个浮点结果j^j。那么我们有什么选择?

  • 我们可以将至少一个参数转换为浮点数,然后执行i ** j
  • 我们可以执行i ** j并将结果转换为浮点数(当ij是浮点数时,自动使用浮点指数,因此结果相同)。
  • 我们可以使用math.pow

因此,让我们进行测试:

>>> timeit('float(i) ** j', setup='i, j = 7, 5')
0.7610865891750791
>>> timeit('i ** float(j)', setup='i, j = 7, 5')
0.7930400942188385
>>> timeit('float(i ** j)', setup='i, j = 7, 5')
0.8946636625872202
>>> timeit('math.pow(i, j)', setup='import math; i, j = 7, 5')
0.5699394063529439

如您所见,math.pow 实际上更快!而且如果您考虑一下,函数调用的开销也消失了,因为在所有其他替代方案中我们都需要调用 float()


另外值得注意的是,**pow 的行为可以通过为自定义类型实现特殊的 __pow__(和 __rpow__)方法来重载。因此,如果您不希望这样做(无论出于何种原因),使用 math.pow 将不会实现这一点。


你因为“**和pow的行为可以被覆盖”而获得了+1分:如果编写模块,防止来自用户环境的意外发生非常重要。 - Xerix

35

pow()函数允许您添加第三个参数作为模数。

例如:最近我在执行

2**23375247598357347582 % 23375247598357347583

时遇到了内存错误,所以我使用:

pow(2, 23375247598357347582, 23375247598357347583)

这只需几毫秒就能返回结果,而不是普通指数需要的大量时间和内存。因此,在处理大数和并行模数时,pow()更有效率,但在处理没有模数的小数时, ** 更有效率。


1
这个问题并不是在询问内置的 pow 函数。它在询问 math.pow(x, y)x ** y 之间的区别。 - wim
这是一个关于编程的相关内容,链接为https://dev59.com/tWYq5IYBdhLWcg3w30Tc。它与此处相关,并已在上面的评论中被提到。 - sancho.s ReinstateMonicaCellio

7

仅供参考: ** 运算符相当于两个参数版本的 内置的 pow 函数, 如果前两个参数是整数,则 pow 函数 接受一个可选的第三个参数(模数)。

因此,如果您想计算幂的余数,请使用内置函数。对于合理大小的参数,math.pow 将给出错误的结果:

import math

base = 13
exp = 100
mod = 2
print math.pow(base, exp) % mod
print pow(base, exp, mod)

当我运行时,在第一种情况下我得到了0.0,显然这是不可能的,因为13是奇数(因此它的所有整数幂也是奇数)。math.pow版本使用IEEE-754双精度(52位尾数,略小于16个十进制位数)的有限精度导致出现错误。
为了公正起见,我们必须说,math.pow也可以更快:
>>> import timeit
>>> min(timeit.repeat("pow(1.1, 9.9)", number=2000000, repeat=5))
0.3063715160001266
>>> min(timeit.repeat("math.pow(1.1, 9.9)", setup="import math", number=2000000, repeat=5))
0.2647279420000359
< p > < code > math.pow 函数在工程应用中有其优势(至今仍然如此),但是对于数论应用,您应该使用内置的< code > pow 函数。


一些在线示例


更新(必要更正):
我删除了math.pow(2,100)pow(2,100)的时间比较,因为math.pow会给出错误的结果,而例如pow(2,50)math.pow(2,50)之间的比较会是公平的(虽然不是math模块函数的实际用途)。我添加了一个更好的比较,并说明了导致math.pow限制的细节。

6
"**"确实比"math.pow()"更快,但如果您想要一个像您的示例中那样简单的二次函数,使用乘积甚至更快。"
10.*10.

将会更快

10.**2

虽然在单次操作(使用timeit)中差异不大且不明显,但在大量操作时会产生明显的影响。


4

实际上,它们用于不同的任务。

当您需要整数运算时,请使用pow(相当于带有两个参数的x ** y)。

如果任一参数为浮点数且您需要浮点输出,则使用math.pow

如需了解powmath.pow之间的差异,请参见此问题的讨论。


0
对于小的幂,比如2,我更喜欢直接将底数相乘:
使用x*x而不是x**2pow(x, 2)
我没有计时,但我敢打赌,乘法运算符与指数运算符或pow函数一样快。

0

操作符 **(与pow()相同)可用于计算非常大的整数。

>>> 2 ** 12345
164171010688258216356020741663906501410127235530735881272116103087925094171390144280159034536439457734870419127140401667195510331085657185332721089236401193044493457116299768844344303479235489462...

>>> math.pow(2, 12345)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: math range error

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