¿Las matemáticas de punto flotante están rotas?

Considera el siguiente código:

0.1 + 0.2 == 0.3 -> false 
 0.1 + 0.2 -> 0.30000000000000004 

¿Por qué ocurren estas imprecisiones?

La matemática de punto flotante binario es así. En la mayoría de los lenguajes de progtwigción, se basa en el estándar IEEE 754 . JavaScript usa representación de coma flotante de 64 bits, que es lo mismo que el double de Java. 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 , que es 1/10 ) cuyo denominador no es una potencia de dos no pueden representarse exactamente.

Para 0.1 en el formato estándar binary64 , la representación se puede escribir exactamente como

  • 0.1000000000000000055511151231257827021181583404541015625 en decimal, o
  • 0x1.999999999999ap-4 en notación CW hexfloat .

Por el contrario, el número racional 0.1 , que es 1/10 , se puede escribir exactamente como

  • 0.1 en decimal, o
  • 0x1.99999999999999...p-4 en una anotación analógica de hexfloat C99, donde ... representa una secuencia interminable de 9.

Las constantes 0.2 y 0.3 en su progtwig también serán aproximaciones a sus valores verdaderos. Sucede que el double más cercano a 0.2 es más grande que el número racional 0.2 pero que el double más cercano a 0.3 es más pequeño que el número racional 0.3 . La sum de 0.1 y 0.2 termina siendo más grande que el número racional 0.3 y por lo tanto está en desacuerdo con la constante en su código.

Un tratamiento bastante completo de los problemas de aritmética de coma flotante es lo que todo científico informático debería saber sobre la aritmética de coma flotante . Para una explicación más fácil de digerir, vea floating-point-gui.de .

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 coma 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 que los errores de punto 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 hace los cálculos de coma flotante solo debe tener un error de menos de la mitad de una unidad en el último lugar. Por lo tanto, mucho hardware se detendrá con una precisión que solo es necesaria para producir un error de menos de la mitad de una unidad en el último lugar para una sola operación que es especialmente problemática en la división de coma flotante. Lo que constituye una sola operación depende de cuántos operandos toma 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 lugar a un error deseable ya que los errores se acumulan con el tiempo.

2. Estándares

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. Lo siguiente, sin embargo, cubrirá el modo normalizado de IEEE-754 que es el modo de operación típico.

En el estándar IEEE-754, los diseñadores de hardware tienen cualquier valor de error / épsilon, siempre y cuando sea menos de la mitad de una unidad en el último lugar, y el resultado solo debe ser menor que la mitad de una unidad en el último lugar para una operación. Esto explica por qué cuando hay operaciones repetidas, los errores se sumn. Para la precisión doble IEEE-754, este es el bit número 54, ya que se utilizan 53 bits para representar la parte numérica (normalizada), también llamada mantisa, del número de punto flotante (por ejemplo, el 5.3 en 5.3e5). Las siguientes secciones entran en 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 principal causa del error en la división de punto flotante son los algoritmos de división utilizados para calcular el cociente. La mayoría de los sistemas computacionales 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 de forma iterativa, es decir, cada ciclo calcula algunos bits del cociente hasta que se alcanza la precisión deseada, 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 del cociente suele ser el ancho del radix, o un número de bits de el cociente calculado en cada iteración, más algunos bits de guardia. Para el estándar IEEE-754, doble precisión (64 bits), sería el tamaño de la base del divisor, más algunos bits de protección k, donde k>=2 . Entonces, por ejemplo, una tabla típica de selección de cocientes para un divisor que compute 2 bits del cociente a la vez (raíz 4) sería 2+2= 4 bits (más algunos bits opcionales).

3.1 Error de redondeo de división: Aproximación de reciprocidad

