Sé que el 0.1
número decimal no se puede representar exactamente con un número binario finito ( explicación ), por double n = 0.1
lo que perderá algo de precisión y no será exactamente0.1
. Por otro lado 0.5
se puede representar exactamente porque lo es 0.5 = 1/2 = 0.1b
.
Habiendo dicho eso, es comprensible que sumar 0.1
tres veces no dé 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 sumar 0.1
cinco veces dará exactamente 0.5
? Se imprime el siguiente código 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 sumarlo 5 veces da exactamente lo 0.5
que se puede representar con precisión?
sum
tuviera el mismo valor final que si el bucle se ejecutara realmente. En el estándar C ++, esto se denomina "regla as-if" o "mismo comportamiento observable".Respuestas:
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 exactamente0.1
es decir,new BigDecimal("0.1") < new BigDecimal(0.1)
pero0.5
es exactamente1.0/2
Este programa le muestra los verdaderos valores involucrados.
huellas dactilares
Nota: eso
0.3
está ligeramente desactivado, pero cuando llegas a0.4
los bits tienes que cambiar uno hacia abajo para que quepa en el límite de 53 bits y el error se descarta. Una vez más, un arrastra de error de vuelta en para0.6
y0.7
pero para0.8
que1.0
se descarta el error.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 se hace más grande, los bits deben eliminarse al final. Esto provoca un redondeo que en este caso está a su favor.
Puede obtener el efecto contrario al obtener un número menor, por ejemplo,
0.1-0.0999
=>1.0000000000000286E-4
y ve 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 resulta en una gran diferencia en la respuesta.
fuente
strictfp
Time para considerar enteros de punto fijo, creo. (o BigDecimal)El desbordamiento de restricción, en coma 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 *x
yx + x + x + x + x
nuevamente es la aproximación de coma flotante correctamente redondeada para 5 *x
.El primer resultado, para
x + x + x
, deriva del hecho de quex + x
es exacto.x + x + x
es, por lo tanto, el resultado de un solo redondeo.El segundo resultado es más difícil, una demostración de esto se discute aquí (y Stephen Canon alude a otra prueba por análisis de caso en los últimos 3 dígitos de
x
). Para resumir, 3 *x
está en la misma binada que 2 *x
o está en la misma binada que 4 *x
, y en cada caso es posible deducir que el error en la tercera adición cancela el error en la segunda adición (el La primera adición es exacta, como ya dijimos).El tercer resultado, "
x + x + x + x + x
está correctamente redondeado", deriva del segundo de la misma manera que el primero deriva de la exactitud dex + x
.El segundo resultado explica por qué
0.1 + 0.1 + 0.1 + 0.1
es exactamente el número de coma flotante0.4
: los números racionales 1/10 y 4/10 se aproximan de la misma manera, con el mismo error relativo, cuando se convierten en coma flotante. Estos números de punto flotante tienen una relación de exactamente 4 entre ellos. El primero y tercer resultado muestran que0.1 + 0.1 + 0.1
y0.1 + 0.1 + 0.1 + 0.1 + 0.1
se puede esperar que tengan menor error que podría inferirse por análisis de error ingenuo, pero, en sí mismos, que sólo se refieren los resultados, respectivamente,3 * 0.1
y5 * 0.1
, lo que se puede esperar para estar cerca pero no necesariamente idéntica a0.3
y0.5
.Si sigue sumando
0.1
después de la cuarta adición, finalmente observará errores de redondeo que hacen que "0.1
agregado a sí mismo n veces" difiera en binario durante un tiempo. Después de eso, la absorción comenzaría a tener lugar y la curva se volvería plana.n * 0.1
, y diverja aún más de n / 10. Si tuviera que trazar los valores de "0.1 agregado a sí mismo n veces" en función de n, observaría líneas de pendiente constante por binadas (tan pronto como el resultado de la enésima suma esté destinado 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 binada). Dentro de una misma binada, el error crecerá o disminuirá. Si observara la secuencia de las pendientes de binade a binade, reconocería los dígitos repetidos de0.1
fuente
x + x + x
es exactamente el número de punto flotante correctamente redondeado al 3 * realx
. "Correctamente redondeado" significa "más cercano" en este contexto.Los sistemas de punto flotante hacen varias magias, incluyendo tener algunos bits adicionales de precisión para redondear. Por lo tanto, el error muy pequeño debido a la representación inexacta de 0.1 termina siendo redondeado a 0.5.
Piense en el punto flotante como una excelente pero INEXACTA forma 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 coma flotante puede ser extremadamente cercano, pero no exacto. Puede estar tan cerca que podría navegar a Plutón y estar en milímetros. Pero aún no es exacto en un sentido matemático.
No use coma flotante cuando necesite ser exacto en lugar de aproximado. Por ejemplo, las aplicaciones de contabilidad desean realizar un seguimiento exacto de un cierto número de centavos en una cuenta. Los enteros son buenos para eso porque son exactos. El problema principal que debe tener en cuenta con los enteros es el desbordamiento.
Usar BigDecimal para la moneda funciona bien porque la representación subyacente es un número entero, aunque grande.
Reconociendo que los números de coma 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 (De todos modos, es probable que no se pueda conocer la masa exacta de una pelota de béisbol dentro de una masa de un electrón, por lo que la inexactitud realmente no importa).
Para contar aplicaciones (incluida la contabilidad) use integer. Para contar la cantidad de personas que pasan por una puerta, use int o long.
fuente
strictfp
). El hecho de que haya renunciado a comprender algo no significa que sea insondable ni que otros deban renunciar a comprenderlo. Consulte stackoverflow.com/questions/18496560 como ejemplo de las longitudes a las que irán las implementaciones de Java para implementar la definición del lenguaje (que no incluye ninguna disposición para bits de precisión adicionales ni, constrictfp
, para ningún bit de exp extra)