¿Qué causa los errores de redondeo de coma flotante?

62

Soy consciente de que la aritmética de coma flotante tiene problemas de precisión. Por lo general, los supero cambiando a una representación decimal fija del número, o simplemente descuidando el error.

Sin embargo, no sé cuáles son las causas de esta inexactitud. ¿Por qué hay tantos problemas de redondeo con números flotantes?

nmat
fuente
28
Para ser precisos, no es realmente el error causado por el redondeo lo que preocupa a la mayoría de las personas: es el hecho de que el redondeo de punto flotante binario se comporta de manera poco intuitiva. Cambiar a una representación decimal puede hacer que el redondeo se comporte de una manera más intuitiva, pero a cambio casi siempre aumentará el error relativo (o tendrá que aumentar el espacio de almacenamiento para compensar).
Daniel Pryden
12
Mi intento de aclarar las confusiones más comunes: floating-point-gui.de
Michael Borgwardt
Creo que lo que quiere decir @DanielPryden es "Cambiar a una representación [de punto fijo] puede hacer que el redondeo se comporte de una manera más intuitiva ..." . lo que causa problemas de redondeo, ya sea números fijos o de coma flotante, es el ancho de palabra finito de cualquiera de los dos. es solo que, con coma flotante, la magnitud del error de redondeo normalmente sigue siendo aproximadamente proporcional a la magnitud del número que se está redondeando. (excepto cuando te vuelves muy pequeño y con números "desnormalizados")
Robert Bristow-Johnson
@robert: Eso no es exactamente a lo que me refería. El "error" que la mayoría de las personas encuentran con el punto flotante no tiene nada que ver con el punto flotante per se, es la base. Los flotadores y dobles IEEE-754 usan un exponente en la base 2, lo que significa que los números fraccionarios se redondean a potencias negativas de dos (1/2, 1/16, 1/1024, etc.) en lugar de potencias negativas de 10 (1 / 10, 1/1000, etc.) Esto conduce a resultados poco intuitivos como 0.1 redondeo a 0.1000001 y problemas similares.
Daniel Pryden
Puede hacer números de coma flotante en base 10, así es como funciona el decimaltipo de .NET . El punto fijo, por otro lado, es diferente. Mientras su rango sea limitado, el punto fijo es una buena respuesta. Pero el rango restrictivo hace que el punto fijo no sea adecuado para muchas aplicaciones matemáticas, y como resultado las implementaciones de números de punto fijo a menudo no están bien optimizadas en hardware.
Daniel Pryden

Respuestas:

82

Esto se debe a que algunas fracciones necesitan una cantidad muy grande (o incluso infinita) de lugares para expresarse sin redondear. Esto es válido tanto para la notación decimal como para el binario o cualquier otro. Si limitara la cantidad de lugares decimales para usar en sus cálculos (y evitar hacer cálculos en notación de fracciones), tendría que redondear incluso una expresión simple como 1/3 + 1/3. En lugar de escribir 2/3 como resultado, tendría que escribir 0.33333 + 0.33333 = 0.66666 que no es idéntico a 2/3.

En el caso de una computadora, el número de dígitos está limitado por la naturaleza técnica de sus registros de memoria y CPU. La notación binaria utilizada internamente agrega algunas dificultades más. Las computadoras normalmente no pueden expresar números en notación de fracciones, aunque algunos lenguajes de programación agregan esta capacidad, lo que permite evitar esos problemas hasta cierto punto.

Lo que todo informático debe saber sobre la aritmética de coma flotante

Thorsten Müller
fuente
12
Correcto. Pero también señalaría que algunos números que terminan en decimal no terminan en binario. En particular, 0.1 es un número recurrente en binario, por lo que ningún número binario de coma flotante puede representar exactamente 0.1.
Jack Aidley
44
Los puntos flotantes no solo son útiles para muchos decimales. Los enteros de 32 bits solo pueden contar hasta aproximadamente 4 mil millones, pero un flotante de 32 bits puede ser casi infinitamente grande.
Abhi Beckert
77
En particular, las fracciones que podemos expresar como decimales finitos son aquellas cuya factorización prima de los denominadores contiene solo 2 y 5 (por ejemplo, podemos expresar 3/10 y 7/25, pero no 11/18). Cuando nos movemos a binario, perdemos el factor 5, de modo que solo los racionales diádicos (p. Ej. 1/4, 3/128) pueden expresarse exactamente.
David Zhang
70