Los recíprocos que están en la tabla de selección del cociente dependen del método de división : división lenta como división SRT, o división rápida como división Goldschmidt; cada entrada se modifica según el algoritmo de división en un bash de producir el error más bajo posible. En cualquier caso, sin embargo, todos los recíprocos son aproximaciones del recíproco real e introducen algún elemento de error. Tanto la división lenta como los métodos rápidos de división calculan el cociente de forma iterativa, es decir, se calcula un 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 es menos de la mitad de uno unidad en el último lugar. Los métodos de división lenta calculan un número fijo de dígitos del cociente en cada paso y son generalmente menos costosos de construir, y los métodos de división rápida calculan un número variable de dígitos por paso y suelen ser más caros 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 mediante una aproximación recíproca, por lo que son propensos al error.

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 truncado, redondo a cero, redondo a más cercano (predeterminado), redondeo hacia abajo y redondeo. 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 agrega 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 punto flotante solo tiene que arrojar un resultado con un error de menos de la mitad de una unidad en el último lugar para una sola operación, el error boostá con las operaciones repetidas si no se observa. Esta es la razón por la que en los cálculos que requieren un error limitado, los matemáticos utilizan métodos como el uso del dígito par redondeado a 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 754 para predecir errores de redondeo y corregirlos. Debido a su bajo error relativo en comparación con otros modos de redondeo, redondee 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, el dígito par redondeado al 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. Usar el truncamiento, el rodeo y el redondeo solos puede dar como resultado un error que es mayor que la mitad de una unidad en el último lugar, pero menos de una unidad en el último lugar, por lo que estos modos no se recomiendan a menos que sean utilizado en Interval Arithmetic.

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 punto flotante sobre operaciones repetidas se sumrán a menos que se corrijan.

Cuando conviertes .1 o 1/10 en base 2 (binario) obtienes un patrón de repetición después del punto decimal, igual que tratar de representar 1/3 en la base 10. El valor no es exacto, y por lo tanto no puedes hacer matemática exacta usando métodos de coma flotante normales.

La mayoría de las respuestas aquí abordan esta cuestión en términos muy secos y técnicos. Me gustaría abordar esto en términos que los seres humanos normales puedan entender.

Imagina que estás tratando de cortar las pizzas. Usted tiene un cortador de pizza robótico que puede cortar rebanadas de pizza exactamente a la mitad. Puede reducir a la mitad una pizza entera, o puede reducir a la mitad una porción existente, pero en cualquier caso, la mitad es siempre exacta.

Ese cortador de pizza tiene movimientos muy finos, y si comienzas con una pizza entera, luego la reduces a la mitad y sigues dividiendo a la mitad la rebanada más pequeña cada vez, puedes hacer la mitad 53 veces antes de que el corte sea demasiado pequeño incluso para sus habilidades de alta precisión . En ese momento, ya no puede dividir a la mitad ese segmento muy fino, pero debe incluirlo o excluirlo tal como está.

Ahora, ¿cómo unirías todas las rebanadas de una manera que sumría una décima parte (0.1) o una quinta parte (0.2) de una pizza? Piensa en ello y trata de resolverlo. Incluso puede intentar usar una pizza de verdad, si tiene a mano un cortador de pizza de precisión mítica. 🙂


La mayoría de los progtwigdores experimentados, por supuesto, conocen la respuesta real, que es que no hay forma de juntar una décima o quinta exacta de la pizza usando esas rebanadas, sin importar qué tan finamente las cortes. Puede hacer una buena aproximación, y si sum la aproximación de 0.1 con la aproximación de 0.2, obtiene una aproximación bastante buena de 0.3, pero sigue siendo eso, una aproximación.

Para números de precisión doble (que es la precisión que le permite dividir la pizza a la mitad 53 veces), los números inmediatamente menores y mayores a 0.1 son 0.09999999999999999167332731531132594682276248931884765625 y 0.1000000000000000055511151231257827021181583404541015625. Este último es bastante más cercano a 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 la “porción más pequeña” que debemos decidir incluir, lo que introduce un sesgo hacia arriba o excluir, lo que introduce un sesgo a la baja. 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. De nuevo, preferimos el valor que es ligeramente mayor que 0.2.

Observe que en ambos casos, las aproximaciones para 0.1 y 0.2 tienen un ligero sesgo ascendente. Si agregamos suficientes 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 particular, 0.1 + 0.2 es realmente 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, mientras que el número más cercano a 0.3 es en realidad 0.299999999999999988897769753748434595763683319091796875.


