Problemas com valor double truncado

Caros,

Andei pesquisando GUJ e Google afora, mas ainda estou com o mesmo problema para fazer um trunc (truncate) um valor decimal (double).

Alguns casos funciona, mas em outro em específico não.

Vai ai as soluções de truncamento por precisão (casas decimais) que usei, mas o resultado foi o mesmo:

public static double trunc( double val, int precision ) { BigDecimal bd = new BigDecimal(val).setScale(precision,BigDecimal.ROUND_DOWN); return bd.doubleValue(); }
OU

public static double trunc( double val, int precision ) { double fator = Math.pow(10,precision); return ((long)(val * fator)) / fator; }

E no Unit Test:

[code]public void testTrunc() {
double original = 1234.567890;
double trunc = 0;

trunc = NumericUtil.trunc(original,3);
assertEquals(trunc, 1234.567); // OK

trunc = NumericUtil.trunc(-original,4);
assertEquals(trunc, -1234.5678); // OK

trunc = NumericUtil.trunc(4.31D, 2);
assertEquals(trunc, 4.31); // DIFERENTE
}[/code]

Alguém pode dar uma solução definitiva?
Grato

Qual o valor de trunc na última execução?

Ele retorna 4.3

Veja se o RoundingMode.HALF_EVEN funciona pro teu caso.

Obs: Se o teu programa depende de cálculos precisos, esqueça o pontos flutuantes normais do Java.

Err, fiz mais uns testes aqui… não deram certo.

O problema é a arredondamento que o java faz com operações com floats e doubles. Por exemplo, 4.01D + 0.1D = 4.109999999999999.

Como disse, depender dos tipos do java pra precisão de cálculos não dá muito certo.

O que pode fazer é um assertEquals com uma margem de erro.

Exato. Ou você coloca essa margem de erro, ou passa a usar apenas BigDecimal e String.

Aliás, o ponto flutuante tem esse problema não apenas em Java.

Aqui seu código funcionando:

[code] String trunc2 = nu.trunc(“4.31”, 2);
assertEquals(“4.31”, trunc2); // DIFERENTE
System.out.println(trunc2);

////////

	public String trunc(String val, int precision) {
		BigDecimal bd = new BigDecimal(val).setScale(precision,
				BigDecimal.ROUND_DOWN);;
		return bd.toString();
	}

[/code]

Valeu

[quote=Bruno Laturner]Como disse, depender dos tipos do java pra precisão de cálculos não dá muito certo.
O que pode fazer é um assertEquals com uma margem de erro.[/quote]

Hahahaha… margem de erro? Estou falando de cálculos financeiros, não de cálculos estatísticos.

O que adianta garantir o teste e falhar na execução? POG!

[quote=danieldestro][quote=Bruno Laturner]Como disse, depender dos tipos do java pra precisão de cálculos não dá muito certo.
O que pode fazer é um assertEquals com uma margem de erro.[/quote]

Hahahaha… margem de erro? Estou falando de cálculos financeiros, não de cálculos estatísticos.

O que adianta garantir o teste e falhar na execução? POG![/quote]

Como disse, depender dos tipos nativos do Java para precisão não dá certo. Eles foram projetados para velocidade de execução, e não corretude.

A solução é nunca usar um float nem um double.

O ideal seria usar um value object(do Fowler) Money ou Currency, implementado por baixo com um BigDecimal, em que todas as operações recebam e retornem um outro Money.

Nem mesmo com BigDecimal puramente funcionou a princípio:

BigDecimal trunc = NumericUtil.trunc(4.31D, 2); assertEquals(trunc, new BigDecimal(4.31));

Este teste falhou!

[quote=danieldestro]Nem mesmo com BigDecimal puramente funcionou a princípio:

BigDecimal trunc = NumericUtil.trunc(4.31D, 2); assertEquals(trunc, new BigDecimal(4.31));

Este teste falhou!

Não funciona por que você usou doubles. Tente com Strings.

Pois é.

Alterado o método para Strings, este teste funciona:

[code]BigDecimal trunc = null;

trunc = NumericUtil.trunc(“1234.567890”,3);
assertEquals(trunc, new BigDecimal(“1234.567”));

trunc = NumericUtil.trunc("-1234.567890",4);
assertEquals(trunc, new BigDecimal("-1234.5678"));

trunc = NumericUtil.trunc(“4.31”, 2);
assertEquals(trunc, new BigDecimal(“4.31”));[/code]

editado: o pior que não posso mudar meu sistema todo que está baseando em “doubles”.

A principio eu resolvi assim:

[code]
public static BigDecimal trunc( BigDecimal val, int precision ) {
if( precision <= 0 ) {
throw new IllegalArgumentException(“Precisão deve ser maior que zero”);
}
return val.setScale(precision,BigDecimal.ROUND_DOWN);
}

public static double trunc( double val, int precision ) {
return trunc( BigDecimal.valueOf(val), precision ).doubleValue(); // resolvido
}

public static BigDecimal trunc( String val, int precision ) {
return trunc(new BigDecimal(val), precision);
}[/code]

editado: Acabei olhando o fonte do BigDecimal para ver como poderia corrigir.

Uma coisa que aprendi ao custo de perder alguns fios de cabelo, foi o de nunca utilizar float e double (e por sinal algo que pouca gente vai concordar). Na minha opinião a forma que esses números são armazenados é simplesmente idiota, a precisão não é bem definida para eles.

Sempre uso BigDecimal para cálculos finaceiros e cálculos que exijam precisão. float e double só servem em lugares onde um pequeno erro é aceitável em nome da performance, tal como em animações de jogos, e mesmo assim é preciso tomar-se alguns cuidados.

Gostaria que o java tivesse uma classe BigRational, armazenando um numerador e um denominador, pois atualmente não há forma de se representar 1/3, 1/7 ou 1/11 em java nem com float, nem com double e nem com BigDecimal, mas se esta classe existisse, BigDecimal não precisaria mais existir.

Se não puder evitar o float e double, tente usar o modificador [size=18]strictfp[/size].

Daniel,
Tudo bem que é um porre, mas acho que a solução do Joshua Bloch no Effective Java 2ª Edition, resolveria seu problema não?