Flutuantes

7 respostas
renatosilva

Antes de mais nada, leiam devagar! leiam devagar :smiley:

Pessoal, considerem a seguinte conta em Java (tirei de uma discussão interessante no RioJUG)

Na base 2 pelo o que calculei, 1.6 = 1.100110011001… e 4.8 = 100.110011001100…

Ou seja, eles são dízimas periódicas. Eu gostaria de saber ou lembrar:

:arrow: (1) como é feito o processo de arredondamento, já que por mais extensa que seja a mantissa usada (quanto mais períodos pegarmos), mais exato será a conversão de volta pra decimal, porém nunca chegando ao valor exato original. Por exemplo, considerando 4.8, usando um período da dízima mais um bit, em binário, temos:

:arrow: (2) É impressão minha ou o número de casas fracionárias em binário é o mesmo o de casas no decimal? Existe alguma relação lógica ou no exemplo acima foi só coincidência?

:arrow: (3) Eu concluí que é feito um arredondamento até o número de casas que se está trabalhando, no caso de 1.6 * 3 é uma casa, então vamos arredondando:

4.78125 > 4.7813 > 4.781 > 4.78 > 4.8 (valor correto em decimal)

É assim mesmo? Na verdade apenas um período da dízima seria necessário para o arredondamento, assim teríamos 100.1100 = 4.75, que arredonda pra cima e dá 4.8. É isso?

:arrow: (4) Por que em Java, usando double ou foat (pior), 1.6 * 3 > 4.8? O ponto é: como um arredondamento pode dar mais que 4.8 e não menos? Se pegarmos 100.110011001100… e arredondarmos, como isso pode dar mais que 4.8?

Eu mandei imprimir (1.6f * 3) e (1.6 * 3), que deram isso aqui:
4.800000190734863
4.800000000000001

:arrow: (5) Sabemos que quando comparamos floats, devemos especificar uma “faixa de tolerância” pela qual eles serão considerados iguais ou não. Mas no caso de cálculos como o do exemplo, não seria melhor truncar o resultado de acordo com o número de casas que se está trabalhando? Ou mesmo arredondar…No exemplo, só consideraríamos a primeira casa fracionária de 4.800000190734863 ou 4.800000000000001, o que dá ambos 4.8. Truncar ou arredondar? Se o 1.6 vêm do usuário, acho que o arredondamento seria melhor…É isso?

Valeu…

EDIT: acho que postei no fórum errado, desculpem, se alguém puder mover eu agradeço…

7 Respostas

T

renato3110:
Antes de mais nada, leiam devagar! leiam devagar :smiley:

Pessoal, considerem a seguinte conta em Java (tirei de uma discussão interessante no RioJUG)

Na base 2 pelo o que calculei, 1.6 = 1.100110011001… e 4.8 = 100.110011001100…

Ou seja, eles são dízimas periódicas. Eu gostaria de saber ou lembrar:

:arrow: (1) como é feito o processo de arredondamento, já que por mais extensa que seja a mantissa usada (quanto mais períodos pegarmos), mais exato será a conversão de volta pra decimal, porém nunca chegando ao valor exato original. Por exemplo, considerando 4.8, usando um período da dízima mais um bit, em binário, temos:

:arrow: (2) É impressão minha ou o número de casas fracionárias em binário é o mesmo o de casas no decimal? Existe alguma relação lógica ou no exemplo acima foi só coincidência?

Não tinha pensado nisso. Mas é isso mesmo.

O que você chama de "número de casas fracionárias em binário" é a posição do último dígito 1 na representação
fracionária em binário. Só para fazer um aquecimento:

0.1(binário) -&gt 0,5 (decimal)

0.00001(binário) -&gt 0,03125 (decimal) = (5 elevado a 5) dividido por (10 elevado a 5)

0.0001 (binário) -&gt 0,0625 (decimal)

0.1111(binário) -&gt 0,9375

0.11111(binário) -&gt 0,96875

Note que esse tal dígito "1" contribui para um dígito "5" na última posição não-zero da representação decimal.
Não estou pondo a prova aqui, mas dá para você ter uma idéia.

Não, não tem nada a ver. O arredondamento não é feito "de baixo para cima" como você mostrou.
Ele é feito com a maior precisão possível que permite a você recompor o número (e é por isso que às vezes aparecem certas coisas em Java, como um número 4.79999999999998 ou 4.[telefone removido], que não aparecem em C++, por exemplo, que costuma imprimir com 6 dígitos após a vírgula.)

O arredondamento não é um truncamento, e tanto pode dar mais quanto menos (por propriedades estatísticas). Se ele desse sempre menos, você teria sérios problemas.

"Truncar resultados intermediários" de acordo com o número de casas que se está trabalhando é convite para ter resultados que dependem da ordem de avaliação da expressão. Isso parece conversa de professor de física de 30 anos atrás. Use a máxima precisão possível nos cálculos intermediários, e arredonde (nunca trunque) somente para visualização.

Leia esta página com atenção:
What Every Computer Scientist Should Know About Floating-Point Arithmetic


