Considere el siguiente código:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
¿Por qué ocurren estas imprecisiones?
math
language-agnostic
floating-point
floating-accuracy
Cato Johnston
fuente
fuente
Respuestas:
La matemática de punto flotante binario es así. En la mayoría de los lenguajes de programación, se basa en el estándar IEEE 754 . El quid del problema es que los números se representan en este formato como un número entero multiplicado por una potencia de dos; los números racionales (como
0.1
, cuál es1/10
) cuyo denominador no es una potencia de dos no pueden representarse exactamente.En
0.1
elbinary64
formato estándar , la representación se puede escribir exactamente como0.1000000000000000055511151231257827021181583404541015625
en decimal, o0x1.999999999999ap-4
en notación C99 hexfloat .En contraste, el número racional
0.1
, que es1/10
, puede escribirse exactamente como0.1
en decimal, o0x1.99999999999999...p-4
en un análogo de notación de flotación hexagonal C99, donde...
representa una secuencia interminable de 9.Las constantes
0.2
y0.3
en su programa también serán aproximaciones a sus valores verdaderos. Sucede que el más cercanodouble
a0.2
es más grande que el número racional0.2
pero que el más cercanodouble
a0.3
es más pequeño que el número racional0.3
. La suma0.1
y0.2
termina siendo mayor que el número racional0.3
y, por lo tanto, no está de acuerdo con la constante en su código.Un tratamiento bastante completo de los problemas aritméticos de punto flotante es lo que todo informático debe saber sobre la aritmética de punto flotante . Para una explicación más fácil de digerir, vea floating-point-gui.de .
Nota al margen: todos los sistemas de números posicionales (base-N) comparten este problema con precisión
Los números decimales antiguos simples (base 10) tienen los mismos problemas, por lo que números como 1/3 terminan en 0.333333333 ...
Acaba de toparse con un número (3/10) que resulta fácil de representar con el sistema decimal, pero que no se ajusta al sistema binario. También va en ambos sentidos (en algún grado pequeño): 1/16 es un número feo en decimal (0.0625), pero en binario se ve tan ordenado como un 10,000 en decimal (0.0001) ** - si estuviéramos en Con el hábito de usar un sistema de números de base 2 en nuestra vida diaria, incluso miraría ese número y comprendería instintivamente que podría llegar allí reduciendo a la mitad algo, reduciéndolo a la mitad una y otra vez.
** Por supuesto, esa no es exactamente la forma en que los números de punto flotante se almacenan en la memoria (usan una forma de notación científica). Sin embargo, ilustra el punto de que los errores de precisión de punto flotante binario tienden a surgir porque los números del "mundo real" con los que generalmente estamos interesados en trabajar son a menudo potencias de diez, pero solo porque usamos un día de sistema de números decimales. hoy. Esta es también la razón por la que diremos cosas como 71% en lugar de "5 de cada 7" (71% es una aproximación, ya que 5/7 no se puede representar exactamente con ningún número decimal).
Entonces no: los números binarios de coma flotante no están rotos, simplemente resultan ser tan imperfectos como cualquier otro sistema de números base-N :)
Nota lateral: Trabajar con flotadores en la programación
En la práctica, este problema de precisión significa que necesita usar funciones de redondeo para redondear sus números de coma flotante a la cantidad de decimales que le interese antes de mostrarlos.
También debe reemplazar las pruebas de igualdad con comparaciones que permitan cierta tolerancia, lo que significa:
No no hacer
if (x == y) { ... }
En cambio hazlo
if (abs(x - y) < myToleranceValue) { ... }
.donde
abs
es el valor absolutomyToleranceValue
debe elegirse para su aplicación en particular, y tendrá mucho que ver con la cantidad de "margen de maniobra" que está dispuesto a permitir, y cuál puede ser el mayor número que va a comparar (debido a problemas de pérdida de precisión ) Tenga cuidado con las constantes de estilo "epsilon" en su idioma de elección. Estos son no para ser utilizados como valores de tolerancia.fuente
La perspectiva de un diseñador de hardware
Creo que debería agregar la perspectiva de un diseñador de hardware a esto ya que diseño y construyo hardware de punto flotante. Conocer el origen del error puede ayudar a comprender lo que está sucediendo en el software y, en última instancia, espero que esto ayude a explicar las razones por las cuales los errores de coma flotante ocurren y parecen acumularse con el tiempo.
1. Información general
Desde una perspectiva de ingeniería, la mayoría de las operaciones de punto flotante tendrán algún elemento de error, ya que el hardware que realiza los cálculos de punto flotante solo debe tener un error de menos de la mitad de una unidad en el último lugar. Por lo tanto, gran parte del hardware se detendrá con una precisión que solo es necesaria para generar un error de menos de la mitad de una unidad en el último lugar para una sola operación, lo cual es especialmente problemático en la división de coma flotante. Lo que constituye una sola operación depende de cuántos operandos tome la unidad. Para la mayoría, son dos, pero algunas unidades toman 3 o más operandos. Debido a esto, no hay garantía de que las operaciones repetidas den como resultado un error deseable ya que los errores se acumulan con el tiempo.
2. Normas
La mayoría de los procesadores siguen el estándar IEEE-754 , pero algunos usan estándares desnormalizados o diferentes. Por ejemplo, hay un modo desnormalizado en IEEE-754 que permite la representación de números de coma flotante muy pequeños a expensas de la precisión. Sin embargo, lo siguiente cubrirá el modo normalizado de IEEE-754, que es el modo de operación típico.
En el estándar IEEE-754, a los diseñadores de hardware se les permite cualquier valor de error / épsilon siempre que sea menos de la mitad de una unidad en el último lugar, y el resultado solo debe ser menos de la mitad de una unidad en el último Lugar para una operación. Esto explica por qué cuando hay operaciones repetidas, los errores se suman. Para la precisión doble IEEE-754, este es el 54 ° bit, ya que 53 bits se utilizan para representar la parte numérica (normalizada), también llamada mantisa, del número de coma flotante (por ejemplo, el 5.3 en 5.3e5). Las siguientes secciones dan más detalles sobre las causas del error de hardware en varias operaciones de coma flotante.
3. Causa del error de redondeo en la división
La causa principal del error en la división de coma flotante son los algoritmos de división utilizados para calcular el cociente. La mayoría de los sistemas informáticos calculan la división usando la multiplicación por un inverso, principalmente en
Z=X/Y
,Z = X * (1/Y)
. Una división se calcula iterativamente, es decir, cada ciclo calcula algunos bits del cociente hasta que se alcanza la precisión deseada, lo que para IEEE-754 es cualquier cosa con un error de menos de una unidad en el último lugar. La tabla de recíprocos de Y (1 / Y) se conoce como la tabla de selección de cociente (QST) en la división lenta, y el tamaño en bits de la tabla de selección de cociente suele ser el ancho de la raíz, o un número de bits de El cociente calculado en cada iteración, más algunos bits de protección. Para el estándar IEEE-754, doble precisión (64 bits), sería el tamaño de la raíz del divisor, más unos pocos bits de protección k, dondek>=2
. Entonces, por ejemplo, una tabla de selección de cociente típica para un divisor que calcula 2 bits del cociente a la vez (radix 4) sería2+2= 4
bits (más algunos bits opcionales).3.1 Error de redondeo de división: aproximación de recíproco
Los recíprocos en la tabla de selección de cociente dependen del método de división : división lenta como la división SRT o división rápida como la división Goldschmidt; cada entrada se modifica de acuerdo con el algoritmo de división en un intento de producir el error más bajo posible. En cualquier caso, sin embargo, todos los recíprocos son aproximaciones.del recíproco real e introducir algún elemento de error. Los métodos de división lenta y división rápida calculan el cociente de forma iterativa, es decir, se calcula un cierto número de bits del cociente en cada paso, luego el resultado se resta del dividendo y el divisor repite los pasos hasta que el error sea inferior a la mitad de uno Unidad en último lugar. Los métodos de división lenta calculan un número fijo de dígitos del cociente en cada paso y generalmente son menos costosos de construir, y los métodos de división rápida calculan un número variable de dígitos por paso y generalmente son más costosos de construir. La parte más importante de los métodos de división es que la mayoría de ellos se basan en la multiplicación repetida por una aproximación de un recíproco, por lo que son propensos a errores.
4. Errores de redondeo en otras operaciones: truncamiento
Otra causa de los errores de redondeo en todas las operaciones son los diferentes modos de truncamiento de la respuesta final que permite IEEE-754. Hay truncar, redondear hacia cero, redondear al más cercano (predeterminado), redondear hacia abajo y redondear hacia arriba. Todos los métodos introducen un elemento de error de menos de una unidad en el último lugar para una sola operación. Con el tiempo y las operaciones repetidas, el truncamiento también se suma acumulativamente al error resultante. Este error de truncamiento es especialmente problemático en la exponenciación, que implica alguna forma de multiplicación repetida.
5. Operaciones repetidas
Dado que el hardware que realiza los cálculos de coma flotante solo necesita obtener un resultado con un error de menos de la mitad de una unidad en el último lugar para una sola operación, el error crecerá en operaciones repetidas si no se observa. Esta es la razón por la que en los cálculos que requieren un error acotado, los matemáticos usan métodos como el uso del dígito par redondeado al más cercano en el último lugar de IEEE-754, porque, con el tiempo, es más probable que los errores se cancelen entre sí. out, y Aritmética de intervalo combinada con variaciones de los modos de redondeo IEEE 754para predecir errores de redondeo y corregirlos. Debido a su bajo error relativo en comparación con otros modos de redondeo, redondear al dígito par más cercano (en último lugar), es el modo de redondeo predeterminado de IEEE-754.
Tenga en cuenta que el modo de redondeo predeterminado, redondear al dígito par más cercano en el último lugar , garantiza un error de menos de la mitad de una unidad en el último lugar para una operación. El uso del truncamiento, redondeo y redondeo solo puede dar como resultado un error que es mayor que la mitad de una unidad en el último lugar, pero menor que una unidad en el último lugar, por lo que estos modos no se recomiendan a menos que sean utilizado en aritmética de intervalos.
6. Resumen
En resumen, la razón fundamental de los errores en las operaciones de coma flotante es una combinación del truncamiento en el hardware y el truncamiento de un recíproco en el caso de la división. Dado que el estándar IEEE-754 solo requiere un error de menos de la mitad de una unidad en el último lugar para una sola operación, los errores de coma flotante sobre operaciones repetidas se sumarán a menos que se corrijan.
fuente
Cuando convierte .1 o 1/10 a base 2 (binario) obtiene un patrón repetitivo después del punto decimal, al igual que tratar de representar 1/3 en base 10. El valor no es exacto y, por lo tanto, no puede hacerlo matemática exacta con él utilizando métodos normales de coma flotante.
fuente
La mayoría de las respuestas aquí abordan esta pregunta en términos técnicos muy secos. Me gustaría abordar esto en términos que los seres humanos normales puedan entender.
Imagine que está tratando de cortar pizzas. Tiene un cortador de pizza robótico que puede cortar rebanadas de pizza exactamente por la mitad. Puede reducir a la mitad una pizza entera, o puede reducir a la mitad una rebanada existente, pero en cualquier caso, la reducción a la mitad siempre es exacta.
Ese cortador de pizza tiene movimientos muy finos, y si comienza con una pizza completa, luego divida en dos y continúe dividiendo la porción más pequeña cada vez, puede hacer la reducción a la mitad 53 veces antes de que la porción sea demasiado pequeña incluso para sus habilidades de alta precisión . En ese punto, ya no puede reducir a la mitad ese corte muy delgado, sino que debe incluirlo o excluirlo tal como está.
Ahora, ¿cómo dividiría todas las rebanadas de tal manera que sumen una décima parte (0.1) o una quinta parte (0.2) de una pizza? Realmente piense en ello e intente resolverlo. Incluso puede intentar usar una pizza real, si tiene a mano un cortador de pizza de precisión mítica. :-)
Los programadores más experimentados, por supuesto, conocen la respuesta real, que es que no hay forma de juntar una décima o quinta parte exacta de la pizza usando esas rebanadas, sin importar cuán finamente las corte. Puede hacer una aproximación bastante buena, y si suma la aproximación de 0.1 con la aproximación de 0.2, obtendrá una aproximación bastante buena de 0.3, pero sigue siendo solo eso, una aproximación.
Para los números de doble precisión (que es la precisión que le permite reducir a la mitad su pizza 53 veces), los números inmediatamente menores y mayores que 0.1 son 0.09999999999999999167332731531132594682276248931884765625 y 0.1000000000000000055511151231257827021181583404541015625. El último está bastante más cerca de 0.1 que el primero, por lo que un analizador numérico, con una entrada de 0.1, favorecerá al último.
(La diferencia entre esos dos números es el "segmento más pequeño" que debemos decidir incluir, que introduce un sesgo hacia arriba, o excluir, que introduce un sesgo hacia abajo. El término técnico para ese segmento más pequeño es un ulp ).
En el caso de 0.2, los números son todos iguales, solo aumentados por un factor de 2. Nuevamente, favorecemos el valor que es ligeramente superior a 0.2.
Observe que en ambos casos, las aproximaciones para 0.1 y 0.2 tienen un ligero sesgo hacia arriba. Si agregamos suficiente de estos sesgos, empujarán el número cada vez más lejos de lo que queremos, y de hecho, en el caso de 0.1 + 0.2, el sesgo es lo suficientemente alto como para que el número resultante ya no sea el número más cercano a 0.3.
En gran medida, la
PD: algunos lenguajes de programación también proporcionan cortadores de pizza que pueden dividir rebanadas en décimas exactas . Aunque tales cortadores de pizza son poco comunes, si tiene acceso a uno, debe usarlo cuando sea importante poder obtener exactamente una décima parte o un quinto de una rebanada.
(Publicado originalmente en Quora).
fuente
Errores de redondeo de punto flotante. 0.1 no se puede representar con tanta precisión en la base 2 como en la base 10 debido al factor primo faltante de 5. Al igual que 1/3 toma un número infinito de dígitos para representar en decimal, pero es "0.1" en la base 3, 0.1 toma un número infinito de dígitos en base-2 donde no lo hace en base-10. Y las computadoras no tienen una cantidad infinita de memoria.
fuente
Además de las otras respuestas correctas, es posible que desee considerar escalar sus valores para evitar problemas con la aritmética de punto flotante.
Por ejemplo:
... en lugar de:
La expresión
0.1 + 0.2 === 0.3
regresafalse
en JavaScript, pero afortunadamente la aritmética de enteros en coma flotante es exacta, por lo que los errores de representación decimal pueden evitarse mediante el escalado.Como ejemplo práctico, para evitar problemas de punto flotante donde la precisión es primordial, se recomienda 1 manejar el dinero como un número entero que representa el número de centavos:
2550
centavos en lugar de25.50
dólares.1 Douglas Crockford: JavaScript: The Good Parts : Apéndice A - Awful Parts (página 105) .
fuente
Mi respuesta es bastante larga, así que la he dividido en tres secciones. Como la pregunta es acerca de las matemáticas de coma flotante, he puesto énfasis en lo que la máquina realmente hace. También lo hice específico para la precisión doble (64 bits), pero el argumento se aplica igualmente a cualquier aritmética de coma flotante.
Preámbulo
Un número de formato de punto flotante binario de doble precisión IEEE 754 (binary64) representa un número de la forma
en 64 bits:
1
si el número es negativo, de lo0
contrario 1 .1.
siempre se omite 2 ya que el bit más significativo de cualquier valor binario es1
.1 - IEEE 754 permite el concepto de un cero con signo -
+0
y-0
se tratan de manera diferente:1 / (+0)
es infinito positivo;1 / (-0)
Es infinito negativo. Para valores cero, los bits de mantisa y exponente son todos cero. Nota: los valores cero (+0 y -0) no se clasifican explícitamente como denormal 2 .2 - Este no es el caso para los números denormales , que tienen un exponente de compensación de cero (y un implícito
0.
). El rango de números de precisión doble denormal es d min ≤ | x | ≤ d max , donde d min (el número distinto de cero representable más pequeño) es 2 -1023-51 (≈ 4.94 * 10 -324 ) y d max (el número denormal más grande, para el cual la mantisa consiste completamente de1
s) es 2 -1023 + 1 - 2 - 1023 - 51 (≈ 2.225 * 10 - 308 ).Convertir un número de doble precisión en binario
Existen muchos convertidores en línea para convertir un número de coma flotante de doble precisión a binario (por ejemplo, en binaryconvert.com ), pero aquí hay un código C # de muestra para obtener la representación IEEE 754 para un número de doble precisión (separo las tres partes con dos puntos (
:
) :Llegando al punto: la pregunta original
(Salte al final para la versión TL; DR)
Cato Johnston (el que pregunta) preguntó por qué 0.1 + 0.2! = 0.3.
Escritas en binario (con dos puntos que separan las tres partes), las representaciones IEEE 754 de los valores son:
Tenga en cuenta que la mantisa se compone de dígitos recurrentes de
0011
. Esta es la clave de por qué hay algún error en los cálculos: 0.1, 0.2 y 0.3 no se pueden representar en binario precisamente en un número finito de bits binarios, no más de 1/9, 1/3 o 1/7 se pueden representar con precisión en dígitos decimales .También tenga en cuenta que podemos disminuir la potencia en el exponente en 52 y desplazar el punto en la representación binaria a la derecha en 52 lugares (al igual que 10-3 * 1.23 == 10-5 * 123). Esto nos permite representar la representación binaria como el valor exacto que representa en la forma a * 2 p . donde 'a' es un número entero.
Convirtiendo los exponentes a decimal, eliminando el desplazamiento y volviendo a agregar el implícito
1
(entre corchetes), 0.1 y 0.2 son:Para sumar dos números, el exponente debe ser el mismo, es decir:
Como la suma no es de la forma 2 n * 1. {bbb} aumentamos el exponente en uno y desplazamos el punto decimal ( binario ) para obtener:
Ahora hay 53 bits en la mantisa (la 53 está entre corchetes en la línea de arriba). El modo de redondeo predeterminado para IEEE 754 es ' Redondear al más cercano ', es decir, si un número x cae entre dos valores a y b , se elige el valor donde el bit menos significativo es cero.
Tenga en cuenta que una y B difieren sólo en el último bit;
...0011
+1
=...0100
. En este caso, el valor con el bit menos significativo de cero es b , entonces la suma es:mientras que la representación binaria de 0.3 es:
que solo difiere de la representación binaria de la suma de 0.1 y 0.2 por 2 -54 .
La representación binaria de 0.1 y 0.2 son las representaciones más precisas de los números permitidos por IEEE 754. La adición de estas representaciones, debido al modo de redondeo predeterminado, da como resultado un valor que difiere solo en el bit menos significativo.
TL; DR
Escribiendo
0.1 + 0.2
en una representación binaria IEEE 754 (con dos puntos que separan las tres partes) y comparándolo0.3
, esto es (he puesto los distintos bits entre corchetes):Convertidos de nuevo a decimal, estos valores son:
La diferencia es exactamente 2 -54 , que es ~ 5.5511151231258 × 10 -17 - insignificante (para muchas aplicaciones) cuando se compara con los valores originales.
Comparar los últimos bits de un número de coma flotante es inherentemente peligroso, como lo sabrá cualquiera que lea el famoso " Lo que todo informático debe saber sobre la aritmética de coma flotante " (que cubre todas las partes principales de esta respuesta).
La mayoría de las calculadoras usan dígitos de protección adicionales para solucionar este problema, que es cómo
0.1 + 0.2
daría0.3
: los últimos bits se redondean.fuente
Los números de coma flotante almacenados en la computadora constan de dos partes, un número entero y un exponente al que se toma la base y se multiplica por la parte entera.
Si la computadora funcionara en la base 10,
0.1
sería1 x 10⁻¹
,0.2
sería2 x 10⁻¹
y0.3
sería3 x 10⁻¹
. Operaciones aritméticas con enteros es fácil y exacta, por lo que añadir0.1 + 0.2
, obviamente, tendrá como resultado0.3
.Las computadoras no suelen funcionar en la base 10, funcionan en la base 2. Aún puede obtener resultados exactos para algunos valores, por ejemplo,
0.5
is1 x 2⁻¹
y0.25
is1 x 2⁻²
, y al agregarlos se obtiene en3 x 2⁻²
, o0.75
. Exactamente.El problema viene con números que se pueden representar exactamente en la base 10, pero no en la base 2. Esos números deben redondearse a su equivalente más cercano. Suponiendo el muy común formato de coma flotante IEEE de 64 bits, el número más cercano a
0.1
es3602879701896397 x 2⁻⁵⁵
y el número más cercano a0.2
es7205759403792794 x 2⁻⁵⁵
; sumarlos juntos da como resultado10808639105689191 x 2⁻⁵⁵
, o un valor decimal exacto de0.3000000000000000444089209850062616169452667236328125
. Los números de punto flotante generalmente se redondean para su visualización.fuente
Error de redondeo de punto flotante. De lo que todo informático debe saber sobre la aritmética de coma flotante :
fuente
Mi solución alternativa:
La precisión se refiere al número de dígitos que desea conservar después del punto decimal durante la suma.
fuente
Se han publicado muchas buenas respuestas, pero me gustaría agregar una más.
No todos los números pueden representarse mediante flotantes / dobles. Por ejemplo, el número "0.2" se representará como "0.200000003" en precisión simple en el estándar de coma flotante IEEE754.
El modelo para los números reales de la tienda debajo del capó representa los números flotantes como
Aunque puede escribir
0.2
fácilmente,FLT_RADIX
yDBL_RADIX
es 2; no 10 para una computadora con FPU que utiliza el "Estándar IEEE para aritmética de punto flotante binario (ISO / IEEE Std 754-1985)".Por lo tanto, es un poco difícil representar tales números exactamente. Incluso si especifica esta variable explícitamente sin ningún cálculo intermedio.
fuente
Algunas estadísticas relacionadas con esta famosa pregunta de doble precisión.
Al agregar todos los valores ( a + b ) usando un paso de 0.1 (de 0.1 a 100) tenemos ~ 15% de probabilidad de error de precisión . Tenga en cuenta que el error podría generar valores ligeramente mayores o menores. Aquí hay unos ejemplos:
Al restar todos los valores ( a - b donde a> b ) usando un paso de 0.1 (de 100 a 0.1) tenemos ~ 34% de probabilidad de error de precisión . Aquí hay unos ejemplos:
* 15% y 34% son realmente enormes, así que siempre use BigDecimal cuando la precisión es de gran importancia. Con 2 dígitos decimales (paso 0.01) la situación empeora un poco más (18% y 36%).
fuente
No, no está roto, pero la mayoría de las fracciones decimales deben ser aproximadas
La aritmética de coma flotante es exacta, desafortunadamente, no coincide bien con nuestra representación habitual de números de base 10, por lo que resulta que a menudo le damos una entrada ligeramente diferente de lo que escribimos.
Incluso los números simples como 0.01, 0.02, 0.03, 0.04 ... 0.24 no son representables exactamente como fracciones binarias. Si cuenta hasta 0.01, .02, .03 ..., no hasta que llegue a 0.25 obtendrá la primera fracción representable en la base 2 . Si lo intentaste con FP, tu 0.01 habría estado ligeramente apagado, por lo que la única forma de sumar 25 de ellos a un buen 0.25 exacto habría requerido una larga cadena de causalidad que involucra bits de guarda y redondeo. Es difícil de predecir, así que levantamos las manos y decimos "FP no es exacto", pero eso no es realmente cierto.
Constantemente le damos al hardware FP algo que parece simple en la base 10 pero que es una fracción repetitiva en la base 2.
Cuando escribimos en decimal, cada fracción (específicamente, cada decimal final) es un número racional de la forma
a / (2 n x 5 m )
En binario, solo obtenemos el término 2 n , es decir:
a / 2 n
Así que en decimal, no podemos representar 1 / 3 . Como la base 10 incluye 2 como factor primo, cada número que podemos escribir como fracción binaria también se puede escribir como una fracción base 10. Sin embargo, casi nada de lo que escribimos como una fracción base 10 es representable en binario. En el rango de 0.01, 0.02, 0.03 ... 0.99, solo se pueden representar tres números en nuestro formato FP: 0.25, 0.50 y 0.75, porque son 1/4, 1/2 y 3/4, todos los números con un factor primo usando solo el término 2 n .
En la base 10 que no podemos representar 1 / 3 . Pero en binario, no podemos hacer 1 / 10 o 1 / 3 .
Entonces, si bien cada fracción binaria se puede escribir en decimal, lo contrario no es cierto. Y, de hecho, la mayoría de las fracciones decimales se repiten en binario.
Los desarrolladores generalmente reciben instrucciones de hacer < comparaciones epsilon , un mejor consejo podría ser redondear a valores integrales (en la biblioteca C: round () y roundf (), es decir, permanecer en el formato FP) y luego comparar. El redondeo a una longitud de fracción decimal específica resuelve la mayoría de los problemas con la salida.
Además, en los problemas de cálculo de números reales (los problemas para los que se inventó la FP en computadoras antiguas y terriblemente caras), las constantes físicas del universo y todas las demás mediciones solo son conocidas por un número relativamente pequeño de cifras significativas, por lo que todo el espacio del problema fue "inexacto" de todos modos. La "precisión" de FP no es un problema en este tipo de aplicación.
Todo el problema surge realmente cuando las personas intentan usar FP para el conteo de frijoles. Funciona para eso, pero solo si te apegas a los valores integrales, lo que de alguna manera frustra el punto de usarlo. Es por eso que tenemos todas esas bibliotecas de software de fracción decimal.
Me encanta la respuesta de Chris de Pizza , porque describe el problema real, no solo el saludo habitual sobre la "inexactitud". Si la PF fuera simplemente "inexacta", podríamos arreglar eso y lo habríamos hecho hace décadas. La razón por la que no lo hemos hecho es porque el formato FP es compacto y rápido y es la mejor manera de calcular muchos números. Además, es un legado de la era espacial y la carrera armamentista y los primeros intentos de resolver grandes problemas con computadoras muy lentas que usan sistemas de memoria pequeños. (A veces, núcleos magnéticos individuales para almacenamiento de 1 bit, pero esa es otra historia ) .
Si solo está contando frijoles en un banco, las soluciones de software que utilizan representaciones de cadenas decimales en primer lugar funcionan perfectamente bien. Pero no se puede hacer la cromodinámica cuántica o la aerodinámica de esa manera.
fuente
nextafter()
con un incremento o decremento entero en la representación binaria de un flotante IEEE. Además, puede comparar flotantes como enteros y obtener la respuesta correcta, excepto cuando ambos son negativos (debido a la magnitud del signo frente al complemento de 2).¿Probaste la solución de cinta adhesiva?
Trate de determinar cuándo ocurren los errores y corríjalos con sentencias if cortas, no es bonito pero para algunos problemas es la única solución y esta es una de ellas.
Tuve el mismo problema en un proyecto de simulación científica en C #, y puedo decirte que si ignoras el efecto mariposa, se convertirá en un gran dragón gordo y te morderá en el a **
fuente
Para ofrecer la mejor solución que puedo decir, descubrí el siguiente método:
Déjame explicarte por qué es la mejor solución. Como otros mencionaron en las respuestas anteriores, es una buena idea usar la función JavaScript toFixed () lista para usar para resolver el problema. Pero lo más probable es que te encuentres con algunos problemas.
Imagínese que usted va a sumar dos números flotantes como
0.2
y0.7
aquí está:0.2 + 0.7 = 0.8999999999999999
.Su resultado esperado fue
0.9
que significa que necesita un resultado con precisión de 1 dígito en este caso. Entonces debería haber usado,(0.2 + 0.7).tofixed(1)
pero no puede simplemente dar un cierto parámetro a toFixed () ya que depende del número dado, por ejemploEn este ejemplo, necesita una precisión de 2 dígitos, por lo que debería ser
toFixed(2)
, ¿cuál debería ser el parámetro para adaptarse a cada número flotante?Podría decir que sea 10 en cada situación:
¡Maldición! ¿Qué vas a hacer con esos ceros no deseados después de las 9? Es el momento de convertirlo en flotante para hacerlo como desee:
Ahora que ha encontrado la solución, es mejor ofrecerla como una función como esta:
Probémoslo tú mismo:
Puedes usarlo de esta manera:
Como W3SCHOOLS sugiere que también hay otra solución, puede multiplicar y dividir para resolver el problema anterior:
¡Tenga en cuenta que
(0.2 + 0.1) * 10 / 10
no funcionará en absoluto aunque parezca lo mismo! Prefiero la primera solución, ya que puedo aplicarla como una función que convierte la entrada flotante en salida precisa.fuente
Esos números extraños aparecen porque las computadoras usan el sistema de números binarios (base 2) para fines de cálculo, mientras que nosotros usamos el decimal (base 10).
Hay una mayoría de números fraccionarios que no se pueden representar con precisión ni en binario ni en decimal ni en ambos. Resultado: resultados de un número redondeado (pero preciso).
fuente
Muchos de los numerosos duplicados de esta pregunta se refieren a los efectos del redondeo de coma flotante en números específicos. En la práctica, es más fácil tener una idea de cómo funciona mirando los resultados exactos de los cálculos de interés en lugar de simplemente leer sobre ello. Algunos lenguajes proporcionan maneras de hacerlo - como la conversión de una
float
odouble
deBigDecimal
Java.Dado que esta es una pregunta independiente del idioma, necesita herramientas independientes del idioma, como un convertidor de decimal a coma flotante .
Aplicandolo a los números en la pregunta, tratados como dobles:
0.1 se convierte en 0.1000000000000000055511151231257827021181583404541015625,
0.2 se convierte en 0.200000000000000011102230246251565404236316680908203125,
0.3 se convierte en 0.299999999999999988897769753748434595763683319091796875, y
0.30000000000000004 se convierte a 0.3000000000000000444089209850062616169452667236328125.
Agregar los primeros dos números manualmente o en una calculadora decimal, como la Calculadora de precisión completa , muestra que la suma exacta de las entradas reales es 0.3000000000000000166533453693773481063544750213623046875.
Si se redondeara al equivalente de 0.3, el error de redondeo sería 0.0000000000000000277555756156289135105907917022705078125. Redondear al equivalente de 0.30000000000000004 también da un error de redondeo 0.0000000000000000277555756156289135105907917022705078125. Se aplica el disyuntor redondo a par.
Volviendo al convertidor de coma flotante, el hexadecimal bruto para 0.30000000000000004 es 3fd3333333333334, que termina en un dígito par y, por lo tanto, es el resultado correcto.
fuente
Dado que nadie ha mencionado esto ...
Algunos lenguajes de alto nivel, como Python y Java, vienen con herramientas para superar las limitaciones de punto flotante binario. Por ejemplo:
El
decimal
módulo de Python y laBigDecimal
clase de Java , que representan números internamente con notación decimal (en oposición a la notación binaria). Ambos tienen una precisión limitada, por lo que siguen siendo propensos a errores, sin embargo, resuelven los problemas más comunes con la aritmética de coma flotante binaria.Los decimales son muy buenos cuando se trata de dinero: diez centavos más veinte centavos son siempre exactamente treinta centavos:
El
decimal
módulo de Python se basa en el estándar IEEE 854-1987 .fractions
Módulo de Python yBigFraction
clase de Apache Common . Ambos representan números racionales como(numerator, denominator)
pares y pueden dar resultados más precisos que la aritmética de coma flotante decimal.Ninguna de estas soluciones es perfecta (especialmente si observamos las actuaciones, o si requerimos una precisión muy alta), pero aún así resuelven una gran cantidad de problemas con la aritmética de coma flotante binaria.
fuente
¿Puedo agregar? la gente siempre asume que se trata de un problema informático, pero si cuenta con las manos (base 10), no puede obtener a
(1/3+1/3=2/3)=true
menos que tenga infinito para agregar 0.333 ... a 0.333 ... así como con el(1/10+2/10)!==3/10
problema en la base 2, lo trunca a 0.333 + 0.333 = 0.666 y probablemente lo redondea a 0.667, lo que también sería técnicamente inexacto.Cuente en ternario, y los tercios no son un problema, sin embargo, tal vez una raza con 15 dedos en cada mano pregunte por qué su matemática decimal se rompió ...
fuente
El tipo de matemática de punto flotante que se puede implementar en una computadora digital necesariamente usa una aproximación de los números reales y las operaciones en ellos. (La versión estándar tiene más de cincuenta páginas de documentación y tiene un comité para encargarse de sus erratas y mejoras adicionales).
Esta aproximación es una mezcla de aproximaciones de diferentes tipos, cada una de las cuales puede ignorarse o explicarse cuidadosamente debido a su forma específica de desviación de la exactitud. También implica una serie de casos excepcionales explícitos, tanto a nivel de hardware como de software, que la mayoría de las personas pasan por delante mientras fingen no darse cuenta.
Si necesita una precisión infinita (usando el número π, por ejemplo, en lugar de uno de sus muchos sustitutos más cortos), debe escribir o usar un programa matemático simbólico.
Pero si está de acuerdo con la idea de que a veces las matemáticas de punto flotante tienen un valor difuso y la lógica y los errores pueden acumularse rápidamente, y puede escribir sus requisitos y pruebas para permitir eso, entonces su código puede pasar con frecuencia con lo que está en su FPU
fuente
Solo por diversión, jugué con la representación de flotadores, siguiendo las definiciones del Estándar C99 y escribí el código a continuación.
El código imprime la representación binaria de flotantes en 3 grupos separados.
y después de eso imprime una suma que, cuando se suma con suficiente precisión, mostrará el valor que realmente existe en el hardware.
Entonces, cuando escribe
float x = 999...
, el compilador transformará ese número en una representación de bits impresa por la función dexx
modo que la suma impresa por la funciónyy
sea igual al número dado.En realidad, esta suma es solo una aproximación. Para el número 999,999,999, el compilador insertará en representación de bits del flotador el número 1,000,000,000
Después del código, adjunto una sesión de consola, en la que calculo la suma de términos para ambas constantes (menos PI y 999999999) que realmente existe en el hardware, insertada allí por el compilador.
Aquí hay una sesión de consola en la que calculo el valor real del flotante que existe en el hardware. Solía
bc
imprimir la suma de términos generados por el programa principal. Uno puede insertar esa suma en Pythonrepl
o algo similar también.Eso es. El valor de 999999999 es de hecho
También puede verificar
bc
que -3.14 también está perturbado. No olvides establecer unscale
factorbc
.La suma mostrada es lo que hay dentro del hardware. El valor que obtienes al calcularlo depende de la escala que establezcas. Establecí el
scale
factor en 15. Matemáticamente, con una precisión infinita, parece que es 1,000,000,000.fuente
Otra forma de ver esto: se usan 64 bits para representar números. Como consecuencia, no hay más de 2 ** 64 = 18,446,744,073,709,551,616 números diferentes que se pueden representar con precisión.
Sin embargo, Math dice que ya hay infinitos decimales entre 0 y 1. IEE 754 define una codificación para usar estos 64 bits de manera eficiente para un espacio numérico mucho mayor más NaN y +/- Infinito, por lo que hay espacios entre los números representados con precisión llenos de números solo aproximados.
Lamentablemente 0.3 se encuentra en un hueco.
fuente
Imagine trabajar en base diez con, digamos, 8 dígitos de precisión. Usted verifica si
y aprende que esto vuelve
false
. ¿Por qué? Bueno, como números reales tenemos1/3 = 0.333 .... y 2/3 = 0.666 ....
Truncando en ocho decimales, obtenemos
que es, por supuesto, diferente de
1.00000000
exactamente0.00000001
.La situación para los números binarios con un número fijo de bits es exactamente análoga. Como números reales, tenemos
1/10 = 0.0001100110011001100 ... (base 2)
y
1/5 = 0.0011001100110011001 ... (base 2)
Si los truncamos a, digamos, siete bits, obtendríamos
mientras que por otro lado,
3/10 = 0.01001100110011 ... (base 2)
que, truncado a siete bits, es
0.0100110
, y estos difieren exactamente0.0000001
.La situación exacta es un poco más sutil porque estos números generalmente se almacenan en notación científica. Entonces, por ejemplo, en lugar de almacenar 1/10 ya
0.0001100
que podemos almacenarlo como algo así1.10011 * 2^-4
, dependiendo de cuántos bits hayamos asignado para el exponente y la mantisa. Esto afecta cuántos dígitos de precisión obtienes para tus cálculos.El resultado es que, debido a estos errores de redondeo, esencialmente nunca desea usar == en números de punto flotante. En cambio, puede verificar si el valor absoluto de su diferencia es menor que algún número pequeño fijo.
fuente
Desde Python 3.5 puede usar la
math.isclose()
función para probar la igualdad aproximada:fuente
Dado que este hilo se ramificó un poco en una discusión general sobre las implementaciones actuales de coma flotante, agregaría que hay proyectos para solucionar sus problemas.
Eche un vistazo a https://posithub.org/ por ejemplo, que muestra un tipo de número llamado posit (y su predecesor unum) que promete ofrecer una mejor precisión con menos bits. Si mi comprensión es correcta, también soluciona el tipo de problemas en la pregunta. Proyecto bastante interesante, la persona detrás de él es un matemático que es el Dr. John Gustafson . Todo es de código abierto, con muchas implementaciones reales en C / C ++, Python, Julia y C # ( https://hastlayer.com/arithmetics ).
fuente
Desde https://0.30000000000000004.com/
fuente
Los números decimales como
0.1
,0.2
y0.3
no se representan exactamente en tipos de coma flotante codificados en binario. La suma de las aproximaciones para0.1
y0.2
difiere de la aproximación utilizada para0.3
, de ahí la falsedad de0.1 + 0.2 == 0.3
como se puede ver más claramente aquí:Salida:
Para que estos cálculos se evalúen de manera más confiable, necesitaría usar una representación basada en decimales para los valores de coma flotante. El Estándar C no especifica dichos tipos por defecto, sino como una extensión descrita en un Informe técnico .
Las
_Decimal32
,_Decimal64
y los_Decimal128
tipos podrían estar disponibles en su sistema (por ejemplo, GCC les apoya en objetivos seleccionados , pero Clang no los admite en OS X ).fuente
Math.sum (javascript) ... tipo de reemplazo del operador
la idea es usar operadores matemáticos en su lugar para evitar errores de flotación
Math.sum detecta automáticamente la precisión para usar
Math.sum acepta cualquier número de argumentos
fuente
Acabo de ver este interesante tema en torno a los puntos flotantes:
Considere los siguientes resultados:
Podemos ver claramente un punto de interrupción cuando
2**53+1
: todo funciona bien hasta2**53
.Esto sucede debido al binario de doble precisión: formato de punto flotante binario de doble precisión IEEE 754: binario64
Desde la página de Wikipedia para el formato de coma flotante de precisión doble :
Gracias a @a_guest por señalarme eso.
fuente
Una pregunta diferente ha sido nombrada como un duplicado de esta:
En C ++, ¿por qué el resultado es
cout << x
diferente del valor que muestra un depuradorx
?El
x
en la pregunta es unafloat
variable.Un ejemplo sería
El depurador muestra
9.89999962
, la salida de lacout
operación es9.9
.La respuesta es que
cout
la precisión predeterminada parafloat
6 es, por lo que se redondea a 6 dígitos decimales.Ver aquí para referencia
fuente