& # 8220; nuovo BigDecimal (13.3D) & # 8221; risulta impreciso & # 8220; 13.3000000000000007105 .. & # 8221 ;?
-
19-08-2019 - |
Domanda
In che modo il BigDecimal
di Java può essere così 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
C'è qualche situazione in cui si vorrebbe il risultato 1? So che Java 1.5 ha cambiato il metodo inString ()
, ma è stata questa la conseguenza prevista?
Inoltre mi rendo conto che BigDecimal
ha doubleValue ()
ecc., ma la libreria con cui sto lavorando utilizza utilmente un toString ()
e Non posso cambiarlo :-(
Saluti.
Soluzione
Bene, l'API API risolve questa apparente incoerenza nel costruttore BigDecimal (double val)
:
I risultati di questo costruttore possono essere in qualche modo imprevedibili. Uno potrebbe supponiamo che scrivere di nuovo BigDecimal (0.1) in Java crea un BigDecimal che è esattamente uguale a 0.1 (un valore non scalato di 1, con una scala di 1), ma in realtà è uguale a 0,1000000000000000055511151231257827021181583404541015625. Questo perché 0.1 non può essere rappresentato esattamente come un doppio (o, del resto, come una frazione binaria di qualsiasi lunghezza finita). Quindi, il valore che viene passato al costruttore non è esattamente uguale a 0.1, nonostante le apparenze.
Il costruttore di String, d'altra parte, è perfettamente prevedibile: la creazione di nuovi BigDecimal ("0,1") crea un BigDecimal che è esattamente uguale a 0,1, come ci si aspetterebbe. Pertanto, è generalmente raccomandato che il file Costruttore di stringhe da utilizzare in preferenza a questo.
Quando un doppio deve essere usato come fonte per un BigDecimal , nota che questo costruttore fornisce un esatto conversione; non dà lo stesso risultato come convertire il doppio in a String usando il Metodo Double.toString (doppio) e quindi utilizzando BigDecimal (String) costruttore. Per ottenere quel risultato, usa il metodo valueOf statico (doppio) .
Morale della storia: il dolore sembra autoinflitto, basta usare new BigDecimal (String val)
o BigDecimal.valueOf (double val)
invece =)
Altri suggerimenti
Il tuo problema non ha nulla a che fare con BigDecimal
e tutto con Double
, che non può rappresentare 13.3 in modo accurato, poiché utilizza frazioni binarie internamente.
Quindi il tuo errore viene introdotto nella prima riga. Il primo BigDecimal
semplicemente lo preserva, mentre String.valueOf ()
esegue alcuni arrotondamenti di pesce che fanno sì che il secondo abbia il contenuto desiderato, praticamente per fortuna.
Potresti voler informarti su come vengono implementati i valori in virgola mobile ( IEEE 754-1985 ). E improvvisamente, tutto diventerà cristallino.
Non è colpa di BigDecimal
- è colpa di double
. BigDecimal
rappresenta accuratamente il valore esatto di d
. String.valueOf
mostra solo il risultato con alcune cifre decimali.
Le frazioni rappresentate con tipi di numero binario (ad es. double
, float
) non possono essere archiviate accuratamente in questi tipi.
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
*/
uscita:
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
Usa semplicemente BigDecimal.valueOf(d)
o nuovi BigDecimal (s)
.