PS Algunos lenguajes de progtwigción también proporcionan cortadores de pizza que pueden dividir rebanadas en décimas exactas . Aunque estos cortadores de pizza son poco comunes, si tiene acceso a uno, debe usarlo cuando es importante poder obtener exactamente una décima o una quinta parte de un corte.

(Publicado originalmente en Quora).

Errores de redondeo de punto flotante. 0.1 no se puede representar con tanta precisión en base-2 como en base-10 debido al factor primo perdido de 5. Así como 1/3 toma un número infinito de dígitos para representar en decimal, pero es “0.1” en base-3, 0.1 toma una cantidad infinita de dígitos en base-2 donde no lo hace en base-10. Y las computadoras no tienen una cantidad infinita de memoria.

Además de las otras respuestas correctas, es posible que desee considerar ampliar sus valores para evitar problemas con la aritmética de punto flotante.

Por ejemplo:

 var result = 1.0 + 2.0; // result === 3.0 returns true 

… en lugar de:

 var result = 0.1 + 0.2; // result === 0.3 returns false 

La expresión 0.1 + 0.2 === 0.3 devuelve false en JavaScript, pero afortunadamente la aritmética entera en coma flotante es exacta, por lo que los errores de representación decimal se pueden evitar escalando.

Como ejemplo práctico, para evitar problemas de coma 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 de 25.50 dólares.


1 Douglas Crockford: JavaScript: Las partes buenas : Apéndice A: partes terribles (página 105) .

Mi respuesta es bastante larga, así que la he dividido en tres secciones. Dado que la pregunta es acerca de las matemáticas de punto 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 punto flotante.

Preámbulo

Un número de formato de coma flotante binario de doble precisión IEEE 754 (binary64) representa un número de la forma

valor = (-1) ^ s * (1.m 51 m 50 … m 2 m 1 m 0 ) 2 * 2 e-1023

en 64 bits:

  • El primer bit es el bit de signo : 1 si el número es negativo, 0 contrario 1 .
  • Los siguientes 11 bits son el exponente , que se compensa con 1023. En otras palabras, después de leer los bits de exponente de un número de precisión doble, se debe restar 1023 para obtener la potencia de dos.
  • Los 52 bits restantes son el significando (o mantisa). En la mantisa, un 1. implícito es siempre 2 omitido ya que el bit más significativo de cualquier valor binario es 1 .

1 – IEEE 754 permite el concepto de cero firmado – +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 números denormales , que tienen un exponente de desplazamiento de cero (y un implícito 0. ). El rango de números de doble precisión 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 enteramente de 1 s) es 2 – 1023 + 1 – 2 -1023 – 51 (≈ 2.225 * 10 -308 ).


Convertir un número de precisión doble en binario

Existen muchos convertidores en línea para convertir un número de punto flotante de doble precisión en binario (por ejemplo, en binaryconvert.com ), pero aquí hay un código de C # para obtener la representación IEEE 754 para un número de precisión doble (separando las tres partes con dos puntos ( )

 public static string BinaryRepresentation(double value) { long valueInLongType = BitConverter.DoubleToInt64Bits(value); string bits = Convert.ToString(valueInLongType, 2); string leadingZeros = new string('0', 64 - bits.Length); string binaryRepresentation = leadingZeros + bits; string sign = binaryRepresentation[0].ToString(); string exponent = binaryRepresentation.Substring(1, 11); string mantissa = binaryRepresentation.Substring(12); return string.Format("{0}:{1}:{2}", sign, exponent, mantissa); } 

Llegando al punto: la pregunta original

(Salte al final para la versión TL; DR)

Cato Johnston (el preguntador asker) preguntó por qué 0.1 + 0.2! = 0.3.

Escrito en binario (con dos puntos que separan las tres partes), las representaciones de los valores IEEE 754 son:

 0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010 0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010 

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, más de 1/9, 1/3 o 1/7 se pueden representar con precisión en dígitos decimales

Convertir los exponentes en decimal, eliminar el desplazamiento y volver a agregar el 1 implícito (entre corchetes), 0.1 y 0.2 son:

 0.1 = 2^-4 * [1].1001100110011001100110011001100110011001100110011010 0.2 = 2^-3 * [1].1001100110011001100110011001100110011001100110011010 

