0.3*3 != 0.9 - Alguém consegue me explicar?  XML
Índice dos Fóruns » Java Avançado
Autor Mensagem
ivo costa
JavaEvangelist
[Avatar]

Membro desde: 06/11/2007 12:07:34
Mensagens: 493
Localização: Porto Alegre - RS
Offline

Estava dando uma googlada sobre erros de arredondamentos e eis que me deparo com esse link:
http://markmail.org/message/52arif4344c37mr5

Aqui está sendo mostrado três formas diferentes de se somar 0.3 que resultam em 3 números distintos.
Eis o código:


A saída desse código é:

0.3*3 double->double: 0.8999999999999999
0.3*3 double->float: ---0.9
0.3*3 float->float: ------0.90000004


A primeira saída é 0.899... pq o número 0.3 não tem representação binária, ao se tentar passar 0.3 para binário fica assim:
0.3 * 2 = 0.6 * 2 = 0.2 * 2 = 0.4 * 2 = 0.8 * 2 = 0.6 * 2 = 0.2 ...
0 1 0 0 1 1 0 0 1 1 0 0 ...
Então há um truncamento no meio desse número que vai gerar uma perda de precisão, devido essa perda a volta do 0.3bin * 3bin para decimal resulta 0.899... e não o esperado 0.9.

A segunda saída sai "certa aos nossos olhos" pq é feito a multilicação usando double e depois é feito um casting para float, como sabemos em um float não cabe um double então o número é cortado ao meio (pq 32bits é metade de 64bits) e é feito um arrendondamento científico:
0.89999999(corta)99999999 -> como o proxímo número é maior que 4 soma-se 1 ao anterior, como o anterior virou 10 soma-se 1 ao anterior...
0.90000000

Agora a pergunta: alguém consegue me explicar o 0.90000004???


Mais algumas curiosidades:
Se eu deseja-se achar várias casas decimais do número 0.3 em bin, poderia fácilmente fazer um laço que vai multiplicando o número por 2:


E a saída desse código é:

dec->bin com int: ------0100110011001100110011001100110011001100110011001100110011001100
double->bin normal: --010011001100110011001100110011001100110011001100110011
dec->bin com BigDec: 0100110011001100110011001100110011001100110011001100110000000000

Usando double não daria para alcançar uma quantidade muito grande de digitos.
Notem que o nosso companheiro de trabalho BigDecimal também sofre desse mal, que poderia ser contornado fazendo divisões com números inteiros ao invés de decimais (como eu fiz no método converteEmBinComInt).

Alguns links interessantes:
http://www.vivaolinux.com.br/artigo/Cuidado-com-numeros-em-Ponto-Flutuante/?pagina=2
http://javeiros.wordpress.com/2008/06/25/evite-float-e-double-se-voce-quer-respostas-exatas/
http://www.dfanning.com/math_tips/razoredge.html
http://guj.com.br/posts/list/89949.java#481543

Mais uma vez eu faço a pergunta pq : 0.90000004 ?

This message was edited 1 time. Last update was at 26/08/2008 14:54:08


Eu sonho com um mundo melhor, onde galinhas que atravessam a rua não serão questionadas pelos seus motivos.
Formate o seu código usando as tags [code] http://www.guj.com.br/posts/list/50115.java
Faça perguntas inteligentes
[MSN]
ViniGodoy
Moderador
[Avatar]

Membro desde: 11/12/2006 08:22:01
Mensagens: 20512
Localização: Curitiba/PR
Offline

0,3 em binário é uma dizima:
http://www.guj.com.br/posts/list/84121.java#448928

Provavelmente as últimas casas dessa dízima, dentro de uma precisão de float é 4.
E por isso aquele número ficou sobrando ali.

@ViniGodoy - Lattes

Tem dúvidas de Java? Poste no fórum! Não respondo dúvidas de java via MP!

Ponto V! - Desenvolvimento de Jogos Profissional - @Pontov - Facebook
Projeto Towel - Swing de uma forma inteligente (Novo lar do ObjectTableModel e do Auto-Filtro).

Ei... você está usando DefaultTableModel no seu projeto??
Não faça isso! Veja: http://www.guj.com.br/posts/list/15/199067.java#1001295
[WWW]
ViniGodoy
Moderador
[Avatar]

Membro desde: 11/12/2006 08:22:01
Mensagens: 20512
Localização: Curitiba/PR
Offline

Ah sim, note que no último caso você efetivamente pegou a imprecisão do 0.3 e multiplicou por 3. Nas outras operações, você está usando doubles (mesmo no segundo caso, a conta é com double e o arredondamento para float é no final).

Uma dica básica para evitar imprecisões como essa, além de usar tipos maiores, é quando possível multiplicar antes de dividir. Nesse caso, existe uma divisão por 10 (0.3 = 3/10).

Se você mudar a conta para:
(3*3) / 10.0

Nunca haverá imprecisão. E você pode usar float e o ponto decimal onde quiser.

Isso evita multiplicar a imprecisão do float/double. E uma pequena imprecisão, quando multiplicada, pode virar um grande erro.

This message was edited 2 times. Last update was at 26/08/2008 15:12:43


@ViniGodoy - Lattes

Tem dúvidas de Java? Poste no fórum! Não respondo dúvidas de java via MP!

Ponto V! - Desenvolvimento de Jogos Profissional - @Pontov - Facebook
Projeto Towel - Swing de uma forma inteligente (Novo lar do ObjectTableModel e do Auto-Filtro).

Ei... você está usando DefaultTableModel no seu projeto??
Não faça isso! Veja: http://www.guj.com.br/posts/list/15/199067.java#1001295
[WWW]
 
Índice dos Fóruns » Java Avançado
Ir para:   
Powered by JForum 2.1.8 © JForum Team