& # 8220; new BigDecimal (13.3D) & # 8221; imprécise & # 8220; 13.3000000000000007105 .. & # 8221 ;?
-
19-08-2019 - |
Question
Comment se fait-il que le BigDecimal
de Java puisse être aussi douloureux?
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-t-il une situation dans laquelle le résultat 1 serait souhaité? Je sais que Java 1.5 a modifié la méthode toString ()
mais était-ce la conséquence escomptée?
Je réalise aussi que BigDecimal
a doubleValue ()
, etc., mais la bibliothèque avec laquelle je travaille utilise utilement un toString ()
et Je ne peux pas changer ça: - (
A bientôt.
La solution
Eh bien, le API corrige cette incohérence apparente dans le constructeur BigDecimal (double valeur)
:
Les résultats de ce constructeur peuvent être quelque peu imprévisibles. On pourrait suppose que l'écriture nouvelle BigDecimal (0.1) en Java crée un BigDecimal qui est exactement égal à 0,1 (une valeur non échelonnée de 1, avec une échelle de 1), mais elle est en réalité égale à 0.1000000000000000055511151231257827021181583404541015625. En effet, 0,1 ne peut pas être représenté exactement comme un double (ou, d'ailleurs, en tant que fraction binaire de toute longueur finie). Ainsi, la valeur qui est transmis à la constructeur n'est pas exactement égal à 0,1 malgré les apparences.
Le constructeur String, quant à lui, est parfaitement prévisible: L’écriture de nouvelles données BigDecimal ("0.1") crée un BigDecimal qui est exactement égal à 0,1, comme on pourrait s'y attendre. Par conséquent, il est généralement recommandé que le Constructeur de chaîne être utilisé dans préférence pour celui-ci.
Lorsqu'un double doit être utilisé comme source d'un BigDecimal , notez que ce constructeur fournit une exacte conversion; ça ne donne pas le même résultat en convertissant le double en un String en utilisant le Méthode Double.toString (double) et puis en utilisant le BigDecimal (String) constructeur. Pour obtenir ce résultat, utilisez la méthode statique valueOf (double) .
Morale de l’histoire: la douleur semble s’auto-infliger, il suffit d’utiliser new BigDecimal (String val)
ou BigDecimal.valueOf (double valeur)
à la place =)
Autres conseils
Votre problème n'a rien à voir avec BigDecimal
et tout avec Double
, qui ne peut pas représenter 13.3 avec précision, car il utilise des fractions binaires en interne.
Votre erreur est donc introduite à la toute première ligne. Le premier BigDecimal
le conserve tout simplement, alors que String.valueOf ()
effectue des arrondis ludiques qui donnent au second contenu le contenu souhaité, plutôt par chance.
Vous voudrez peut-être vous informer sur la façon dont les valeurs en virgule flottante sont implémentées ( IEEE 754-1985 ). Et tout à coup, tout deviendra limpide.
Ce n'est pas la faute de BigDecimal
- c'est la faute de double
. BigDecimal
représente avec précision la valeur exacte de d
. String.valueOf
n'affiche que le résultat à quelques décimales près.
Les fractions représentées avec des types de nombres binaires (c'est-à-dire double
, float
) ne peuvent pas être stockées avec précision dans ces types.
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
*/
sortie:
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
Utilisez simplement BigDecimal.valueOf (d)
ou nouveau BigDecimal (s)
.