Para agregar dos números, el exponente debe ser el mismo, es decir:

 0.1 = 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0) 0.2 = 2^-3 * 1.1001100110011001100110011001100110011001100110011010 sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111 

Como la sum no es de la forma 2 n * 1. {bbb} aumentamos el exponente en uno y cambiamos el punto decimal ( binario ) para obtener:

 sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1) 

Ahora hay 53 bits en la mantisa (el 53 está entre corchetes en la línea superior). El modo de redondeo predeterminado para IEEE 754 es ‘De ronda a más cercana ‘, 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.

 a = 2^-2 * 1.0011001100110011001100110011001100110011001100110011 x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1) b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100 

Tenga en cuenta que a y b difieren solo en el último bit; ...0011 + 1 = ...0100 . En este caso, el valor con el bit menos significativo de cero es b , por lo que la sum es:

 sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100 

TL; DR

Escribiendo 0.1 + 0.2 en una representación binaria IEEE 754 (con dos puntos que separan las tres partes) y comparándolo con 0.3 , esto es (he puesto los distintos bits entre corchetes):

 0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100] 0.3 => 0:01111111101:0011001100110011001100110011001100110011001100110[011] 

Convertido de nuevo a decimal, estos valores son:

 0.1 + 0.2 => 0.300000000000000044408920985006... 0.3 => 0.299999999999999988897769753748... 

La diferencia es exactamente 2 -54 , que es ~ 5.5511151231258 × 10 -17 – insignificante (para muchas aplicaciones) en comparación con los valores originales.

Comparar los últimos bits de un número de coma flotante es intrínsecamente peligroso, ya que cualquiera que lea el famoso ” Lo que cada científico debería saber sobre la aritmética de coma flotante ” (que cubre todas las partes principales de esta respuesta) lo sabrá.

La mayoría de las calculadoras usan dígitos de guardia adicionales para evitar este problema, que es como 0.1 + 0.2 daría 0.3 : los últimos bits son redondeados.

Los números de puntos flotantes almacenados en la computadora constan de dos partes, un entero y un exponente al que la base se toma y se multiplica por la parte entera.

Si la computadora estuviera trabajando en la base 10, 0.1 sería 1 x 10⁻¹ , 0.2 sería 2 x 10⁻¹ , y 0.3 sería 3 x 10⁻¹ . La matemática entera es fácil y exacta, por lo que agregar 0.1 + 0.2 obviamente dará como resultado 0.3 .

Las computadoras generalmente no funcionan en la base 10, funcionan en la base 2. Todavía puede obtener resultados exactos para algunos valores, por ejemplo 0.5 es 1 x 2⁻¹ y 0.25 es 1 x 2⁻² , y 1 x 2⁻² resulta en 3 x 2⁻² , o 0.75 . Exactamente.

El problema viene con números que pueden representarse exactamente en la base 10, pero no en la base 2. Esos números deben redondearse a su equivalente más cercano. Suponiendo el formato de punto flotante IEEE de 64 bits muy común, el número más cercano a 0.1 es 3602879701896397 x 2⁻⁵⁵ , y el número más cercano a 0.2 es 7205759403792794 x 2⁻⁵⁵ ; 10808639105689191 x 2⁻⁵⁵ resulta en 10808639105689191 x 2⁻⁵⁵ , o un valor decimal exacto de 0.3000000000000000444089209850062616169452667236328125 . Los números de coma flotante generalmente se redondean para mostrarlos.

Floating point rounding error. From What Every Computer Scientist Should Know About Floating-Point Arithmetic :

Squeezing infinitely many real numbers into a finite number of bits requires an approximate representation. Although there are infinitely many integers, in most programs the result of integer computations can be stored in 32 bits. In contrast, given any fixed number of bits, most calculations with real numbers will produce quantities that cannot be exactly represented using that many bits. Therefore the result of a floating-point calculation must often be rounded in order to fit back into its finite representation. This rounding error is the characteristic feature of floating-point computation.

My workaround:

 function add(a, b, precision) { var x = Math.pow(10, precision || 2); return (Math.round(a * x) + Math.round(b * x)) / x; } 

