¿Qué es más preciso, x **. 5 o math.sqrt (x)?

Recientemente descubrí que x**.5 y math.sqrt(x) no siempre producen el mismo resultado en Python:

 Python 2.6.1 (r261:67517, Dec 4 2008, 16:51:00) [MSC v.1500 32 bit (Intel)] on win32 >>> 8885558**.5 - math.sqrt(8885558) -4.5474735088646412e-13 

Al verificar todos los enteros por debajo de 10 ** 7, los dos métodos produjeron resultados diferentes para casi exactamente el 0,1% de las muestras, con un aumento en el tamaño del error (lentamente) para números más grandes.

Entonces la pregunta es, ¿qué método es más preciso?

Ninguno de los dos es más preciso, ambos divergen de la respuesta real en partes iguales:

 >>> (8885558**0.5)**2 8885557.9999999981 >>> sqrt(8885558)**2 8885558.0000000019 >>> 2**1023.99999999999 1.7976931348498497e+308 >>> (sqrt(2**1023.99999999999))**2 1.7976931348498495e+308 >>> ((2**1023.99999999999)**0.5)**2 1.7976931348498499e+308 >>> ((2**1023.99999999999)**0.5)**2 - 2**1023.99999999999 1.9958403095347198e+292 >>> (sqrt(2**1023.99999999999))**2 - 2**1023.99999999999 -1.9958403095347198e+292 

http://mail.python.org/pipermail/python-list/2003-November/238546.html

El módulo matemático envuelve las funciones matemáticas de la biblioteca de la plataforma C con los mismos nombres; math.pow() es más útil si necesita (o simplemente quiere) una alta compatibilidad con las extensiones C que llaman a pow() C.

__builtin__.pow() es la implementación del operador infix ** de Python, y se ocupa de los números complejos, las potencias de enteros ilimitados y la exponenciación modular (el C pow() no maneja ninguno de ellos).

** Es más completo. math.sqrt es probablemente la implementación en C de sqrt que probablemente esté relacionada con pow .

Tanto la función pow como la función math.sqrt () pueden calcular resultados más precisos que los que puede almacenar el tipo de flotador predeterminado. Creo que los errores que está viendo son el resultado de las limitaciones de las matemáticas de punto flotante en lugar de las imprecisiones de las funciones. Además, ¿desde cuándo una diferencia de ~ 10 ^ (- 13) es un problema al tomar la raíz cuadrada de un número de 7 dígitos? Incluso los cálculos físicos más precisos rara vez requieren tantos dígitos significativos …

Otra razón para usar math.sqrt () es que es más fácil de leer y entender, lo que generalmente es una buena razón para hacer las cosas de cierta manera.

Usa el decimal para encontrar raíces cuadradas más precisas:

 >>> import decimal >>> decimal.getcontext().prec = 60 >>> decimal.Decimal(8885558).sqrt() Decimal("2980.86531061032678789963529280900544861029083861907705317042") 

Cada vez que se le da la opción de elegir entre dos funciones que están integradas en un lenguaje, la función más específica será casi siempre igual o mejor que la genérica (ya que si fuera peor, los codificadores simplemente lo habrían implementado en términos de la función genérica). Sqrt es más específico que la exponencia genérica, por lo que puede esperar que sea una mejor opción. Y lo es, al menos en términos de velocidad. En términos de precisión, no estás tratando con suficiente precisión en tus números para poder decir.

Nota: para aclarar, sqrt es más rápido en Python 3.0. Es más lento en versiones anteriores de Python. Consulte las mediciones de JF Sebastians en ¿Cuál es más rápido en Python: x **. 5 o math.sqrt (x)? .

Esto tiene que ser algún tipo de cosa específica de la plataforma porque obtengo resultados diferentes:

 Python 2.5.1 (r251:54863, Jan 13 2009, 10:26:13) [GCC 4.0.1 (Apple Inc. build 5465)] on darwin >>> 8885558**.5 - math.sqrt(8885558) 0.0 

¿Qué versión de python estás usando y qué sistema operativo?

Mi conjetura es que tiene algo que ver con la promoción y el casting. En otras palabras, ya que estás haciendo 8885558 **. 5, 8885558 tiene que ser promovido a un flotador. Todo esto se maneja de manera diferente según el sistema operativo, el procesador y la versión de Python. Bienvenido al maravilloso mundo de la aritmética de punto flotante. 🙂

Tuve el mismo problema contigo en Win XP Python 2.5.1, mientras que no lo hago en Gentoo Python 2.5.4 de 32 bits. Es una cuestión de implementación de la biblioteca C.

Ahora, en Win, math.sqrt(8885558)**2 da 8885558.0000000019 , mientras que (8885558**.5)**2 da 8885557.9999999981 , que parecen equivaler al mismo épsilon.

Yo digo que uno no puede realmente decir cuál es la opción “mejor”.

No tengo el mismo comportamiento. ¿Quizás el error es específico de la plataforma? En AMD64 obtengo esto:

 Python 2.5.2 (r252: 60911, 10 de marzo de 2008, 15:14:55) 
 [GCC 3.3.5 (propolice)] en openbsd4
 Escriba "ayuda", "derechos de autor", "créditos" o "licencia" para obtener más información.
 >>> importar matematicas
 >>> math.sqrt (8885558) - (8885558 **. 5)
 0.0
 >>> (8885558 **. 5) - math.sqrt (8885558)
 0.0

En teoría, math.sqrt debería tener una mayor precisión que math.pow. Ver el método de Newton para calcular las raíces cuadradas [0]. Sin embargo, la limitación en el número de dígitos decimales del flotador de Python (o el doble C) probablemente ocultará la diferencia.

[0] http://en.wikipedia.org/wiki/Integer_square_root

Realicé la misma prueba y obtuve los mismos resultados, 10103 diferencias de 10000000. Esto estaba usando Python 2.7 en Windows.

La diferencia es de redondeo. Creo que cuando los dos resultados difieren, solo una ULP es la diferencia más pequeña posible para un flotador. La respuesta verdadera se encuentra entre los dos, pero un float no tiene la capacidad de representarlo exactamente y debe ser redondeado.

Como se señaló en otra respuesta, el módulo decimal puede usarse para obtener una mayor precisión que un float . Utilicé esto para tener una mejor idea del verdadero error, y en todos los casos el sqrt estaba más cerca que el **0.5 . Aunque no por mucho!

 >>> s1 = sqrt(8885558) >>> s2 = 8885558**0.5 >>> s3 = decimal.Decimal(8885558).sqrt() >>> s1, s2, s3 (2980.865310610327, 2980.8653106103266, Decimal('2980.865310610326787899635293')) >>> s3 - decimal.Decimal(s1) Decimal('-2.268290468226740188598632812E-13') >>> s3 - decimal.Decimal(s2) Decimal('2.2791830406379010009765625E-13')