Python 中浮点数的内置 pow() 和 math.pow() 之间有何区别?
- 2025-03-20 08:47:00
- admin 原创
- 49
问题描述:
在有两个浮点参数的情况下,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 版更改: 1nan 和 nan0 的结果未定义。
请注意最后一行:文档暗示的行为math.pow()
是指数运算符的行为**
(因此也是的行为pow(x, y)
)。这是官方保证的吗?
背景:我的目标是为不确定的数字提供内置和pow()
的实现,其行为方式与常规 Python 浮点数相同(相同的数值结果、相同的异常、极端情况相同的结果等)。我已经实现了一些效果很好的方法,但有一些极端情况需要处理。math.pow()
解决方案 1:
快速检查
从签名可以看出它们是不同的:
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()
。这以牺牲灵活性为代价。查看源代码,我们可以看到 的参数直接转换为双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__()
对于内置类型,研究两种数字类型(例如float、long和complex )实现的幂函数之间的差异是有益的。
覆盖默认行为
此处描述了模拟数字类型。本质上,如果您要为不确定的数字创建新类型,则必须为您的类型提供__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__()
解决方案 2:
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__`
解决方案 3:
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
。
解决方案 4:
只需添加 %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)
扫码咨询,免费领取项目管理大礼包!