Desigualdade causada pela imprecisão do flutuador

15

Pelo menos em Java, se eu escrever 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;

o valor de seria . Acredito que isso seja causado pelo fato de os flutuadores serem muito limitados na maneira de representar números com precisão. Mas não entendo por que apenas mudar a posição de poderia causar essa desigualdade.efalsea

Reduzi os s para um nas linhas 3 e 4, como abaixo, mas o valor de se torna :betrue

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

O que exatamente aconteceu nas linhas 3 e 4? Por que operações de adição com flutuadores não são associativas?

Desde já, obrigado.

Zeta conhecido
fonte
16
Como mostra o exemplo, a adição de ponto flutuante é comutativa. Mas não é associativo.
Yuval Filmus
11
Convido você a procurar as definições básicas. Observe também que o compilador analisa como ( r + s ) + t (adição associada à esquerda). r+s+t(r+s)+t
Yuval Filmus
2
Para uma maneira fácil de entender por que isso deve ser assim, considere Xum número muito grande e Yum número muito pequeno, como esse X + Y = X. Aqui, X + Y + -Xserá zero. Mas X + -X + Yserá Y.
David Schwartz

Respostas:

20

Nas implementações típicas de ponto flutuante, o resultado de uma única operação é produzido como se a operação fosse executada com precisão infinita e arredondada para o número de ponto flutuante mais próximo.

Compare e b + a : O resultado de cada operação executada com precisão infinita é o mesmo; portanto, esses resultados idênticos de precisão infinita são arredondados de maneira idêntica. Em outras palavras, a adição de ponto flutuante é comutativa.a+bb+uma

Considere : b é um número de ponto flutuante. Com números binários de ponto flutuante, 2 b também é um número de ponto flutuante (o expoente é maior em um), então b + b é adicionado sem nenhum erro de arredondamento. Então a é adicionado ao valor exato b + b . O resultado é o valor exato 2 b + a , arredondado para o número de ponto flutuante mais próximo.b+b+umab2bb+bumab+b2b+uma

Tome : a + b é adicionado e haverá um erro de arredondamento r , então obtemos o resultado a + b + r . Adicione b , e o resultado será o valor exato 2 b + a + r , arredondado para o número de ponto flutuante mais próximo.uma+b+buma+bruma+b+rb2b+uma+r

Então, em um caso, , arredondado. No outro caso, 2 b + a + r , arredondado.2b+uma2b+uma+r

PS. Quer se trate de dois números particulares e b ambos os cálculos se obter o mesmo resultado ou não depende dos números, e sobre o erro de arredondamento no cálculo um + b , e é geralmente difícil de prever. O uso de precisão simples ou dupla não faz diferença para o problema em princípio, mas como os erros de arredondamento são diferentes, haverá valores de aeb em que na precisão única os resultados são iguais e na precisão dupla não são, ou vice-versa. A precisão será muito maior, mas o problema de que duas expressões são matematicamente iguais, mas não iguais na aritmética de ponto flutuante permanece o mesmo.umabuma+b

PPS. Em alguns idiomas, a aritmética de ponto flutuante pode ser executada com maior precisão ou com um intervalo maior de números do que o indicado pelas declarações reais. Nesse caso, seria muito mais provável (mas ainda não garantido) que ambas as somas apresentassem o mesmo resultado.

PPPS. Um comentário perguntou se deveríamos perguntar se os números de ponto flutuante são iguais ou não. Absolutamente se você souber o que está fazendo. Por exemplo, se você classifica uma matriz ou implementa um conjunto, você se mete em um problema terrível se quiser usar alguma noção de "aproximadamente igual". Em uma interface gráfica com o usuário, pode ser necessário recalcular o tamanho do objeto se o tamanho de um objeto foi alterado - você compara oldSize == newSize para evitar esse recálculo, sabendo que, na prática, você quase nunca tem tamanhos quase idênticos, e seu programa está correto mesmo se houver um recálculo desnecessário.

gnasher729
fonte
Nesse caso específico, b se torna periódico quando convertido em binário, portanto, há erros de arredondamento em todos os lugares.
André Souza Lemos
11
@ AndréSouzaLemos bnesta resposta não é 0,00004, é o que você obtém após a conversão e o arredondamento.
Alexey Romanov
"Em implementações típicas de ponto flutuante, o resultado de uma única operação é produzido como se a operação fosse executada com precisão infinita e arredondada para o número de ponto flutuante mais próximo". quando tentei implementar isso em termos de portas lógicas (o simulador só podia lidar com barramentos de 64 bits).
John Dvorak
Pergunta ingênua: testar a igualdade de flutuação faz sentido? Por que a maioria das linguagens de programação permite um teste = = b onde ambos ou um são flutuantes?
Curious_cat
Definição relevante da Wikipedia: "A máquina Epsilon fornece um limite superior para o erro relativo devido ao arredondamento na aritmética de ponto flutuante".
Blackhawk
5

O formato binário de ponto flutuante suportado por computadores é essencialmente semelhante à notação científica decimal usada por seres humanos.

Um número de ponto flutuante consiste em um sinal, mantissa (largura fixa) e expoente (largura fixa), assim:

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

A notação científica regular tem um formato semelhante:

+/- 1.23456 × 10^99

Se fizermos aritmética em notação científica com precisão finita, arredondando após cada operação, obtemos todos os mesmos efeitos negativos que o ponto flutuante binário.


Exemplo

Para ilustrar, suponha que usamos exatamente três dígitos após o ponto decimal.

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

(a + b) + b

Agora calculamos:

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

Na próxima etapa, é claro:

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

Portanto (a + b) + b = 9.999 × 10 4 .

(b + b) + a

Mas se fizermos as operações em uma ordem diferente:

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

Em seguida, calculamos:

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

Portanto (b + b) + a = 1.000 × 10 5 , que é diferente da nossa outra resposta.

Nayuki
fonte
5

Java usa a representação de ponto flutuante binário IEEE 754, que dedica 23 dígitos binários à mantissa, que é normalizada para começar com o primeiro dígito significativo (omitido, para economizar espaço).

0,0000410=0.00000000000000101001111100010110101100010001110001101101000111 ...2=[1]01001111100010110101100010001110001101101000111 ...2×2-15

100010+0,0000410=1111101000.00000000000000101001111100010110101100010001110001101101000111 ...2=[1]111101000000000000000001 101001111100010110101100010001110001101101000111 ...2×29

As partes em vermelho são as mantissas, como na verdade são representadas (antes do arredondamento).

(100010+0,0000410)+0,0000410(0,0000410+0,0000410)+100010

André Souza Lemos
fonte
0

Recentemente, enfrentamos um problema de arredondamento semelhante. As respostas acima mencionadas estão corretas, porém bastante técnicas.

Eu achei o seguinte uma boa explicação do porquê existem erros de arredondamento. http://csharpindepth.com/Articles/General/FloatingPoint.aspx

TLDR: pontos flutuantes binários não podem ser mapeados com precisão para pontos flutuantes decimais. Isso causa imprecisões que podem se agravar durante operações matemáticas.

Um exemplo usando números flutuantes decimais: 1/3 + 1/3 + 1/3 normalmente seria igual a 1. No entanto, em decimais: 0,333333 + 0,333333 + 0,333333 nunca é exatamente igual a 1,000000

O mesmo acontece ao executar operações matemáticas em decimais binários.

Freek Sanders
fonte