precision refers to the number of digits you want to preserve after the decimal point during addition.

A lot of good answers have been posted, but I’d like to append one more.

Not all numbers can be represented via floats / doubles For example, the number “0.2” will be represented as “0.200000003” in single precision in IEEE754 float point standard.

Model for store real numbers under the hood represent float numbers as

enter image description here

Even though you can type 0.2 easily, FLT_RADIX and DBL_RADIX is 2; not 10 for a computer with FPU which uses “IEEE Standard for Binary Floating-Point Arithmetic (ISO/IEEE Std 754-1985)”.

So it is a bit hard to represent such numbers exactly. Even if you specify this variable explicitly without any intermediate calculation.

Some statistics related to this famous double precision question.

When adding all values ( a + b ) using a step of 0.1 (from 0.1 to 100) we have ~15% chance of precision error . Note that the error could result in slightly bigger or smaller values. Aquí hay unos ejemplos:

 0.1 + 0.2 = 0.30000000000000004 (BIGGER) 0.1 + 0.7 = 0.7999999999999999 (SMALLER) ... 1.7 + 1.9 = 3.5999999999999996 (SMALLER) 1.7 + 2.2 = 3.9000000000000004 (BIGGER) ... 3.2 + 3.6 = 6.800000000000001 (BIGGER) 3.2 + 4.4 = 7.6000000000000005 (BIGGER) 

When subtracting all values ( a – b where a > b ) using a step of 0.1 (from 100 to 0.1) we have ~34% chance of precision error . Aquí hay unos ejemplos:

 0.6 - 0.2 = 0.39999999999999997 (SMALLER) 0.5 - 0.4 = 0.09999999999999998 (SMALLER) ... 2.1 - 0.2 = 1.9000000000000001 (BIGGER) 2.0 - 1.9 = 0.10000000000000009 (BIGGER) ... 100 - 99.9 = 0.09999999999999432 (SMALLER) 100 - 99.8 = 0.20000000000000284 (BIGGER) 

*15% and 34% are indeed huge, so always use BigDecimal when precision is of big importance. With 2 decimal digits (step 0.01) the situation worsens a bit more (18% and 36%).

No, not broken, but most decimal fractions must be approximated

Resumen

Floating point arithmetic is exact, unfortunately, it doesn’t match up well with our usual base-10 number representation, so it turns out we are often giving it input that is slightly off from what we wrote.

Even simple numbers like 0.01, 0.02, 0.03, 0.04 … 0.24 are not representable exactly as binary fractions, even if you had thousands of bits of precision in the mantissa, even if you had millions. If you count off in 0.01 increments, not until you get to 0.25 will you get the first fraction (in this sequence) representable in base 10 and base 2 . But if you tried that using FP, your 0.01 would have been slightly off, so the only way to add 25 of them up to a nice exact 0.25 would have required a long chain of causality involving guard bits and rounding. It’s hard to predict so we throw up our hands and say “FP is inexact”.

We constantly give the FP hardware something that seems simple in base 10 but is a repeating fraction in base 2.

¿Cómo pasó esto?

When we write in decimal, every fraction is a rational number of the form

x / (2 n + 5 n ).

In binary, we only get the 2 n term, that is:

x / 2 n

So in decimal, we can’t represent 1 / 3 . Because base 10 includes 2 as a prime factor, every number we can write as a binary fraction also can be written as a base 10 fraction. However, hardly anything we write as a base 10 fraction is representable in binary. In the range from 0.01, 0.02, 0.03 … 0.99, only three numbers can be represented in our FP format: 0.25, 0.50, and 0.75, because they are 1/4, 1/2, and 3/4, all numbers with a prime factor using only the 2 n term.

In base 10 we can’t represent 1 / 3 . But in binary, we can’t do 1 / 10 or 1 / 3 .

So while every binary fraction can be written in decimal, the reverse is not true. And in fact most decimal fractions repeat in binary.

Dealing with it

Developers are usually instructed to do < epsilon comparisons, better advice might be to round to integral values (in the C library: round() and roundf(), ie, stay in the FP format) and then compare. Rounding to a specific decimal fraction length solves most problems with output.

Also, on real number-crunching problems (the problems that FP was invented for on early, frightfully expensive computers) the physical constants of the universe and all other measurements are only known to a relatively small number of significant figures, so the entire problem space was “inexact” anyway. FP “accuracy” isn’t a problem in this kind of application.

The whole issue really arises when people try to use FP for bean counting. It does work for that, but only if you stick to integral values, which kind of defeats the point of using it. This is why we have all those decimal fraction software libraries.

I love the Pizza answer by Chris , because it describes the actual problem, not just the usual handwaving about “inaccuracy”. If FP were simply “inaccurate”, we could fix that and would have done it decades ago. The reason we haven’t is because the FP format is compact and fast and it’s the best way to crunch a lot of numbers. Also, it’s a legacy from the space age and arms race and early attempts to solve big problems with very slow computers using small memory systems. (Sometimes, individual magnetic cores for 1-bit storage, but that’s another story. )

Conclusión

If you are just counting beans at a bank, software solutions that use decimal string representations in the first place work perfectly well. But you can’t do quantum chromodynamics or aerodynamics that way.

Did you try the duct tape solution?

Try to determine when errors occur and fix them with short if statements, it’s not pretty but for some problems it is the only solution and this is one of them.

  if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;} else { return n * 0.1 + 0.000000000000001 ;} 

I had the same problem in a scientific simulation project in c#, and I can tell you that if you ignore the butterfly effect it's gonna turn to a big fat dragon and bite you in the a**

Those weird numbers appear because computers use binary(base 2) number system for calculation purposes, while we use decimal(base 10).

There are a majority of fractional numbers that cannot be represented precisely either in binary or in decimal or both. Result – A rounded up (but precise) number results.

Can I just add; people always assume this to be a computer problem, but if you count with your hands (base 10), you can’t get (1/3+1/3=2/3)=true unless you have infinity to add 0.333… to 0.333… so just as with the (1/10+2/10)!==3/10 problem in base 2, you truncate it to 0.333 + 0.333 = 0.666 and probably round it to 0.667 which would be also be technically inaccurate.

Count in ternary, and thirds are not a problem though – maybe some race with 15 fingers on each hand would ask why your decimal math was broken…

Many of this question’s numerous duplicates ask about the effects of floating point rounding on specific numbers. In practice, it is easier to get a feeling for how it works by looking at exact results of calculations of interest rather than by just reading about it. Some languages provide ways of doing that – such as converting a float or double to BigDecimal in Java.

Since this is a language-agnostic question, it needs language-agnostic tools, such as a Decimal to Floating-Point Converter .

Applying it to the numbers in the question, treated as doubles:

0.1 converts to 0.1000000000000000055511151231257827021181583404541015625,

0.2 converts to 0.200000000000000011102230246251565404236316680908203125,

0.3 converts to 0.299999999999999988897769753748434595763683319091796875, and

0.30000000000000004 converts to 0.3000000000000000444089209850062616169452667236328125.

Adding the first two numbers manually or in a decimal calculator such as Full Precision Calculator , shows the exact sum of the actual inputs is 0.3000000000000000166533453693773481063544750213623046875.

If it were rounded down to the equivalent of 0.3 the rounding error would be 0.0000000000000000277555756156289135105907917022705078125. Rounding up to the equivalent of 0.30000000000000004 also gives rounding error 0.0000000000000000277555756156289135105907917022705078125. The round-to-even tie breaker applies.

Returning to the floating point converter, the raw hexadecimal for 0.30000000000000004 is 3fd3333333333334, which ends in an even digit and therefore is the correct result.

Given that nobody has mentioned this…

Some high level languages such as Python and Java come with tools to overcome binary floating point limitations. Por ejemplo:

  • Python’s decimal module and Java’s BigDecimal class , that represent numbers internally with decimal notation (as opposed to binary notation). Both have limited precision, so they are still error prone, however they solve most common problems with binary floating point arithmetic.

    Decimals are very nice when dealing with money: ten cents plus twenty cents are always exactly thirty cents:

     >>> 0.1 + 0.2 == 0.3 False >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3') True 

    Python’s decimal module is based on IEEE standard 854-1987 .

  • Python’s fractions module and Apache Common’s BigFraction class . Both represent rational numbers as (numerator, denominator) pairs and they may give more accurate results than decimal floating point arithmetic.

