Python中内置的pow()和math.pow()之间的差异?

时间:2012-04-23 14:37:24

标签: python math

在两个 float 的情况下,Python内置pow(x, y)(没有第三个参数)和math.pow()返回的值返回的结果是否存在差异?参数。

我问这个问题,因为math.pow()的{​​{3}}表示pow(x, y)(即x**y)与math.pow(x, y)基本相同:

  

math.pow(x,y)

     

将x升至y。特殊情况   尽可能遵循C99标准的附录'F'。在   特别是,pow(1.0,x)和pow(x,0.0)总是返回1.0,即使是x   是零或NaN。如果x和y都是有限的,x是负的,y   不是整数,则pow(x,y)未定义,并引发ValueError。

     

版本2.6中更改:1 ** nan和nan ** 0的结果未定义。

注意最后一行:文档暗示math.pow()的行为是指数运算符**(因此pow(x, y))的行为。这是官方保证吗?

背景:我的目标是为具有不确定性的数字提供内置pow()math.pow() 的实现,其行为方式相同与常规Python浮点数一样(相同的数值结果,相同的例外情况,角点情况的相同结果等)。我有documentation一些效果很好的东西,但有一些already implemented需要处理。

4 个答案:

答案 0 :(得分:51)

快速检查

从签名中,我们可以看出它们是不同的:

  

pow(x,y [,z])

     

math.pow(x,y)

另外,在shell中尝试它会给你一个快速的想法:

>>> pow is math.pow
False

测试差异

了解两个函数之间行为差异的另一种方法是测试它们:

import math
import traceback
import sys

inf = float("inf")
NaN = float("nan")

vals = [inf, NaN, 0.0, 1.0, 2.2, -1.0, -0.0, -2.2, -inf, 1, 0, 2]

tests = set([])

for vala in vals:
  for valb in vals:
    tests.add( (vala, valb) )
    tests.add( (valb, vala) )


for a,b in tests:
  print("math.pow(%f,%f)"%(a,b) )
  try:
    print("    %f "%math.pow(a,b))
  except:
    traceback.print_exc()

  print("__builtins__.pow(%f,%f)"%(a,b) )
  try:
    print("    %f "%__builtins__.pow(a,b))
  except:
    traceback.print_exc()

然后我们可以注意到一些微妙的差异。例如:

math.pow(0.000000,-2.200000)
    ValueError: math domain error

__builtins__.pow(0.000000,-2.200000)
    ZeroDivisionError: 0.0 cannot be raised to a negative power

还有其他差异,上面的测试列表并不完整(没有长数字,没有复杂等等),但这将为我们提供一个实用的列表,列出两个函数的行为方式不同。我还建议扩展上面的测试以检查每个函数返回的类型。您可能会编写类似的内容,以创建两个函数之间差异的报告。

math.pow()

math.pow()处理其参数的方式与内置**pow()完全不同。这是以灵活性为代价的。看一下the source,我们可以看到math.pow()的参数直接转换为双打

static PyObject *
math_pow(PyObject *self, PyObject *args)
{
    PyObject *ox, *oy;
    double r, x, y;
    int odd_y;

    if (! PyArg_UnpackTuple(args, "pow", 2, 2, &ox, &oy))
        return NULL;
    x = PyFloat_AsDouble(ox);
    y = PyFloat_AsDouble(oy);
/*...*/

然后针对双精度执行检查以确保有效性,然后将结果传递给基础C数学库。

内置pow()

另一方面,内置pow()(与**运算符相同)的行为非常不同,它实际上使用了对象自己的**运算符的实现,它可以是如果需要,可以通过替换数字的__pow__()__rpow__()__ipow__()方法来覆盖最终用户。

对于内置类型,研究为两种数值类型实现的幂函数之间的差异是有益的,例如floatslongcomplex

覆盖默认行为

描述模拟数字类型here。实际上,如果您要为具有不确定性的数字创建新类型,那么您需要为您的类型提供__pow__()__rpow__()和可能__ipow__()方法。这将允许您的号码与运营商一起使用:

class Uncertain:
  def __init__(self, x, delta=0):
    self.delta = delta
    self.x = x
  def __pow__(self, other):
    return Uncertain(
      self.x**other.x, 
      Uncertain._propagate_power(self, other)
    )
  @staticmethod
  def _propagate_power(A, B):
    return math.sqrt(
      ((B.x*(A.x**(B.x-1)))**2)*A.delta*A.delta +
      (((A.x**B.x)*math.log(B.x))**2)*B.delta*B.delta
    )

为了覆盖math.pow(),你必须修补它以支持你的新类型:

def new_pow(a,b):
    _a = Uncertain(a)
    _b = Uncertain(b)
    return _a ** _b

math.pow = new_pow

请注意,为了实现此功能,您必须与Uncertain类进行争吵,以应对Uncertain实例作为__init__()的输入

答案 1 :(得分:31)

math.pow()隐式将其参数转换为float

>>> math.pow(Fraction(1, 3), 2)
0.1111111111111111
>>> math.pow(Decimal(10), -1)
0.1

但内置的pow没有:

>>> pow(Fraction(1, 3), 2)
Fraction(1, 9)
>>> pow(Decimal(10), -1)
Decimal('0.1')
  

我的目标是为具有不确定性的数字提供内置pow()和math.pow()的实现

您可以通过为您的班级定义pow**方法来重载__pow____rpow__

但是,你不能重载math.pow(没有math.pow = pow之类的黑客攻击。您可以通过定义math.pow转化来使类与__float__一起使用,但之后您将失去与您的数字相关的不确定性。

答案 2 :(得分:12)

Python的标准pow包含一个简单的黑客攻击,使pow(2, 3, 2)(2 ** 3) % 2更快(当然,你只会注意到大数字)。

另一个很大的区别是两个函数如何处理不同的输入格式。

>>> pow(2, 1+0.5j)
(1.8810842093664877+0.679354250205337j)
>>> math.pow(2, 1+0.5j)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't convert complex to float

但是,我不知道为什么有人会更喜欢math.pow而不是pow

答案 3 :(得分:0)

只需添加%timeit比较

In [1]: def pair_generator(): 
    ...:     yield (random.random()*10, random.random()*10) 
    ...:   

In [2]: %timeit [a**b for a, b in pair_generator()]                                                                    
538 ns ± 1.94 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit [math.pow(a, b) for a, b in pair_generator()]                                                          
632 ns ± 2.77 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)