0.3*3 != 0.9 - Alguém consegue me explicar?

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 ?

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.

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.