Neither of these solutions is perfect (especially if we look at performances, or if we require a very high precision), but still they solve a great number of problems with binary floating point arithmetic.

The kind of floating-point math that can be implemented in a digital computer necessarily uses an approximation of the real numbers and operations on them. (The standard version runs to over fifty pages of documentation and has a committee to deal with its errata and further refinement.)

This approximation is a mixture of approximations of different kinds, each of which can either be ignored or carefully accounted for due to its specific manner of deviation from exactitude. It also involves a number of explicit exceptional cases at both the hardware and software levels that most people walk right past while pretending not to notice.

If you need infinite precision (using the number π, for example, instead of one of its many shorter stand-ins), you should write or use a symbolic math program instead.

But if you’re okay with the idea that sometimes floating-point math is fuzzy in value and logic and errors can accumulate quickly, and you can write your requirements and tests to allow for that, then your code can frequently get by with what’s in your FPU.

Just for fun, I played with the representation of floats, following the definitions from the Standard C99 and I wrote the code below.

The code prints the binary representation of floats in 3 separated groups

 SIGN EXPONENT FRACTION 

and after that it prints a sum, that, when summed with enough precision, it will show the value that really exists in hardware.

So when you write float x = 999... , the compiler will transform that number in a bit representation printed by the function xx such that the sum printed by the function yy be equal to the given number.

In reality, this sum is only an approximation. For the number 999,999,999 the compiler will insert in bit representation of the float the number 1,000,000,000

After the code I attach a console session, in which I compute the sum of terms for both constants (minus PI and 999999999) that really exists in hardware, inserted there by the compiler.

 #include  #include  void xx(float *x) { unsigned char i = sizeof(*x)*CHAR_BIT-1; do { switch (i) { case 31: printf("sign:"); break; case 30: printf("exponent:"); break; case 23: printf("fraction:"); break; } char b=(*(unsigned long long*)x&((unsigned long long)1<>23))-127; printf(sign?"positive" " ( 1+":"negative" " ( 1+"); unsigned int i = 1<<22; unsigned int j = 1; do { char b=(fraction&i)!=0; b&&(printf("1/(%d) %c", 1<>=1); printf("*2^%d", exponent); printf("\n"); } void main() { float x=-3.14; float y=999999999; printf("%lu\n", sizeof(x)); xx(&x); xx(&y); yy(x); yy(y); } 

Here is a console session in which I compute the real value of the float that exists in hardware. I used bc to print the sum of terms outputted by the main program. One can insert that sum in python repl or something similar also.

 -- .../terra1/stub @ qemacs fc -- .../terra1/stub @ gcc fc -- .../terra1/stub @ ./a.out sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1 sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0 negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1 positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29 -- .../terra1/stub @ bc scale=15 ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29 999999999.999999446351872 

Eso es. The value of 999999999 is in fact

 999999999.999999446351872 

You can also check with bc that -3.14 is also perturbed. Do not forget to set a scale factor in bc .

The displayed sum is what inside the hardware. The value you obtain by computing it depends on the scale you set. I did set the scale factor to 15. Mathematically, with infinite precision, it seems it is 1,000,000,000.

In order to offer The best solution I can say I discovered following method:

 parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3 

Let me explain why it’s the best solution. As others mentioned in above answers it’s a good idea to use ready to use Javascript toFixed() function to solve the problem. But most likely you’ll encounter with some problems.

Imagine you are going to add up two float numbers like 0.2 and 0.7 here it is: 0.2 + 0.7 = 0.8999999999999999 .

Your expected result was 0.9 it means you need a result with 1 digit precision in this case. So you should have used (0.2 + 0.7).tofixed(1) but you can’t just give a certain parameter to toFixed() since it depends on the given number, for instance

 `0.22 + 0.7 = 0.9199999999999999` 

In this example you need 2 digits precision so it should be toFixed(2) , so what should be the paramter to fit every given float number?

You might say let it be 10 in every situation then:

 (0.2 + 0.7).toFixed(10) => Result will be "0.9000000000" 

