Desigualdad causada por la inexactitud del flotador

15

Al menos en Java, si escribo este código:

float a = 1000.0F;
float b = 0.00004F;
float c = a + b + b;
float d = b + b + a;
boolean e = c == d;

El valor de sería . Creo que esto se debe al hecho de que los flotadores son muy limitados en la forma de representar con precisión los números. Pero no entiendo por qué solo cambiar la posición de podría causar esta desigualdad.miFunlsmiun

Reduje la s a uno en las líneas 3 y 4 como se muestra a continuación, sin embargo , el valor de vuelve :simitrtumi

float a = 1000.0F;
float b = 0.00004F;
float c = a + b;
float d = b + a;
boolean e = c == d;

¿Qué sucedió exactamente en las líneas 3 y 4? ¿Por qué las operaciones de suma con flotadores no son asociativas?

Gracias por adelantado.

Zeta conocido
fuente
16
Como muestra su ejemplo, la adición de coma flotante es conmutativa. Pero no es asociativo.
Yuval Filmus el
1
Te animo a que busques las definiciones básicas. Tenga en cuenta también que el compilador analiza como (la suma se asocia a la izquierda). ( r + s ) + tr+s+t(r+s)+t
Yuval Filmus el
2
Para una manera fácil de ver por qué esto debería ser así, considere Xun número muy grande y Yun número muy pequeño, de tal manera X + Y = X. Aquí, X + Y + -Xserá cero. Pero X + -X + Ylo será Y.
David Schwartz el

Respuestas:

20

En implementaciones típicas de coma flotante, el resultado de una sola operación se produce como si la operación se realizara con precisión infinita y luego se redondeara al número de coma flotante más cercano.

Compare y b + a : el resultado de cada operación realizada con precisión infinita es el mismo, por lo tanto, estos resultados idénticos de precisión infinita se redondean de manera idéntica. En otras palabras, la suma de punto flotante es conmutativa.un+sisi+un

Tome : b es un número de coma flotante. Con números binarios de coma flotante, 2 b también es un número de coma flotante (el exponente es mayor en uno), por lo que b + b se agrega sin ningún error de redondeo. Entonces una se añade a la exacta valor b + b . El resultado es el valor exacto 2 b + a , redondeado al número de punto flotante más cercano.si+si+unsi2sisi+siunsi+si2si+un

Tome : un + b se añade, y habrá un error de redondeo r , por lo que obtener el resultado de un + b + r . Agregue b , y el resultado es el valor exacto 2 b + a + r , redondeado al número de punto flotante más cercano.un+si+siun+sirun+si+rsi2si+un+r

Entonces, en un caso, , redondeado. En el otro caso, 2 b + a + r , redondeado.2si+un2si+un+r

PD. Si para dos números particulares y b ambos cálculos dan el mismo resultado o no, depende de los números y del error de redondeo en el cálculo a + b , y generalmente es difícil de predecir. El uso de precisión simple o doble no hace ninguna diferencia al problema en principio, pero dado que los errores de redondeo son diferentes, habrá valores de a y b donde en precisión simple los resultados son iguales y en precisión doble no lo son, o viceversa. La precisión será mucho mayor, pero el problema de que dos expresiones son matemáticamente iguales pero no iguales en aritmética de coma flotante sigue siendo el mismo.unsiun+si

PPS En algunos idiomas, la aritmética de coma flotante se puede realizar con mayor precisión o un rango de números mayor que el que dan las declaraciones reales. En ese caso, sería mucho más probable (pero aún no está garantizado) que ambas sumas den el mismo resultado.

PPPS Un comentario preguntó si deberíamos preguntar si los números de coma flotante son iguales o no. Absolutamente si sabes lo que estás haciendo. Por ejemplo, si ordena una matriz o implementa un conjunto, se mete en problemas si desea utilizar alguna noción de "aproximadamente igual". En una interfaz gráfica de usuario, es posible que deba volver a calcular los tamaños de los objetos si el tamaño de un objeto ha cambiado: compara oldSize == newSize para evitar ese recálculo, sabiendo que en la práctica casi nunca tiene tamaños casi idénticos, y su programa es correcto incluso si hay un recálculo innecesario.

