“New BigDecimal (13.3D)” resulta em imprecisa “13,3000000000000007105 ..”?
-
19-08-2019 - |
Pergunta
Como é que BigDecimal
de Java pode ser este doloroso?
Double d = 13.3D;
BigDecimal bd1 = new BigDecimal(d);
BigDecimal bd2 = new BigDecimal(String.valueOf(d));
System.out.println("RESULT 1: "+bd1.toString());
System.out.println("RESULT 2: "+bd2.toString());
RESULT 1: 13.300000000000000710542735760100185871124267578125
RESULT 2: 13.3
Existe alguma situação onde Resultado 1 seria desejável? Eu sei que Java 1.5 mudou o método toString()
mas foi essa a consequência pretendida?
Também percebo que BigDecimal
tem doubleValue()
etc, mas a biblioteca que eu estou trabalhando com prestativamente usa um toString()
e eu não posso mudar isso: - (
Felicidades.
Solução
Bem, a API faz endereço essa aparente inconsistência na BigDecimal(double val)
construtor:
Os resultados desta construtor pode ser um pouco imprevisível. Alguém pode assumir que escrever novo BigDecimal (0,1) em Java cria um BigDecimal que é exatamente igual a 0,1 (um valor fora de escala de 1, com uma escala de 1), mas na verdade é igual para 0,1000000000000000055511151231257827021181583404541015625. Isso ocorre porque 0.1 não pode ser representado exatamente como um duplo (ou, para essa matéria, como uma fração binária de qualquer comprimento finito). Assim, o valor que está a ser transmitido para o construtor não é exatamente igual a 0,1, as aparências, não obstante.
O construtor String, por outro lado, é perfeitamente previsível: escrever novo BigDecimal ( "0.1") cria um BigDecimal que é exatamente igual a 0,1, como seria de esperar. Portanto, é geralmente recomendado que o construtor corda ser utilizado em preferência a este.
Quando um casal deve ser utilizado como uma fonte para um BigDecimal , nota que Este construtor fornece uma exata conversão; ele não dá o mesmo resultar como converter a dupla a um String usando o Double.toString método (duas vezes) e em seguida, usando o BigDecimal (String) construtor. Para obter esse resultado, uso a estática valueOf método (duplo) .
Moral da história: A dor parece, o uso apenas auto-infligido new BigDecimal(String val)
ou BigDecimal.valueOf(double val)
vez =)
Outras dicas
Seu problema não tem nada a ver com BigDecimal
, e tudo com Double
, que não pode representar 13,3 precisão, uma vez que utiliza frações binárias internamente.
Portanto, o seu erro é introduzido na primeira linha. O primeiro BigDecimal
simplesmente preserva-lo, enquanto String.valueOf()
faz algum arredondamento de peixe que faz com que o segundo a ter o conteúdo desejado, praticamente com a sorte.
Você pode querer informar-se sobre como valores de ponto flutuante são implementadas ( IEEE 754-1985 ). E de repente, tudo se tornará cristalina.
Isto não é culpa do BigDecimal
- é culpa do double
. BigDecimal
está representando com precisão o exata ??em> valor de d
. String.valueOf
é apenas mostrar o resultado de algumas casas decimais.
As fracções representado com tipos número binário (isto é. double
, float
) não podem ser armazenados com precisão nesses tipos.
Double d = 13.3;
BigDecimal bdNotOk = new BigDecimal(d);
System.out.println("not ok: " + bdNotOk.toString());
BigDecimal bdNotOk2 = new BigDecimal(13.3);
System.out.println("not ok2: " + bdNotOk2.toString());
double x = 13.3;
BigDecimal ok = BigDecimal.valueOf(x);
System.out.println("ok: " + ok.toString());
double y = 13.3;
// pretty lame, constructor's behavior is different from valueOf static method
BigDecimal bdNotOk3 = new BigDecimal(y);
System.out.println("not ok3: " + bdNotOk3.toString());
BigDecimal ok2 = new BigDecimal("13.3");
System.out.println("ok2: " + ok2.toString());
Double e = 0.0;
for(int i = 0; i < 10; ++i) e = e + 0.1; // some fractions cannot be accurately represented with binary
System.out.println("not ok4: " + e.toString()); // should be 1
BigDecimal notOk5 = BigDecimal.valueOf(e);
System.out.println("not ok5: " + notOk5.toString()); // should be 1
/*
* here are some fractions that can be represented exactly in binary:
* 0.5 = 0.1 = 1 / 2
* 0.25 = 0.01 = 1 / 4
* 0.75 = 0.11 = 3 / 4
* 0.125 = 0.001 = 1 / 8
*/
saída:
not ok: 13.300000000000000710542735760100185871124267578125
not ok2: 13.300000000000000710542735760100185871124267578125
ok: 13.3
not ok3: 13.300000000000000710542735760100185871124267578125
ok2: 13.3
not ok4: 0.9999999999999999
not ok5: 0.9999999999999999
Apenas uso BigDecimal.valueOf(d)
ou new BigDecimal(s)
.