¿Por qué agregar 0.1 varias veces permanece sin pérdidas?

10 minutos de lectura

avatar de usuario
icza

Sé que el 0.1 número decimal no se puede representar exactamente con un número binario finito (explicación), entonces double n = 0.1 perderá algo de precisión y no será exactamente 0.1. Por otro lado 0.5 se puede representar exactamente porque es 0.5 = 1/2 = 0.1b.

Habiendo dicho eso, es comprensible que agregar 0.1 tres veces no dará exactamente 0.3 por lo que se imprime el siguiente código false:

double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
    sum += d;
System.out.println(sum == 0.3); // Prints false, OK

Pero entonces ¿cómo es que sumando 0.1 cinco veces dará exactamente 0.5? El siguiente código se imprime true:

double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
    sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?

Si 0.1 no se puede representar exactamente, ¿cómo es que sumando 5 veces da exactamente 0.5 que se puede representar con precisión?

  • Si realmente lo investiga, estoy seguro de que puede resolverlo, pero el punto flotante está lleno de “sorpresas”, y a veces es mejor simplemente mirar con asombro.

    – Lame caliente

    30 de septiembre de 2014 a las 12:11

  • Estás pensando en esto de una manera matemática. La aritmética de punto flotante no es matemática de ninguna manera.

    – Jakob

    1 de octubre de 2014 a las 5:57

  • @HotLicks eso es muy mucho la actitud equivocada de tener.

    – Hobbs

    2 de octubre de 2014 a las 7:12

  • @RussellBorogove, incluso si se optimizara, solo sería una optimización válida si sum tenía el mismo valor final que si el bucle se hubiera ejecutado realmente. En el estándar de C++, esto se denomina “regla como si” o “mismo comportamiento observable”.

    – Hobbs

    2 de octubre de 2014 a las 7:42

  • @Jakob no es cierto en absoluto. La aritmética de coma flotante se define rigurosamente, con un buen tratamiento matemático de los límites de error y demás. Es solo que muchos programadores no están dispuestos a continuar con el análisis, o creen erróneamente que “el punto flotante es inexacto” es todo lo que hay que saber y que no vale la pena molestarse con el análisis.

    – Hobbs

    2 de octubre de 2014 a las 7:49

avatar de usuario
pedro laurey

El error de redondeo no es aleatorio y la forma en que se implementa intenta minimizar el error. Esto significa que a veces el error no es visible o no hay error.

Por ejemplo 0.1 no es exactamente 0.1 es decir new BigDecimal("0.1") < new BigDecimal(0.1) pero 0.5 es exactamente 1.0/2

Este programa le muestra los verdaderos valores involucrados.

BigDecimal _0_1 = new BigDecimal(0.1);
BigDecimal x = _0_1;
for(int i = 1; i <= 10; i ++) {
    System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue());
    x = x.add(_0_1);
}

huellas dactilares

0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0

Tenga en cuenta que 0.3 está un poco apagado, pero cuando llegas a 0.4 los bits deben desplazarse uno hacia abajo para ajustarse al límite de 53 bits y el error se descarta. Una vez más, un error vuelve a aparecer para 0.6 y 0.7 pero para 0.8 para 1.0 el error se descarta.

Agregarlo 5 veces debería acumular el error, no cancelarlo.

La razón por la que hay un error se debe a la precisión limitada. es decir, 53 bits. Esto significa que a medida que el número usa más bits a medida que crece, los bits deben eliminarse al final. Esto provoca un redondeo que en este caso es a su favor.
Puede obtener el efecto contrario al obtener un número más pequeño, por ejemplo 0.1-0.0999 => 1.0000000000000286E-4
y ves más errores que antes.

