¿Diferencia entre la función pow () y math.pow () para flotadores, en Python?

¿Hay alguna diferencia en los resultados devueltos por la función pow(x, y) incorporada de Python pow(x, y) (ningún tercer argumento) y los valores devueltos por math.pow() , en el caso de dos argumentos flotantes ?

Estoy haciendo esta pregunta porque la documentación para math.pow() implica que pow(x, y) (es decir, x**y ) es esencialmente lo mismo que math.pow(x, y) :

math.pow (x, y)

Vuelve x elevado a la potencia y. Casos excepcionales siguen el Anexo ‘F’ de la norma C99 en la medida de lo posible. En particular, pow (1.0, x) y pow (x, 0.0) siempre devuelven 1.0, incluso cuando x es un cero o un NaN. Si tanto x como y son finitos, x es negativo, y y no es un número entero, entonces pow (x, y) no está definido, y genera ValueError.

Cambiado en la versión 2.6: El resultado de 1 ** nan y nan ** 0 no estaba definido.

Note la última línea: la documentación implica que el comportamiento de math.pow() es el del operador de exponenciación ** (y, por lo tanto, de pow(x, y) ). ¿Está oficialmente garantizado?

Antecedentes: mi objective es proporcionar una implementación tanto de pow() como de math.pow() para los números con incertidumbre que se comportan de la misma manera que con los flotadores de Python (los mismos resultados numéricos, las mismas excepciones, los mismos resultados). para casos de esquina, etc.). Ya he implementado algo que funciona bastante bien, pero hay algunos casos de esquina que deben ser manejados.

Comprobación rápida

De las firmas, podemos decir que son diferentes:

pow (x, y [, z])

math.pow (x, y)

Además, probarlo en el shell te dará una idea rápida:

 >>> pow is math.pow False 

Probando las diferencias

Otra forma de entender las diferencias de comportamiento entre las dos funciones es probarlas:

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

Entonces podemos notar algunas diferencias sutiles. Por ejemplo:

 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 

Hay otras diferencias, y la lista de pruebas anterior no está completa (sin números largos, sin complejos, etc.), pero esto nos dará una lista pragmática de cómo las dos funciones se comportan de manera diferente. También recomendaría extender la prueba anterior para verificar el tipo que devuelve cada función. Probablemente podría escribir algo similar que cree un informe de las diferencias entre las dos funciones.

math.pow()

math.pow() maneja sus argumentos de manera muy diferente a la incorporada ** o pow() . Esto viene a costa de la flexibilidad. Echando un vistazo a la fuente , podemos ver que los argumentos de math.pow() se math.pow() directamente en dobles :

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

Las verificaciones se llevan a cabo contra la validez de los dobles, y luego el resultado se pasa a la biblioteca matemática de C subyacente.

pow() incorporado pow()

La función pow() (igual que el operador ** ), por otro lado, se comporta de manera muy diferente, en realidad utiliza la implementación propia del Objeto ** del Objeto, que el usuario final puede anular si es necesario reemplazando un el número __pow__() , __rpow__() o __ipow__() , método.

Para los tipos incorporados, es instructivo estudiar la diferencia entre la función de potencia implementada para dos tipos numéricos, por ejemplo, flotadores , largos y complejos .

Anular el comportamiento predeterminado

La emulación de tipos numéricos se describe aquí . esencialmente, si está creando un nuevo tipo para números con incertidumbre, lo que tendrá que hacer es proporcionar los __pow__() , __rpow__() y posiblemente __ipow__() para su tipo. Esto permitirá que sus números se utilicen con el operador:

 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( ((Bx*(Ax**(Bx-1)))**2)*A.delta*A.delta + (((Ax**Bx)*math.log(Bx))**2)*B.delta*B.delta ) 

Para anular math.pow() , tendrá que aplicar un parche para admitir su nuevo tipo:

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

Tenga en cuenta que para que esto funcione tendrá que disputar la clase de Uncertain para hacer frente a una instancia de Uncertain como una entrada para __init__()

math.pow() convierte implícitamente sus argumentos en float :

 >>> math.pow(Fraction(1, 3), 2) 0.1111111111111111 >>> math.pow(Decimal(10), -1) 0.1 

pero el pow incorporado no hace:

 >>> pow(Fraction(1, 3), 2) Fraction(1, 9) >>> pow(Decimal(10), -1) Decimal('0.1') 

Mi objective es proporcionar una implementación de pow () y de math.pow () para números con incertidumbre.

Puede sobrecargar pow y ** definiendo los métodos __pow__ y __rpow__ para su clase.

Sin embargo, no puedes sobrecargar math.pow (sin math.pow = pow como math.pow = pow ). Puedes hacer que una clase sea utilizable con math.pow definiendo una conversión __float__ , pero luego perderás la incertidumbre asociada a tus números.

El poder estándar de Python incluye un truco simple que hace que el poder pow(2, 3, 2) más rápido que (2 ** 3) % 2 (por supuesto, solo lo notará con números grandes).

Otra gran diferencia es cómo las dos funciones manejan diferentes formatos de entrada.

 >>> pow(2, 1+0.5j) (1.8810842093664877+0.679354250205337j) >>> math.pow(2, 1+0.5j) Traceback (most recent call last): File "", line 1, in  TypeError: can't convert complex to float 

Sin embargo, no tengo idea de por qué alguien preferiría math.pow over pow .