Разница между встроенным pow() и math.pow() для float, в Python?
Есть ли разница в результатах, возвращаемых встроенным в Python pow(x, y)
(без третьего аргумента) и значения, возвращаемые math.pow()
, в случае двух аргументов с плавающей точкой.
Я задаю этот вопрос, потому что документация для math.pow()
подразумевает, что pow(x, y)
(т.е. x**y
) по сути так же, как math.pow(x, y)
:
math.pow(x, y)
Верните x в степень y. Исключительные случаи, насколько это возможно, следуют Приложению "F" стандарта C99. В частности, 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 (те же числовые результаты, те же исключения, те же результаты для угловых случаев и т. д.). Я уже реализовал кое-что, что работает довольно хорошо, но есть некоторые угловые случаи, которые необходимо обработать.
3 ответа
Быстрая проверка
По сигнатурам мы можем сказать, что они разные:
Pow (x, y [, z])
math.pow (x, y)
Кроме того, попытка сделать это в оболочке даст вам быстрое представление:
>>> 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__()
метод.
Для встроенных типов полезно изучить разницу между степенной функцией, реализованной для двух числовых типов, например, с плавающей запятой, длинной и сложной.
Переопределение поведения по умолчанию
Эмуляция числовых типов описана здесь. по сути, если вы создаете новый тип для чисел с неопределенностью, вам нужно будет предоставить __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__()
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__
преобразование, но тогда вы потеряете неопределенность, связанную с вашими числами.
Стандарт 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
,
Just adding %timeit comparison
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)