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:
public class Teste{
public static void main(String[] args) throws Exception {
System.out.println("0.3*3 double->double: " + (0.3 * 3));
float x;
x = (float)(0.3 * 3);
System.out.println("0.3*3 double->float: " + x);
float y;
y = 0.3f * 3;
System.out.println("0.3*3 float->float: " + y);
}
}
A saída desse código é:
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:
public class Teste{
public static void main(String[] args) throws Exception {
mostraABesteira();
converteEmBinComInt(0.3);
converteEmBinComDoubleNormal(0.3);
converteEmBinComBigDecimal(0.3);
}
public static void converteEmBinComInt(double numDecimal){
int num = (int)(numDecimal*10);
int numMaquina = 10;
int precisao = 0;
System.out.print("dec->bin com int: ");
while(num != 0 && precisao < 64){
num *= 2;
if(num < numMaquina) System.out.print("0");
else{
System.out.print("1");
num -= numMaquina;
}
precisao++;
}
}
public static void converteEmBinComDoubleNormal(double numDecimal){
double num = numDecimal;
int precisao = 0;
System.out.print("\ndouble->bin normal: ");
while(num != 0 && precisao < 64){
num *= 2;
if(num < 1) System.out.print("0");
else{
System.out.print("1");
num -= 1;
}
precisao++;
}
}
public static void converteEmBinComBigDecimal(double numDecimal){
BigDecimal num = new BigDecimal(numDecimal);
int precisao = 0;
System.out.print("\ndec->bin com BigDec: ");
while(!num.equals(new BigDecimal(0)) && precisao < 64){
num = num.multiply(new BigDecimal(2));
if(num.compareTo(new BigDecimal(1)) == -1)
System.out.print("0");
else{
System.out.print("1");
num = num.subtract(new BigDecimal(1));
}
precisao++;
}
System.out.println();
}
}
E a saída desse código é:
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://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 ?