Eu sei que o 0.1
número decimal não pode ser representado exatamente com um número binário finito ( explicação ), por double n = 0.1
isso perderá alguma precisão e não será exatamente 0.1
. Por outro lado, 0.5
pode ser representado exatamente porque é 0.5 = 1/2 = 0.1b
.
Dito isto, é compreensível que a adição de 0.1
três vezes não dê exatamente, 0.3
para que o seguinte código seja impresso 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
Mas então, como é que a adição de 0.1
cinco vezes dará exatamente 0.5
? O código a seguir é impresso 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?
Se 0.1
não pode ser representado exatamente, como é que adicioná-lo 5 vezes fornece exatamente o 0.5
que pode ser representado com precisão?
sum
tivesse o mesmo valor final como se o loop fosse realmente executado. No padrão C ++, isso é chamado de "regra como se" ou "mesmo comportamento observável".Respostas:
O erro de arredondamento não é aleatório e, da maneira como é implementado, tenta minimizar o erro. Isso significa que, às vezes, o erro não é visível ou não há erro.
Por exemplo,
0.1
não é exatamente0.1
ou seja,new BigDecimal("0.1") < new BigDecimal(0.1)
mas0.5
é, é exatamente1.0/2
Este programa mostra os verdadeiros valores envolvidos.
impressões
Nota: isso
0.3
está um pouco desligado, mas quando você chega aos0.4
bits, precisa mudar um para baixo para se ajustar ao limite de 53 bits e o erro é descartado. Mais uma vez, um erro volta a aparecer0.6
e0.7
mas para0.8
a1.0
que o erro é descartado.A razão pela qual há um erro é devido à precisão limitada. ou seja, 53 bits. Isso significa que, à medida que o número usa mais bits à medida que aumenta, os bits precisam ser descartados no final. Isso causa arredondamentos, que neste caso estão a seu favor.
Você pode obter o efeito oposto ao obter um número menor, por exemplo,
0.1-0.0999
=>1.0000000000000286E-4
e vê mais erros do que antes.Um exemplo disso é o porquê no Java 6 Por que Math.round (0.49999999999999994) retorna 1 Nesse caso, a perda de um bit no cálculo resulta em uma grande diferença para a resposta.
fonte
strictfp
Time para considerar números inteiros de ponto fixo, eu acho. (ou BigDecimal)Excesso de barramento, em ponto flutuante,
x + x + x
é exatamente o número do ponto flutuante arredondado corretamente (ou seja, o mais próximo) para o real 3 *x
,x + x + x + x
é exatamente 4 *x
ex + x + x + x + x
é novamente a aproximação do ponto flutuante arredondado corretamente para 5 *x
.O primeiro resultado, pois
x + x + x
, deriva do fatox + x
exato.x + x + x
é, portanto, o resultado de apenas um arredondamento.O segundo resultado é mais difícil, uma demonstração dele é discutida aqui (e Stephen Canon alude a outra prova por análise de caso nos últimos 3 dígitos de
x
). Para resumir, 3 *x
está na mesma bandeja que 2 *x
ou na mesma bandeja que 4 *x
e, em cada caso, é possível deduzir que o erro na terceira adição cancela o erro na segunda adição (o sendo a primeira adição exata, como já dissemos).O terceiro resultado, "
x + x + x + x + x
é arredondado corretamente", deriva do segundo da mesma maneira que o primeiro deriva da exatidão dex + x
.O segundo resultado explica por que
0.1 + 0.1 + 0.1 + 0.1
exatamente é o número de ponto flutuante0.4
: os números racionais 1/10 e 4/10 são aproximados da mesma maneira, com o mesmo erro relativo, quando convertidos em ponto flutuante. Esses números de ponto flutuante têm uma proporção de exatamente 4 entre eles. O primeiro e o terceiro resultado mostram que,0.1 + 0.1 + 0.1
e0.1 + 0.1 + 0.1 + 0.1 + 0.1
pode-se esperar que tenha menos erro do que pode ser inferido pela análise de erro ingênua, mas, por si só, eles relacionam os resultados apenas com respectivamente3 * 0.1
e5 * 0.1
, que se espera que sejam próximos, mas não necessariamente idênticos aos0.3
e0.5
.Se você continuar adicionando
0.1
após a quarta adição, finalmente observará erros de arredondamento que fazem com que “0.1
adicionado a si n vezes” divergam dos binários por um tempo. Depois disso, a absorção começaria a ocorrer e a curva ficaria plana.n * 0.1
e ainda mais do n / 10. Se você plotar os valores de "0,1 adicionado a si mesmo n vezes" em função de n, você observaria linhas de inclinação constante por binades (assim que o resultado da enésima adição for destinado a cair em uma binada específica, pode-se esperar que as propriedades da adição sejam semelhantes às adições anteriores que produziram um resultado na mesma binade). Dentro de uma mesma caixa, o erro aumentará ou diminuirá. Se você olhasse para a sequência das pistas de binade a binade, reconheceria os dígitos repetidos de0.1
fonte
x + x + x
é exatamente o número de ponto flutuante arredondado corretamente para o real 3 *x
. "Arredondado corretamente" significa "o mais próximo" neste contexto.Os sistemas de ponto flutuante fazem várias magias, incluindo ter alguns bits extras de precisão para arredondar. Assim, o erro muito pequeno devido à representação inexata de 0,1 acaba sendo arredondado para 0,5.
Pense no ponto flutuante como uma maneira excelente, mas INEXATA de representar números. Nem todos os números possíveis são facilmente representados em um computador. Números irracionais como PI. Ou como SQRT (2). (Os sistemas matemáticos simbólicos podem representá-los, mas eu disse "facilmente".)
O valor do ponto flutuante pode ser extremamente próximo, mas não exato. Pode estar tão perto que você pode navegar para Plutão e sair em milímetros. Mas ainda não é exato no sentido matemático.
Não use ponto flutuante quando precisar ser exato e não aproximado. Por exemplo, os aplicativos de contabilidade desejam acompanhar exatamente um determinado número de centavos em uma conta. Inteiros são bons para isso porque são exatos. O principal problema que você precisa observar com números inteiros é o estouro.
O uso de BigDecimal para moeda funciona bem porque a representação subjacente é um número inteiro, embora grande.
Reconhecendo que os números de ponto flutuante são inexatos, eles ainda têm muitos usos. Sistemas de coordenadas para navegação ou coordenadas em sistemas gráficos. Valores astronômicos. Valores científicos. (Você provavelmente não pode saber a massa exata de uma bola de beisebol dentro da massa de um elétron, de modo que a inexatidão realmente não importa.)
Para aplicativos de contagem (incluindo contabilidade), use inteiro. Para contar o número de pessoas que passam por um portão, use int ou long.
fonte
strictfp
). Só porque você renunciou a entender algo não significa que é insondável nem que outros devam renunciar a entendê-lo. Veja stackoverflow.com/questions/18496560 como um exemplo dos comprimentos que as implementações de Java irão para implementar a definição de linguagem (que não inclui nenhuma provisão para bits de precisão extra nem, comstrictfp
, para nenhum bit de exp extra)