Valeu…

EDIT: acho que postei no fórum errado, desculpem, se alguém puder mover eu agradeço…

renatosilva

Mas não é sempre de baixo para cima, por exemplo 4.7813 > 4.781 é para baixo. Mas o arredondamento é feito ou não?

thingol:

:arrow: (4) Por que em Java, usando double ou foat (pior), 1.6 * 3 &gt 4.8? O ponto é: como um arredondamento pode dar mais que 4.8 e não menos? Se pegarmos 100.110011001100… e arredondarmos, como isso pode dar mais que 4.8?

Eu mandei imprimir (1.6f * 3) e (1.6 * 3), que deram isso aqui:
4.800000190734863
4.800000000000001

O arredondamento não é um truncamento, e tanto pode dar mais quanto menos (por propriedades estatísticas). Se ele desse sempre menos, você teria sérios problemas.

O que eu tô dizendo é o caso específico do 4.8: como se pega 100.110011001100… e se consegue um valor maior que 4.8?

thingol:
"Truncar resultados intermediários" de acordo com o número de casas que se está trabalhando é convite para ter resultados que dependem da ordem de avaliação da expressão. Isso parece conversa de professor de física de 30 anos atrás. Use a máxima precisão possível nos cálculos intermediários, e arredonde (nunca trunque) somente para visualização.

Leia esta página com atenção:
What Every Computer Scientist Should Know About Floating-Point Arithmetic

É que eu reparei que se eu só considerasse a primeira casa, eu teria o valor correto matematicamente (pois never 1,6 x 3 > 4,8 ), e pensei em generalizar isso…

Parece delicioso esse link que você passou, valeuzão! :slight_smile:

Ironlynx

Ótimo tópico Renato!

Mas isso não tem haver mais com covenção?Por exemplo:
Nos 100 metros rasos(atletismo) vc tem os tempos dos milésimos .500 em diante para cima. Por exemplo: os 9s76 feitos pelo Justin Gatlin foram corrigidos pq ele tinha na verdade 9.766 logo, seu tempo foi revisto para 9s77.

renatosilva

Ironlynx:
Ótimo tópico Renato!

Mas isso não tem haver mais com covenção?Por exemplo:
Nos 100 metros rasos(atletismo) vc tem os tempos dos milésimos .500 em diante para cima. Por exemplo: os 9s76 feitos pelo Justin Gatlin foram corrigidos pq ele tinha na verdade 9.766 logo, seu tempo foi revisto para 9s77.

Não entendi muito bem, mas se está falando do arredondamento do 5 para cima ou para baixo, no caso dos flutuantes eu penso que ele seria para cima, pois à direita você tem uma “sujeira” “puxando” o 5 pra cima… se não é isso eu não entendi…

Eu pensei no arredondamento porque o número é uma dízima e a mantissa não vai dar o número exato, e pensei que arredondando seria uma forma de dar o valor exato…

PS: o GUJ tá com um bug estranho, parece que as mensagens mais novas estão aparecendo antes das mais antigas, olhem a msg do ironlynx…

renatosilva

Pra vocês terem uma idéia, olhem o código que eu rodeii, com uma precisão de 23 casas:

public class Workbench {

	public static void main(String[] args) {		
		int[] mantissaBin = { 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0 };	
		float mantissaDecimal = 0;
		
		for (int i = 0; i < mantissaBin.length; i++) {
			mantissaDecimal += mantissaBin[i] * (1 / Math.pow(2, (double) (i + 1)));
			if (mantissaBin[i] == 1)
				System.out.println(mantissaDecimal);
		}		
	}
}

//Saída:
//0.5
//0.75
//0.78125
//0.796875
//0.798828125
//0.[telefone removido]
//0.7999267578125
//0.79998779296875
//0.7999954223632812
//0.7999992370605469
//0.7999997138977051
//0.7999999523162842

Acho que confundi a mantissa do float com a parte fracionária em decimal. O 0.11001100… não é a mantissa, porque a mantissa é a parte fracionária de 1.mantissa * 2^exp = 4.8 * 10^0…

Esse era o tilte da dúvida (4) de como o lance dá mais que 4.8…agora falta entender como isso acontece…

louds

Renato, usa os métodos Float.floatToIntBits e Float.intBitsToFloat para manipular a mantissa de forma precisa.

renatosilva

Não entendi como 1.6 * 3 dá mais que 4.8. Pelos meus cálculos, mesmo na forma 1.xxxx * 2^e isso dá menos, a mantissa seria 00110011001100110011001 e o resultado daria: 4,79999971389770508. O que eu fiz pra achar essa mantissa foi multiplicar 100.110011001100… por 4, o que dá 1.00110011001100…, e na mantissa de 23 bits do float portanto dá o que eu coloquei aí.

Mas usando a dica que o louds passou, vi que a mantissa é 00110011001100110011010, o que no final dá: 4,80000019073486328!

Otilde faque? :x

Criado 2 de outubro de 2006
Ultima resposta 4 de out. de 2006
Respostas 7
Participantes 4