Подтвердить что ты не робот

Разница между встроенными параметрами 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 (те же числовые результаты, те же исключения, те же результаты для угла случаи и т.д.). Я уже реализовал что-то, что работает достаточно хорошо, но есть некоторые angular случаи, которые нужно обработать

4b9b3361

Ответ 1

Быстрая проверка

Из сигнатур мы можем сказать, что они разные:

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() передаются непосредственно в double:

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 math.

встроенный pow()

С другой стороны, встроенный pow() (то же, что и оператор **) ведет себя по-разному, он фактически использует собственную реализацию объектов оператора **, которая может быть переопределена конечным пользователем, если необходимо заменить на метод __pow__(), __rpow__() или __ipow__().

Для встроенных типов поучительно изучать разницу между функцией мощности, реализованной для двух числовых типов, например floats, 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 (без таких хаков, как math.pow = pow). Вы можете сделать класс пригодным для использования с math.pow, определив преобразование __float__, но тогда вы потеряете неопределенность, связанную с вашими числами.

Ответ 3

Стандарт 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 over 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)