Python 中浮点数的内置 pow() 和 math.pow() 之间的区别? [英] Difference between the built-in pow() and math.pow() for floats, in Python?

查看:41
本文介绍了Python 中浮点数的内置 pow() 和 math.pow() 之间的区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Python 内置的pow(x, y)(没有第三个参数)返回的结果和math.pow(),在两个 float 参数的情况下.

我问这个问题是因为文档math.pow() 意味着 pow(x, y)(即 x**y)与 math.pow(x, y):

<块引用>

ma​​th.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 浮点数的行为方式相同(相同的数值结果、相同的异常、极端情况下的相同结果等).我有已经实施一些效果很好的东西,但是有一些需要处理的极端情况.

解决方案

快速检查

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

<块引用>

pow(x, y[, z])

ma​​th.pow(x, y)

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

<预><代码>>>>pow 是 math.pow错误的

测试差异

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

导入数学导入回溯导入系统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]测试 = 设置([])对于 vals 中的 vala:对于 vals 中的 valb:测试添加((vala,valb))测试添加((valb,vala))对于测试中的 a,b:打印(math.pow(%f,%f)"%(a,b))尝试:打印(%f"%math.pow(a,b))除了:traceback.print_exc()打印(__builtins__.pow(%f,%f)"%(a,b))尝试:打印(%f"%__builtins__.pow(a,b))除了:traceback.print_exc()

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

math.pow(0.000000,-2.200000)ValueError:数学域错误__builtins__.pow(0.000000,-2.200000)ZeroDivisionError:0.0 不能被提升到负幂

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

math.pow()

math.pow() 处理它的参数与内置的 **pow() 非常不同.这是以牺牲灵活性为代价的.看看源码,我们可以看到math.pow() 的参数被直接转换为双精度:

静态 PyObject *math_pow(PyObject *self, PyObject *args){PyObject *ox, *oy;双 r, x, y;国际奇数_y;if (!PyArg_UnpackTuple(args, "pow", 2, 2, &ox, &oy))返回空;x = PyFloat_AsDouble(ox);y = PyFloat_AsDouble(oy);/*...*/

然后对双精度进行有效性检查,然后将结果传递给底层 C 数学库.

内置 pow()

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

对于内置类型,研究一下为两种数值类型实现的幂函数的区别是有指导意义的,例如,浮动long复杂.

覆盖默认行为

模拟数字类型在此处进行了描述.本质上,如果您要为具有不确定性的数字创建新类型,您需要做的是提供 __pow__()__rpow__() 和可能的 __ipow__() 方法适合您的类型.这将允许您的号码与运营商一起使用:

类不确定:def __init__(self, x, delta=0):self.delta = delta自我.x = xdef __pow__(自己,其他):返回不确定(self.x**other.x,Uncertain._propagate_power(self, other))@静态方法def _propagate_power(A, B):返回 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 = 不确定(a)_b = 不确定(b)返回_a ** _bmath.pow = new_pow

请注意,要使其正常工作,您必须处理 Uncertain 类以处理 Uncertain 实例作为 __init__()代码>

Is there a difference in the results returned by Python's built-in pow(x, y) (no third argument) and the values returned by math.pow(), in the case of two float arguments.

I am asking this question because the documentation for math.pow() implies that pow(x, y) (i.e. x**y) is essentially the same as math.pow(x, y):

math.pow(x, y)

Return x raised to the power y. Exceptional cases follow Annex ‘F’ of the C99 standard as far as possible. In particular, pow(1.0, x) and pow(x, 0.0) always return 1.0, even when x is a zero or a NaN. If both x and y are finite, x is negative, and y is not an integer then pow(x, y) is undefined, and raises ValueError.

Changed in version 2.6: The outcome of 1**nan and nan**0 was undefined.

Note the last line: the documentation implies that the behavior of math.pow() is that of the exponentiation operator ** (and therefore of pow(x, y)). Is this officially guaranteed?

Background: My goal is to provide an implementation of both the built-in pow() and of math.pow() for numbers with uncertainty that behaves in the same way as with regular Python floats (same numerical results, same exceptions, same results for corner cases, etc.). I have already implemented something that works quite well, but there are some corner cases that need to be handled.

解决方案

Quick Check

From the signatures, we can tell that they are different:

pow(x, y[, z])

math.pow(x, y)

Also, trying it in the shell will give you a quick idea:

>>> pow is math.pow
False

Testing the differences

Another way to understand the differences in behaviour between the two functions is to test for them:

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()

We can then notice some subtle differences. For example:

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

There are other differences, and the test list above is not complete (no long numbers, no complex, etc...), but this will give us a pragmatic list of how the two functions behave differently. I would also recommend extending the above test to check for the type that each function returns. You could probably write something similar that creates a report of the differences between the two functions.

math.pow()

math.pow() handles its arguments very differently from the builtin ** or pow(). This comes at the cost of flexibility. Having a look at the source, we can see that the arguments to math.pow() are cast directly to doubles:

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);
/*...*/

The checks are then carried out against the doubles for validity, and then the result is passed to the underlying C math library.

builtin pow()

The built-in pow() (same as the ** operator) on the other hand behaves very differently, it actually uses the Objects's own implementation of the ** operator, which can be overridden by the end user if need be by replacing a number's __pow__(), __rpow__() or __ipow__(), method.

For built-in types, it is instructive to study the difference between the power function implemented for two numeric types, for example, floats, long and complex.

Overriding the default behaviour

Emulating numeric types is described here. essentially, if you are creating a new type for numbers with uncertainty, what you will have to do is provide the __pow__(), __rpow__() and possibly __ipow__() methods for your type. This will allow your numbers to be used with the operator:

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
    )

In order to override math.pow() you will have to monkey patch it to support your new type:

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

math.pow = new_pow

Note that for this to work you'll have to wrangle the Uncertain class to cope with an Uncertain instance as an input to __init__()

这篇关于Python 中浮点数的内置 pow() 和 math.pow() 之间的区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