Un ejemplo de esto es por qué en Java 6 ¿Por qué Math.round (0.49999999999999994) devuelve 1? En este caso, la pérdida de un bit en el cálculo da como resultado una gran diferencia en la respuesta.

  • ¿Dónde se implementa esto?

    – EpicPandaForce

    30 de septiembre de 2014 a las 12:08

  • @Zhuinden La CPU sigue el estándar IEEE-754. Java le da acceso a las instrucciones subyacentes de la CPU y no se involucra. en.wikipedia.org/wiki/IEEE_punto_flotante

    – Peter Lawrey

    30 de septiembre de 2014 a las 12:10

  • @PeterLawrey: No necesariamente la CPU. En una máquina sin punto flotante en la CPU (y sin FPU separada en uso), la aritmética IEEE será realizada por software. Y si la CPU host tiene un punto flotante pero no cumple con los requisitos de IEEE, creo que una implementación de Java para esa CPU también estaría obligada a usar un punto flotante…

    – R.. GitHub DEJA DE AYUDAR A ICE

    30/09/2014 a las 20:29

  • @R.. en cuyo caso no sé qué pasaría si usaras strictfp Creo que es hora de considerar enteros de punto fijo. (o decimal grande)

    – Peter Lawrey

    30/09/2014 a las 20:32

  • @eugene, el problema clave son los valores limitados que puede representar el punto flotante. Esta limitación puede dar como resultado una pérdida de información y, a medida que aumenta el número, una pérdida de errores. Utiliza el redondeo, pero en este caso, redondea hacia abajo, por lo que lo que hubiera sido un número que es un poco demasiado grande como 0,1 es un poco demasiado grande, se convierte en el valor correcto. exactamente 0.5

    – Peter Lawrey

    1 de octubre de 2014 a las 6:56

avatar de usuario
Pascal Cuoq

Salvo desbordamiento, en punto flotante, x + x + x es exactamente el número de coma flotante correctamente redondeado (es decir, el más cercano) al 3* realx, x + x + x + x es exactamente 4*xy x + x + x + x + x es de nuevo la aproximación de coma flotante correctamente redondeada para 5*x.

El primer resultado, por x + x + xse deriva del hecho de que x + x es exacto x + x + x es por tanto el resultado de un solo redondeo.

El segundo resultado es más difícil, se discute una demostración del mismo. aquí (y Stephen Canon alude a otra prueba por análisis de caso en los últimos 3 dígitos de x). Para resumir, ya sea 3*x esta en lo mismo binada como 2*x o está en el mismo binario que 4*xy en cada caso se puede deducir que el error de la tercera suma cancela el error de la segunda (siendo exacta la primera, como ya dijimos).

El tercer resultado, “x + x + x + x + x está correctamente redondeada”, se deriva de la segunda de la misma manera que la primera se deriva de la exactitud de x + x.


El segundo resultado explica por qué 0.1 + 0.1 + 0.1 + 0.1 es exactamente el número de coma flotante 0.4: los números racionales 1/10 y 4/10 se aproximan de la misma manera, con el mismo error relativo, cuando se convierten a coma flotante. Estos números de punto flotante tienen una razón de exactamente 4 entre ellos. El primer y tercer resultado muestran que 0.1 + 0.1 + 0.1 y 0.1 + 0.1 + 0.1 + 0.1 + 0.1 Se puede esperar que tengan menos error de lo que podría inferirse mediante un análisis de error ingenuo, pero, en sí mismos, solo relacionan los resultados con respectivamente 3 * 0.1 y 5 * 0.1que se puede esperar que sea cercano pero no necesariamente idéntico a 0.3 y 0.5.