Principalmente, los errores de redondeo provienen del hecho de que el infinito de todos los números reales no puede ser representado por la memoria finita de una computadora , y mucho menos una pequeña porción de memoria, como una variable de punto flotante , por lo que muchos números almacenados son solo aproximaciones de El número que deben representar.

Como solo hay un número limitado de valores que no son una aproximación, y cualquier operación entre una aproximación y otro número da como resultado una aproximación, los errores de redondeo son casi inevitables .

Lo importante es darse cuenta de cuándo es probable que causen un problema y tomar medidas para mitigar los riesgos .


Además de lo esencial de David Goldberg Lo que todo informático debe saber sobre la aritmética de punto flotante (reeditado por Sun / Oracle como un apéndice de su Guía de cálculo numérico ), que fue mencionado por thorsten , la revista ACCU Overload tuvo un excelente serie de artículos de Richard Harris sobre el Floating Point Blues .

La serie comenzó con

La computación numérica tiene muchas trampas. Richard Harris comienza a buscar una bala de plata.

El dragón del error numérico no suele despertarse de su sueño, pero si se le acerca con cautela, ocasionalmente causará daños catastróficos en los cálculos del programador desprevenido.

Tanto es así que algunos programadores, después de haberlo encontrado en los bosques de aritmética de punto flotante IEEE 754, aconsejan a sus compañeros que no viajen en esa tierra justa.

En esta serie de artículos exploraremos el mundo de la computación numérica, contrastando la aritmética de coma flotante con algunas de las técnicas que se han propuesto como reemplazos más seguros. Aprenderemos que el territorio del dragón es de gran alcance y que, en general, debemos caminar con cuidado si tememos su atención devastadora.

Richard comienza explicando la taxonomía de los números reales, racionales, irracionales, algebraicos y trascendentales. Luego continúa explicando la representación IEEE754, antes de pasar a un error de cancelación y problemas de orden de ejecución.

Si no lees más profundo que esto, tendrás una excelente base en los problemas asociados con los números de coma flotante.

Sin embargo, si quieres saber más, él continúa con

Luego cambia a tratar de ayudarlo a curar sus Azules de cálculo

y por último pero no menos importante, hay

Vale la pena examinar toda la serie de artículos, y con 66 páginas en total, aún son más pequeñas que las 77 páginas del artículo de Goldberg .

Si bien esta serie cubre gran parte del mismo terreno, la encontré bastante más accesible que el artículo de Goldberg . También me resultó más fácil entender las partes más complejas del documento después de leer los artículos anteriores de Richards y después de esos primeros artículos, Richard se ramifica en muchas áreas interesantes que el documento de Goldberg no menciona.


Como así se dijo en los comentarios:

Como autor de esos artículos, me gustaría mencionar que he creado versiones interactivas de ellos en mi blog www.thusspakeak.com comenzando con thusspakeak.com/ak/2013/06 .

Mark Booth
fuente
1
Como autor de esos artículos, me gustaría mencionar que he creado versiones interactivas de ellos en mi blog www.thusspakeak.com comenzando con thusspakeak.com/ak/2013/06 .
habló así el
Gracias @ thusspakea.k. Agregué una nota a mi respuesta, y esos elementos interactivos funcionan muy bien.
Mark Booth
12

Bueno, thorsten tiene el vínculo definitivo . Yo podria agregar:

Cualquier forma de representación tendrá algún error de redondeo para algún número. Intente expresar 1/3 en coma flotante IEEE o en decimal. Ninguno de los dos puede hacerlo con precisión. Esto va más allá de responder su pregunta, pero he usado esta regla general con éxito:

  • Almacene los valores ingresados ​​por el usuario en decimal (porque casi con seguridad lo ingresaron en una representación decimal; muy pocos usuarios usarán binario o hexadecimal). De esa manera, siempre tiene la representación exacta introducida por el usuario.
  • Si tiene que almacenar fracciones ingresadas por el usuario, almacene el numerador y el denominador (también en decimal)
  • Si tiene un sistema con múltiples unidades de medida para la misma cantidad (como Celsius / Fahrenheit), y el usuario puede ingresar ambos, almacenar el valor que ingresaron y las unidades en las que ingresaron. No intente convertir y guardar como una sola representación, a menos que pueda hacerlo sin pérdida de precisión / exactitud. Use el valor almacenado y las unidades en todos los cálculos.
  • Almacene los valores generados por la máquina en coma flotante IEEE (pueden ser números generados por un dispositivo de medición electrónico, como un sensor analógico con un convertidor A / D o el resultado no redondeado de un cálculo). Tenga en cuenta que esto no se aplica si está leyendo un sensor a través de una conexión en serie y ya le está dando el valor en formato decimal (por ejemplo, 18.2 C).
  • Almacene los totales visibles por el usuario, etc., en decimal (como el saldo de una cuenta bancaria). Redondea adecuadamente, pero usa ese valor como el valor definitivo para todos los cálculos futuros.
Scott Whitlock
fuente
Agregaría: considere usar un paquete matemático de precisión arbitraria como ARPREC o decNumber.
Blrfl
No decimal (en oposición al binario) tiene muchos beneficios para los valores enteros, como el numerador y el denominador de una fracción. Cualquiera puede almacenar valores enteros exactos, y el binario es más eficiente. Hay algún costo en la conversión de entrada y salida para entrada y salida, pero es probable que se vea afectado por el costo de realizar físicamente la E / S.
Keith Thompson el
10

Lo que parece no haberse mencionado hasta ahora son los conceptos de un algoritmo inestable y un problema mal condicionado . Primero abordaré el primero, ya que parece ser un obstáculo más frecuente para los expertos en numeración novatos.

Considere el cálculo de los poderes de la proporción áurea (recíproca) φ=0.61803…; Una forma posible de hacerlo es usar la fórmula de recursión φ^n=φ^(n-2)-φ^(n-1), comenzando con φ^0=1y φ^1=φ. Si ejecuta esta recursividad en su entorno informático favorito y compara los resultados con potencias evaluadas con precisión, encontrará una erosión lenta de cifras significativas. Esto es lo que sucede, por ejemplo, en Mathematica :

ph = N[1/GoldenRatio];  
Nest[Append[#1, #1[[-2]] - #1[[-1]]] & , {1, ph}, 50] - ph^Range[0, 51]  
{0., 0., 1.1102230246251565*^-16, -5.551115123125783*^-17, 2.220446049250313*^-16, 
-2.3592239273284576*^-16, 4.85722573273506*^-16, -7.147060721024445*^-16, 
1.2073675392798577*^-15, -1.916869440954372*^-15, 3.1259717037102064*^-15, 
-5.0411064211886014*^-15, 8.16837916750579*^-15, -1.3209051907825398*^-14, 
2.1377864756200182*^-14, -3.458669982359108*^-14, 5.596472721011714*^-14, 
-9.055131861349097*^-14, 1.465160458236081*^-13, -2.370673237795176*^-13, 
3.835834102607072*^-13, -6.206507137114341*^-13, 1.004234127360273*^-12, 
-1.6248848342954435*^-12, 2.6291189633497825*^-12, -4.254003796798193*^-12, 
6.883122762265558*^-12, -1.1137126558640235*^-11, 1.8020249321541067*^-11, 
-2.9157375879969544*^-11, 4.717762520172237*^-11, -7.633500108148015*^-11, 
1.23512626283229*^-10, -1.9984762736468268*^-10, 3.233602536479646*^-10, 
-5.232078810126407*^-10, 8.465681346606119*^-10, -1.3697760156732426*^-9, 
2.216344150333856*^-9, -3.5861201660070964*^-9, 5.802464316340953*^-9, 
-9.388584482348049*^-9, 1.5191048798689004*^-8, -2.457963328103705*^-8, 
3.9770682079726053*^-8, -6.43503153607631*^-8, 1.0412099744048916*^-7, 
-1.6847131280125227*^-7, 2.725923102417414*^-7, -4.4106362304299367*^-7, 
7.136559332847351*^-7, -1.1547195563277288*^-6}

El resultado pretendido para φ^41tiene el signo incorrecto, e incluso antes, los valores calculados y reales para φ^39compartir sin dígitos en común ( 3.484899258054952* ^ - 9 for the computed version against the true value7.071019424062048 *^-9). El algoritmo es, por lo tanto, inestable, y uno no debe usar esta fórmula de recursión en aritmética inexacta. Esto se debe a la naturaleza inherente de la fórmula de recursión: hay una solución "en descomposición" y "creciente" para esta recursión, y tratar de calcular la solución "en descomposición" mediante una solución directa cuando hay una solución alternativa "creciente" está rogando por pena numérica. Por lo tanto, uno debe asegurarse de que sus algoritmos numéricos sean estables.

Ahora, con el concepto de un problema mal condicionado : aunque puede haber una forma estable de hacer algo numéricamente, es muy posible que el problema que tiene no pueda ser resuelto por su algoritmo. Esto es culpa del problema en sí, y no del método de solución. El ejemplo canónico en numéricos es la solución de ecuaciones lineales que involucran la llamada "matriz de Hilbert":

Matriz de Hilbert

La matriz es el ejemplo canónico de una matriz mal acondicionada : tratar de resolver un sistema con una gran matriz de Hilbert podría devolver una solución inexacta.

Aquí hay una demostración de Mathematica : compare los resultados de la aritmética exacta

Table[LinearSolve[HilbertMatrix[n], HilbertMatrix[n].ConstantArray[1, n]], {n, 2, 12}]
{{1, 1}, {1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 
  1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1,
   1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 
  1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}

y aritmética inexacta

Table[LinearSolve[N[HilbertMatrix[n]], N[HilbertMatrix[n].ConstantArray[1, n]]], {n, 2, 12}]
{{1., 1.}, {1., 1., 1.}, {1., 1., 1., 1.}, {1., 1., 1., 1., 1.},  
  {1., 1., 1., 1., 1., 1.}, {1., 1., 1., 1., 1., 1., 1.}, 
  {1., 1., 1., 1., 1., 1., 1., 1.}, {1., 1., 1., 1., 1., 1., 1., 1., 1.},  
  {1., 1., 1., 0.99997, 1.00014, 0.999618, 1.00062, 0.9994, 1.00031, 
  0.999931}, {1., 1., 0.999995, 1.00006, 0.999658, 1.00122, 0.997327, 
  1.00367, 0.996932, 1.00143, 0.999717}, {1., 1., 0.999986, 1.00022, 
  0.998241, 1.00831, 0.975462, 1.0466, 0.94311, 1.04312, 0.981529, 
  1.00342}}

(Si lo probó en Mathematica , notará algunos mensajes de error que advierten sobre la aparición de problemas).

En ambos casos, simplemente aumentar la precisión no es una cura; solo retrasará la inevitable erosión de las figuras.

Esto es a lo que te puedes enfrentar. Las soluciones pueden ser difíciles: para el primero, puede volver al tablero de dibujo o leer revistas / libros / lo que sea para encontrar si alguien más ha encontrado una solución mejor que usted; para el segundo, te rindes o reformulas tu problema a algo más manejable.


Te dejo con una cita de Dianne O'Leary:

La vida puede arrojarnos algunos problemas mal condicionados, pero no hay una buena razón para conformarse con un algoritmo inestable.


fuente
9

porque los números decimales de base 10 no se pueden expresar en base 2

o en otras palabras, 1/10 no se puede transformar en una fracción con una potencia de 2 en el denominador (que es lo que son esencialmente los números de coma flotante)

monstruo de trinquete
fuente
11
No es exactamente cierto: 0.5 y 0.25 pueden expresarse en base 2. Creo que quiere decir "no todos los números decimales de base 10".
Scott Whitlock
3
Con más precisión. No todos los números fraccionarios se pueden representar exactamente usando una notación de coma flotante (es decir, con. Tanto la base 2 como la base 10 tienen este problema exacto). Intente hacerlo 9*3.3333333en decimal y compárelo con9*3 1/3
Martin York
1
Esta es la fuente más común de confusión de punto flotante. .1 + .1 != .2porque se usa codificación binaria de punto flotante, no decimal.
Sean McMillan
@SeanMcMillan: Y 1.0/3.0*3.0 != 1.0, como se usa la codificación binaria de punto flotante, no trinaria.
Keith Thompson
8

En matemáticas, hay infinitos números racionales. Una variable de 32 bits solo puede tener 2 32 valores diferentes, y una variable de 64 bits solo 2 valores de 64 . Por lo tanto, hay infinitos números racionales que no tienen una representación precisa.

Podríamos idear esquemas que nos permitieran representar 1/3 perfectamente, o 1/100. Resulta que para muchos propósitos prácticos esto no es muy útil. Hay una gran excepción: en las finanzas, las fracciones decimales a menudo aparecen. Esto se debe principalmente a que las finanzas son esencialmente una actividad humana, no física.

Por lo tanto, generalmente elegimos usar punto flotante binario y redondear cualquier valor que no pueda representarse en binario. Pero en finanzas, a veces elegimos coma flotante decimal y redondeamos los valores al valor decimal más cercano.

MSalters
fuente
2
Peor aún, si bien una cantidad infinita (infinitamente infinita) de memoria le permitiría a uno representar todos los racionales, no sería suficiente para representar los reales. Peor aún, casi todos los números reales no son números computables. Lo mejor que podemos hacer con una cantidad finita de memoria es aproximar un subconjunto de rango real de los reales.
David Hammen
44
@Kevin: Estás hablando de los números computables, que es un pequeño subconjunto (un subconjunto con medida cero) de los reales.
David Hammen
1
+1 para la explicación más básica: estás tratando de representar una cantidad infinita de números con un número finito de bits.
Raku
1
@DavidHammen: los números computables son un pequeño subconjunto (de medida cero) de los reales, pero cada número con el que trabajará en un programa es, por definición, computable.
Keith Thompson el
3
@Giorgio: si elige la representación correcta, la raíz cuadrada de 2 es representable, por ejemplo, como la cadena "√2". (Mi vieja calculadora HP-48 fue capaz de hacer exactamente eso, y la cuadratura de ese valor resultó exactamente 2.0). Solo hay un infinito contable de números reales representables para cualquier representación finita, pero ningún cálculo puede arrojar un número que no lo sea, en principio, representable. En la práctica, el punto flotante binario limita drásticamente el conjunto de números representables, con el beneficio de una velocidad increíble y un pequeño almacenamiento en relación con las representaciones simbólicas.
Keith Thompson
-2

El único "problema de redondeo" realmente obvio con los números de punto flotante que pienso es con los filtros de promedio móvil:

$$ \ begin {align} y [n] & = \ frac {1} {N} \ sum \ limits_ {i = 0} ^ {N-1} x [ni] \ & = y [n-1] + \ frac {1} {N} (x [n] - x [nN]) \ \ end {align} $$

para que esto funcione sin la acumulación de ruido, debe asegurarse de que los $ x [n] $ que agregue en las muestras actuales sean exactamente iguales a los $ x [nN] $ que restará $ N $ muestras en futuro. si no es así, lo que es diferente es un pequeño turd que se atasca en su línea de retraso y nunca saldrá. eso se debe a que este filtro de promedio móvil está construido con un IIR que tiene un polo marginalmente estable en $ z = 1 $ y un cero que lo cancela por dentro. pero es un integrador y cualquier basura que se integre y no se elimine por completo existirá en la suma del integrador para siempre. Aquí es donde el punto fijo no tiene el mismo problema que los números de punto flotante.

robert bristow-johnson
fuente
oye, ¿el marcado de matemáticas $ LaTeX $ no funciona en el foro prog.SE ??? eso es realmente lamentable si no es así.
robert bristow-johnson
1
Vea esto en meta.SO y preguntas vinculadas
AakashM