2009-05-08 22 views
21

Recientemente he descubierto que x**.5 y math.sqrt(x) no siempre producen el mismo resultado en Python:¿Cuál es más exacto, x **. 5 o math.sqrt (x)?

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 

Comprobación de todos los números enteros inferiores a 10 ** 7, los dos métodos produjeron resultados diferentes para casi exactamente 0,1% de las muestras, con el tamaño del error aumentando (lentamente) para números más grandes.

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

+0

¿Puedes explicar a que te refieres con "tasa de error"? –

+0

Los dos métodos para calcular una raíz cuadrada no producen resultados idénticos para 10,103 números debajo de 10 ** 7. (O aproximadamente 0.1%) –

+2

Veo, eso es lo que sospeché que podría haber querido decir. Probablemente encontrará que cuando hay una diferencia entre los dos métodos de cálculo, la diferencia estará en el menos significativo o posiblemente en dos bits de la representación de punto flotante. Esto se considera normal y es una consecuencia de los diferentes algoritmos utilizados para calcular los resultados. –

Respuesta

26

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 la plataforma C funciones matemáticas de la biblioteca de los mismos nombres ; math.pow() es más útil si necesita (o solo quiere) alta compatibilidad con extensiones C llamando a C pow().

__builtin__.pow() es la implementación de infija de Python ** operador, y se ocupa de complejos números, potencias enteras sin límites, y exponenciación modular también (C pow() no maneja cualquiera de esos).

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

+2

+1 para el enlace a la fuente autorizada :) –

+7

'sqrt' es una de las operaciones básicas definidas por IEEE 754. Debe implementarse directamente, no como una llamada a' pow', y como una operación básica debe tener un redondeo correcto (que las implementaciones habituales de 'pow' no tienen, no por mucho). –

+3

Calculando los cuadrados respectivos de '(8885558 ** 0.5)' y 'sqrt (8885558)' ** en punto flotante con la misma precisión que la potencia/raíz cuadrada ** y esperando que el cálculo diga algo acerca de qué resultado es más preciso es ingenuo. Concluir que "ambos divergen de la respuesta real en partes iguales" es risible: sus cuadrados calculados en coma flotante difieren de 8885558.0 cada uno por 1ULP. –

10

Tanto la función pow como la función math.sqrt() pueden calcular resultados que son más precisos de lo que puede almacenar el tipo de flotación 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 es una diferencia de ~ 10^(- 13) un problema al tomar la raíz cuadrada de un número de 7 dígitos? Incluso los cálculos de física más precisos rara vez requieren muchos dígitos significativos ...

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

+0

La respuesta a esta pregunta parece contradecir su comentario sobre la velocidad: http: // stackoverflow.com/questions/327002/python-which-is-faster-x-5-or-math-sqrtx –

+0

Un amigo mío realmente comparó esto hoy temprano, y concluyó que math.sqrt() era más rápido. Esto fue en el contexto de un cálculo de euler del proyecto. Confié en su palabra sin verificar su método, por lo que esa afirmación podría ser incorrecta. Aún así, parece muy poco probable que math.sqrt() sea más lento. Si ese fuera el caso, ¿por qué simplemente no lo implementaron en turnos de pow()? –

+0

"en términos de pow()" ... Estoy un poco cansado. :) –

4

Cada vez que se le da la opción entre dos funciones integradas en un idioma, la función más específica casi siempre será igual o mejor que la genérica (ya que si fuera peor, los codificadores habrían simplemente lo implementó en términos de la función genérica). Sqrt es más específico que la exponenciación 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á tratando con la precisión suficiente en sus números para poder decir.

Nota: Para aclarar, sqrt es más rápido en Python 3.0. Es más lento en las versiones anteriores de Python. Ver medidas J.F. Sebastians en Which is faster in Python: x**.5 or math.sqrt(x)?.

+0

¿Puedes aclararme esa última frase? –

+0

Su punto acerca de la velocidad es incorrecto. ** 0.5 es en realidad más rápido que sqrt. – Unknown

+0

No en Python 3.0. – Brian

1

No recibo el mismo comportamiento. Tal vez el error es específico de la plataforma? en AMD64 me sale esto:

 
Python 2.5.2 (r252:60911, Mar 10 2008, 15:14:55) 
[GCC 3.3.5 (propolice)] on openbsd4 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import math 
>>> math.sqrt(8885558) - (8885558**.5) 
0.0 
>>> (8885558**.5) - math.sqrt(8885558) 
0.0 
+0

Temía que ese fuera el caso, leí en alguna parte que math.sqrt se pasa directamente a la implementación de C, pero no puedo encontrar la referencia en este momento. –

+0

Puedo confirmar el resultado. –

+0

Ben: No puedo encontrarlo documentado, pero es cierto para Python 2.5 al menos: http://tinyurl.com/omvudo – Ken

3

Esto tiene que ser una especie de cosa específica de la plataforma porque consigo 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án usando y qué sistema operativo?

Supongo que tiene algo que ver con la promoción y el casting. En otras palabras, como lo está haciendo 8885558 **. 5, 8885558 tiene que ser promovido a flotante. 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 coma flotante. :-)

+0

"Python 2.6.1 (r261: 67517, 4 de diciembre de 2008, 16:51:00) [MSC v.1500 32 bit (Intel)] en win32 " –

+0

Interesante. Tal vez es un problema de 32 bit vs 64 bit de algún tipo? Yo y la otra persona que obtuve resultados diferentes estamos usando un sistema operativo de 64 bits. O podría ser una cosa de BSD. Estoy usando Mac OS X. –

+0

Obtengo 0.0 en Linux de 32 bits también (Python 2.5.2). –

6

Uso decimal para encontrar raíces cuadradas más precisos:

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

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

Ahora, en el triunfo, math.sqrt(8885558)**2 da 8885558.0000000019, mientras (8885558**.5)**2 da 8885557.9999999981, que parecen ascender a la misma épsilon.

Digo que realmente no se puede decir cuál es la opción "mejor".

1

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 pitón (o el doble de C) probablemente ocultará la diferencia.

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

0

que lleva a cabo la misma prueba y obtuvo los mismos resultados, 10103 diferencias de cada 10000000. estaba usando Python 2.7 en Windows.

La diferencia es una de redondeo. Creo que cuando los dos resultados difieren, es solo un ULP que es la menor diferencia posible para un flotador. La verdadera respuesta se encuentra entre los dos, pero un float no tiene la capacidad de representarlo exactamente y debe redondearse.

Como se señala en otra respuesta, el decimal module se puede utilizar para obtener una mejor 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') 
Cuestiones relacionadas