libreria - python 3.6 math module
¿Diferencia entre el pow() incorporado y math.pow() para flotantes, en Python? (3)
¿Hay alguna diferencia en los resultados devueltos por pow(x, y)
incorporado de Python (ningún tercer argumento) y los valores devueltos por math.pow()
, en el caso de dos argumentos float ?
Estoy haciendo esta pregunta porque la documentation 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)
Retorno x elevado a la potencia y. Los casos excepcionales siguen, en la medida de lo posible, el Anexo "F" de la norma C99. En particular, pow (1.0, x) y pow (x, 0.0) siempre devuelven 1.0, incluso cuando x es cero o NaN. Si tanto x como y son finitos, x es negativo e y no es un entero, entonces pow (x, y) no está definido, y aumenta ValueError.
Modificado en la versión 2.6: el resultado de 1 ** nan y nan ** 0 no estaba definido.
Observe 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)
). ¿Esto está oficialmente garantizado?
Antecedentes: Mi objetivo es proporcionar una implementación tanto del pow()
math.pow()
como del math.pow()
para números con incertidumbre que se comporta de la misma manera que con los flotantes normales de Python (mismos resultados numéricos, mismas excepciones, 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
A partir de las firmas, podemos decir que son diferentes:
pow (x, y [, z])
math.pow (x, y)
Además, probarlo en el caparazón le 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 puedas escribir algo similar que cree un informe de las diferencias entre las dos funciones.
math.pow()
math.pow()
maneja sus argumentos de forma muy diferente a los incorporados **
o pow()
. Esto tiene un costo de flexibilidad. Al echar un vistazo a la fuente , podemos ver que los argumentos para 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);
/*...*/
Los controles se llevan a cabo contra los dobles para la validez, y luego el resultado se pasa a la biblioteca matemática C subyacente.
pow()
incorporado pow()
El pow()
incorporado (igual que el operador **
) por otro lado se comporta de manera muy diferente, en realidad usa la propia implementación del operador **
los Objetos, que puede ser anulada por el usuario final si es necesario reemplazando un 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, floats , long y complex .
Reemplazando el comportamiento predeterminado
Emular tipos numéricos se describe here . 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 sean utilizados 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(
((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
)
Para sobrescribir math.pow()
tendrá que parchearlo para que sea compatible con 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 Uncertain
para hacer frente a una instancia Uncertain
como una entrada para __init__()
El pow
estándar de Python incluye un truco simple que hace que pow(2, 3, 2)
más rápido que (2 ** 3) % 2
(por supuesto, solo notarás eso 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 "<stdin>", line 1, in <module>
TypeError: can''t convert complex to float
Sin embargo, no tengo idea de por qué alguien preferiría math.pow
sobre pow
.
math.pow()
convierte implícitamente sus argumentos a float
:
>>> math.pow(Fraction(1, 3), 2)
0.1111111111111111
>>> math.pow(Decimal(10), -1)
0.1
pero el pow
incorporado no:
>>> pow(Fraction(1, 3), 2)
Fraction(1, 9)
>>> pow(Decimal(10), -1)
Decimal(''0.1'')
Mi objetivo es proporcionar una implementación de pow () incorporado y de math.pow () para números con incertidumbre
Puedes sobrecargar pow
y **
definiendo los métodos __pow__
y __rpow__
para tu clase.
Sin embargo, no puedes sobrecargar math.pow
(sin hacks como math.pow = pow
). Puede hacer que una clase se pueda usar con math.pow
definiendo una conversión de __float__
, pero luego perderá la incertidumbre asociada a sus números.