¡Maldita sea! What are you going to do with those unwanted zeros after 9? It’s the time to convert it to float to make it as you desire:

 parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9 

Now that you found the solution, it’s better to offer it as a function like this:

 function floatify(number){ return parseFloat((number).toFixed(10)); } function addUp(){ var number1 = +$("#number1").val(); var number2 = +$("#number2").val(); var unexpectedResult = number1 + number2; var expectedResult = floatify(number1 + number2); $("#unexpectedResult").text(unexpectedResult); $("#expectedResult").text(expectedResult); } addUp(); 
 input{ width: 50px; } #expectedResult{ color: green; } #unexpectedResult{ color: red; } 
   +  = 

Expected Result:

Unexpected Result:

Another way to look at this: Used are 64 bits to represent numbers. As consequence there is no way more than 2**64 = 18,446,744,073,709,551,616 different numbers can be precisely represented.

However, Math says there are already infinitely many decimals between 0 and 1. IEE 754 defines an encoding to use these 64 bits efficiently for a much larger number space plus NaN and +/- Infinity, so there are gaps between accurately represented numbers filled with numbers only approximated.

Unfortunately 0.3 sits in a gap.

Since this thread branched off a bit into a general discussion over current floating point implementations I’d add that there are projects on fixing their issues.

Take a look at https://posithub.org/ for example, which showcases a number type called posit (and its predecessor unum) that promises to offer better accuracy with fewer bits. If my understanding is correct, it also fixes the kind of problems in the question. Quite interesting project, the person behind it is a mathematician it Dr. John Gustafson . The whole thing is open source, with many actual implementations in C/C++, Python, Julia and C# ( https://hastlayer.com/arithmetics ).

Math.sum ( javascript ) …. kind of operator replacement

 .1 + .0001 + -.1 --> 0.00010000000000000286 Math.sum(.1 , .0001, -.1) --> 0.0001 

 Object.defineProperties(Math, { sign: { value: function (x) { return x ? x < 0 ? -1 : 1 : 0; } }, precision: { value: function (value, precision, type) { var v = parseFloat(value), p = Math.max(precision, 0) || 0, t = type || 'round'; return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p); } }, scientific_to_num: { // this is from https://gist.github.com/jiggzson value: function (num) { //if the number is in scientific notation remove it if (/e/i.test(num)) { var zero = '0', parts = String(num).toLowerCase().split('e'), //split into coeff and exponent e = parts.pop(), //store the exponential part l = Math.abs(e), //get the number of zeros sign = e / l, coeff_array = parts[0].split('.'); if (sign === -1) { num = zero + '.' + new Array(l).join(zero) + coeff_array.join(''); } else { var dec = coeff_array[1]; if (dec) l = l - dec.length; num = coeff_array.join('') + new Array(l + 1).join(zero); } } return num; } } get_precision: { value: function (number) { var arr = Math.scientific_to_num((number + "")).split("."); return arr[1] ? arr[1].length : 0; } }, diff:{ value: function(A,B){ var prec = this.max(this.get_precision(A),this.get_precision(B)); return +this.precision(AB,prec); } }, sum: { value: function () { var prec = 0, sum = 0; for (var i = 0; i < arguments.length; i++) { prec = this.max(prec, this.get_precision(arguments[i])); sum += +arguments[i]; // force float to convert strings to number } return Math.precision(sum, prec); } } }); 

the idea is to use Math instead operators to avoid float errors

 Math.diff(0.2, 0.11) == 0.09 // true 0.2 - 0.11 == 0.09 // false 

also note that Math.diff and Math.sum auto-detect the precision to use

Math.sum accepts any number of arguments

A different question has been named as a duplicate to this one:

In C++, why is the result of cout << x different from the value that a debugger is showing for x ?

The x in the question is a float variable.

One example would be

 float x = 9.9F; 

The debugger shows 9.89999962 , the output of cout operation is 9.9 .

The answer turns out to be that cout 's default precision for float is 6, so it rounds to 6 decimal digits.

See here for reference

Sine Python 3.5 you can use math.isclose() function in if conditions

 import math if math.isclose(0.1 + 0.2, 0.3, abs_tol=0.01): pass