gnasher729
fuente
En este caso particular, b se vuelve periódico cuando se convierte a binario, por lo que hay errores de redondeo en todas partes.
André Souza Lemos
1
@ AndréSouzaLemos ben esta respuesta no es 0.00004, es lo que obtienes después de la conversión y redondeo.
Alexey Romanov
"En las implementaciones típicas de coma flotante, el resultado de una sola operación se produce como si la operación se realizara con una precisión infinita y luego se redondeara al número de coma flotante más cercano". cuando intenté implementar esto en términos de puertas lógicas (el simulador solo podía manejar buses de 64 bits).
John Dvorak
Pregunta ingenua: ¿Tiene sentido probar la igualdad de flotación? ¿Por qué la mayoría de los lenguajes de programación permiten la prueba aa == b donde ambos o uno es flotante?
curious_cat
Definición relevante de Wikipedia: " Machine Epsilon da un límite superior al error relativo debido al redondeo en aritmética de coma flotante".
Blackhawk
5

El formato de punto flotante binario admitido por las computadoras es esencialmente similar a la notación científica decimal utilizada por los humanos.

Un número de coma flotante consiste en un signo, mantisa (ancho fijo) y exponente (ancho fijo), como este:

+/-  1.0101010101 × 2^12345
sign   ^mantissa^     ^exp^

La notación científica regular tiene un formato similar:

+/- 1.23456 × 10^99

Si hacemos aritmética en notación científica con precisión finita, redondeando después de cada operación, obtendremos los mismos efectos negativos que el punto flotante binario.


Ejemplo

Para ilustrar, supongamos que usamos exactamente 3 dígitos después del punto decimal.

a = 99990 = 9.999 × 10^4
b =     3 = 3.000 × 10^0

(a + b) + b

Ahora calculamos:

c = a + b
  = 99990 + 3      (exact)
  = 99993          (exact)
  = 9.9993 × 10^4  (exact)
  = 9.999 × 10^4.  (rounded to nearest)

En el siguiente paso, por supuesto:

d = c + b
  = 99990 + 3 = ...
  = 9.999 × 10^4.  (rounded to nearest)

Por lo tanto (a + b) + b = 9.999 × 10 4 .

(b + b) + a

Pero si hicimos las operaciones en un orden diferente:

e = b + b
  = 3 + 3  (exact)
  = 6      (exact)
  = 6.000 × 10^0.  (rounded to nearest)

A continuación calculamos:

f = e + a
  = 6 + 99990      (exact)
  = 99996          (exact)
  = 9.9996 × 10^4  (exact)
  = 1.000 × 10^5.  (rounded to nearest)

Por lo tanto (b + b) + a = 1.000 × 10 5 , que es diferente a nuestra otra respuesta.

Nayuki
fuente
5

Java utiliza la representación de punto flotante binario IEEE 754, que dedica 23 dígitos binarios a la mantisa, que se normaliza para comenzar con el primer dígito significativo (omitido, para ahorrar espacio).

0.0000410=0.00000000000000101001111100010110101100010001110001101101000111 ...2=[1)]01001111100010110101100010001110001101101000111 ...2×2-15

100010+0.0000410=1111101000.00000000000000101001111100010110101100010001110001101101000111 ...2=[1)]11110100000000000000000101001111100010110101100010001110001101101000111 ...2×29 9

Las partes en rojo son las mantisas, ya que en realidad están representadas (antes del redondeo).

(100010+0.0000410)+0.0000410(0.0000410+0.0000410)+100010

André Souza Lemos
fuente
0

Recientemente nos encontramos con un problema de redondeo similar. Las respuestas mencionadas anteriormente son correctas, aunque bastante técnicas.

Encontré lo siguiente como una buena explicación de por qué existen errores de redondeo. http://csharpindepth.com/Articles/General/FloatingPoint.aspx

TLDR: los puntos flotantes binarios no se pueden asignar con precisión a los puntos flotantes decimales. Esto provoca imprecisiones que pueden agravarse durante las operaciones matemáticas.

Un ejemplo que usa números flotantes decimales: 1/3 + 1/3 + 1/3 normalmente sería igual a 1. Sin embargo, en decimales: 0.333333 + 0.333333 + 0.333333 nunca es exactamente igual a 1.000000

Lo mismo sucede cuando se realizan operaciones matemáticas en decimales binarios.

Freek Sanders
fuente