floating-point - programacion - punto flotante metodos numericos
Ejemplos de inexactitud de punto flotante (7)
¿Cómo se explica la inexactitud del punto flotante a los programadores nuevos y a los legos que todavía piensan que las computadoras son infinitamente sabias y precisas?
¿Tiene un ejemplo o anécdota favorita que parece transmitir la idea mucho mejor que una explicación precisa pero seca?
¿Cómo se enseña esto en las clases de Informática?
¿Cómo es esto para una explantación para el profano? Una forma en que las computadoras representan números es contando unidades discretas. Estas son computadoras digitales. Para los números enteros, aquellos sin una parte fraccionaria, las computadoras digitales modernas cuentan con poderes de dos: 1, 2, 4, 8. ,,, valor de posición, dígitos binarios, bla, bla, bla. Para las fracciones, las computadoras digitales cuentan con poderes inversos de dos: 1/2, 1/4, 1/8, ... El problema es que muchos números no pueden representarse por una suma de un número finito de esos poderes inversos. El uso de más valores de posición (más bits) aumentará la precisión de la representación de esos números "problemáticos", pero nunca lo obtendrá exactamente porque solo tiene un número limitado de bits. Algunos números no se pueden representar con un número infinito de bits.
Dormitar...
De acuerdo, quiere medir el volumen de agua en un recipiente y solo tiene 3 tazas de medir: vaso lleno, media taza y cuarto de taza. Después de contar la última copa llena, digamos que queda un tercio de una taza. Sin embargo, no puede medir eso porque no llena exactamente ninguna combinación de tazas disponibles. No llena la media taza, y el desbordamiento del cuarto de taza es demasiado pequeño para llenar cualquier cosa. Entonces tienes un error: la diferencia entre 1/3 y 1/4. Este error se agrava cuando lo combina con errores de otras mediciones.
Aquí está mi simple comprensión.
Problema: el valor 0.45 no se puede representar con precisión mediante un flotador y se redondea a 0.450000018. ¿Porqué es eso?
Respuesta: Un valor int de 45 está representado por el valor binario 101101. Para hacer que el valor sea 0.45, sería exacto si pudiera tomar 45 x 10 ^ -2 (= 45/10 ^ 2). Pero eso es imposible porque debes usar la base 2 en lugar de 10.
Entonces, lo más cercano a 10 ^ 2 = 100 sería 128 = 2 ^ 7. El número total de bits que necesita es 9: 6 para el valor 45 (101101) + 3 bits para el valor 7 (111). Entonces el valor 45 x 2 ^ -7 = 0.3515625. Ahora tiene un grave problema de inexactitud. 0.3515625 no está cerca de 0.45.
¿Cómo mejoramos esta inexactitud? Bueno, podríamos cambiar el valor 45 y 7 por otra cosa.
¿Qué tal 460 x 2 ^ -10 = 0.44921875. Ahora está utilizando 9 bits para 460 y 4 bits para 10. Luego está un poco más cerca, pero todavía no está tan cerca. Sin embargo, si su valor inicial deseado fuera 0.44921875, entonces obtendría una coincidencia exacta sin aproximación.
Entonces la fórmula para su valor sería X = A x 2 ^ B. Donde A y B son valores enteros positivos o negativos. Obviamente, cuanto más altos sean los números, mayor será su precisión, sin embargo, como saben, el número de bits para representar los valores A y B son limitados. Para float tienes un total de 32. Double tiene 64 y Decimal tiene 128.
Básicamente, hay dos trampas principales en las que la gente tropieza con números de coma flotante.
El problema de la escala Cada número FP tiene un exponente que determina la "escala" general del número para que pueda representar valores muy pequeños o realmente grandes, aunque la cantidad de dígitos que puede dedicar es limitada. Agregar dos números de diferentes escalas ocasionará que el más pequeño sea "comido", ya que no hay forma de ajustarlo a una escala mayor.
PS> $a = 1; $b = 0.0000000000000000000000001 PS> Write-Host a=$a b=$b a=1 b=1E-25 PS> $a + $b 1
Como una analogía para este caso, podrías imaginar una gran piscina y una cucharadita de agua. Ambos son de tamaños muy diferentes, pero individualmente se puede captar fácilmente cuánto son aproximadamente. Sin embargo, verter la cucharilla en la piscina lo dejará quieto con aproximadamente una piscina llena de agua.
(Si las personas que están aprendiendo esto tienen problemas con la notación exponencial, también se pueden usar los valores
1
y100000000000000000000
o menos).Luego está el problema de la representación binaria vs. decimal. Un número como
0.1
no se puede representar exactamente con una cantidad limitada de dígitos binarios. Algunos idiomas enmascaran esto, sin embargo:PS> "{0:N50}" -f 0.1 0.10000000000000000000000000000000000000000000000000
Pero puede "amplificar" el error de representación al agregar repetidamente los números:
PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum 9,99999999999998
Sin embargo, no puedo pensar en una buena analogía para explicar esto adecuadamente. Básicamente es el mismo problema por el que puedes representar 1/3 solo aproximadamente en decimal porque para obtener el valor exacto necesitas repetir el 3 indefinidamente al final de la fracción decimal.
De manera similar, las fracciones binarias son buenas para representar mitades, trimestres, octavos, etc. pero cosas como una décima generarán una secuencia infinitamente repetitiva de dígitos binarios.
Luego hay otro problema, aunque la mayoría de las personas no se tropieza con eso, a menos que estén haciendo una gran cantidad de cosas numéricas. Pero entonces, aquellos ya saben sobre el problema. Dado que muchos números en coma flotante son simplemente aproximaciones del valor exacto, esto significa que para una aproximación dada f de un número real r puede haber infinitos más números reales r 1 , r 2 , ... que se correspondan exactamente con la misma aproximación . Esos números se encuentran en un cierto intervalo. Digamos que r min es el valor mínimo posible de r que da como resultado f y r max el valor máximo posible de r para el cual esto se cumple, luego obtienes un intervalo [ r min , r max ] donde cualquier número en ese intervalo puede ser tu número real r .
Ahora, si realiza cálculos en ese número (suma, resta, multiplicación, etc.), pierde precisión. Cada número es solo una aproximación, por lo tanto, en realidad está realizando cálculos con intervalos . El resultado es un intervalo también y el error de aproximación solo se hace más grande, lo que amplía el intervalo. Puede obtener un solo número de ese cálculo. Pero eso es solo un número del intervalo de posibles resultados, teniendo en cuenta la precisión de sus operandos originales y la pérdida de precisión debida al cálculo.
Ese tipo de cosas se llama aritmética Interval y al menos para mí fue parte de nuestro curso de matemáticas en la universidad.
Demuéstrales que el sistema de base 10 sufre exactamente el mismo problema.
Intenta representar 1/3 como una representación decimal en la base 10. No podrás hacerlo exactamente.
Entonces, si escribe "0.3333", tendrá una representación razonablemente exacta para muchos casos de uso.
Pero si lo regresas a una fracción, obtendrás "3333/10000", que no es lo mismo que "1/3".
Otras fracciones, como 1/2, pueden representarse fácilmente mediante una representación decimal finita en base-10: "0.5"
Ahora base-2 y base-10 sufren esencialmente el mismo problema: ambos tienen algunos números que no pueden representar exactamente.
Mientras que base-10 no tiene ningún problema para representar 1/10 como "0.1" en base-2, necesitaría una representación infinita comenzando con "0,000110011 ..".
En Python:
>>> 1.0 / 10
0.10000000000000001
Explique cómo algunas fracciones no se pueden representar precisamente en binario. Al igual que algunas fracciones (como 1/3) no se pueden representar con precisión en la base 10.
Otro ejemplo, en C
printf (" %.20f /n", 3.6);
increíblemente da
3.60000000000000008882
Se puede observar una extraña pieza de rareza numérica si uno convierte 9999999.4999999999 en un float
y vuelve a un double
. El resultado se informa como 10000000, aunque ese valor es obviamente más cercano a 9999999, y aunque 9999999.499999999 redondea correctamente a 9999999.