Si sigues agregando 0.1 después de la cuarta suma, finalmente observará errores de redondeo que hacen “0.1 añadido a sí mismo n veces” divergen de n * 0.1, y divergen aún más de n/10. Si tuviera que graficar los valores de “0.1 sumado a sí mismo n veces” como una función de n, observaría líneas de pendiente constante por binadas (tan pronto como el resultado de la enésima suma esté destinada a caer en una binada particular, se puede esperar que las propiedades de la adición sean similares a las adiciones anteriores que produjeron un resultado en la misma combinación). Dentro de un mismo binade, el error crecerá o se reducirá. Si observara la secuencia de las pendientes de binada a binada, reconocería los dígitos repetidos de 0.1 en binario por un tiempo. Después de eso, la absorción comenzaría a tener lugar y la curva se aplanaría.

  • En la primera línea, está diciendo que x+x+x es exactamente correcto, pero del ejemplo en la pregunta no lo es.

    – Alboz

    30 de septiembre de 2014 a las 12:15

  • @Alboz yo digo eso x + x + x es exactamente el correcto redondeado número de coma flotante al 3* realx. “correctamente redondeado” significa “más cercano” en este contexto.

    – Pascal Cuoq

    30 de septiembre de 2014 a las 12:22

  • +1 Esta debería ser la respuesta aceptada. En realidad, ofrece explicaciones/pruebas de lo que está pasando en lugar de generalidades vagas.

    – R.. GitHub DEJA DE AYUDAR A ICE

    30/09/2014 a las 20:31

  • @Alboz (todo lo cual está previsto por la pregunta). Pero lo que explica esta respuesta es cómo los errores se cancelan fortuitamente en lugar de sumarse en el peor de los casos.

    – Hobbs

    2 de octubre de 2014 a las 7:37

  • @chebus 0.1 es 0x1.9999999999999999999999…p-4 en hexadecimal (una secuencia infinita de dígitos). Se aproxima en doble precisión como 0x1.99999ap-4. 0.2 es 0x1.999999999999999999999…p-3 en hexadecimal. Por la misma razón que 0.1 se aproxima como 0x1.99999ap-4, 0.2 se aproxima como 0x1.99999ap-3. Mientras tanto, 0x1.99999ap-3 también es exactamente 0x1.99999ap-4+0x1.99999ap-4.

    – Pascal Cuoq

    4 mayo 2016 a las 15:51


Los sistemas de coma flotante hacen varias cosas mágicas, incluida la precisión adicional para el redondeo. Por lo tanto, el error muy pequeño debido a la representación inexacta de 0,1 termina redondeándose a 0,5.

Piense en el punto flotante como una forma excelente pero INEXACTA de representar números. No todos los números posibles se representan fácilmente en una computadora. Números irracionales como PI. O como SQRT(2). (Los sistemas matemáticos simbólicos pueden representarlos, pero dije “fácilmente”).

El valor de punto flotante puede ser extremadamente cercano, pero no exacto. Puede estar tan cerca que podrías navegar a Plutón y desviarte por milímetros. Pero todavía no es exacto en un sentido matemático.

No use punto flotante cuando necesite ser exacto en lugar de aproximado. Por ejemplo, las aplicaciones de contabilidad quieren realizar un seguimiento exacto de una cierta cantidad de centavos en una cuenta. Los números enteros son buenos para eso porque son exactos. El problema principal que debe tener en cuenta con los números enteros es el desbordamiento.

El uso de BigDecimal para la moneda funciona bien porque la representación subyacente es un número entero, aunque grande.

Reconociendo que los números de punto flotante son inexactos, todavía tienen muchos usos. Sistemas de coordenadas para navegación o coordenadas en sistemas gráficos. Valores astronómicos. Valores científicos. (Probablemente no puedas conocer la masa exacta de una pelota de béisbol dentro de la masa de un electrón de todos modos, por lo que la inexactitud realmente no importa).

Para aplicaciones de conteo (incluida la contabilidad), use un número entero. Para contar el número de personas que pasan por una puerta, use int o long.

  • la pregunta esta etiquetada [java]. La definición del lenguaje Java tiene no provisión para “pocos bits extra de precisión”, solo para unos pocos bits extra de exponente (y eso es solo si no usa strictfp). Que hayas renunciado a comprender algo no significa que sea insondable ni que los demás deban renunciar a comprenderlo. Consulte stackoverflow.com/questions/18496560 como un ejemplo de las longitudes a las que llegarán las implementaciones de Java para implementar la definición del lenguaje (que no incluye ninguna provisión para bits de precisión adicionales ni, con strictfppara cualquier bit de exp extra)

    – Pascal Cuoq

    5 oct 2014 a las 23:24


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad