70

在两个浮点参数的情况下, Python 的内置pow(x, y)(无第三个参数)返回的结果与 的返回值是否存在差异。math.pow()

我问这个问题是因为文档暗示math.pow()pow(x, y)x**y)本质上与math.pow(x, y)

数学.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()的实现,其行为方式与常规 Python 浮点数相同(相同的数值结果、相同的异常、极端情况下的相同结果等)。我已经实现了一些效果很好的东西,但是有一些极端情况需要处理。math.pow()

4

4 回答 4

60

快速检查

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

pow(x, y[, z])

数学.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(). 这是以灵活性为代价的。查看源代码,我们可以看到 to 的参数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 )实现的幂函数之间的区别是有益的。

覆盖默认行为

此处描述了模拟数字类型。本质上,如果您要为不确定的数字创建新类型,您需要做的是为您的类型提供__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__()

于 2012-04-23T14:50:00.490 回答
35

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

>>> from decimal import Decimal
>>> from fractions import Fraction
>>> 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(没有像 hack 这样的技巧math.pow = pow)。math.pow您可以通过定义转换使类可用__float__,但是您将失去与数字相关的不确定性。

于 2012-04-23T15:01:30.293 回答
12

Python 的标准pow包括一个简单的 hack,它比它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.

于 2012-04-23T14:51:17.153 回答
1

只需添加 %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)
于 2019-07-31T07:29:54.727 回答