Je dois calculer quelques variables à virgule flottante et mon collègue me suggère de les utiliser à la BigDecimal
place double
car ce sera plus précis. Mais je veux savoir de quoi il s'agit et comment en tirer le meilleur parti BigDecimal
?
Je dois calculer quelques variables à virgule flottante et mon collègue me suggère de les utiliser à la BigDecimal
place double
car ce sera plus précis. Mais je veux savoir de quoi il s'agit et comment en tirer le meilleur parti BigDecimal
?
Réponses:
A BigDecimal
est une façon exacte de représenter les nombres. A Double
a une certaine précision. Travailler avec des doubles de différentes grandeurs (disons d1=1000.0
et d2=0.001
) pourrait entraîner 0.001
une chute totale lors de la sommation car la différence de grandeur est si grande. Avec BigDecimal
cela ne se produirait pas.
L'inconvénient BigDecimal
est qu'il est plus lent et qu'il est un peu plus difficile de programmer des algorithmes de cette façon (en raison de la surcharge +
-
*
et /
sans être surchargés).
Si vous avez affaire à de l'argent, ou si la précision est indispensable, utilisez BigDecimal
. Sinon, ils Doubles
ont tendance à être assez bons.
Je recommande la lecture de la javadoc de BigDecimal
comme ils le font mieux expliquer les choses que je fais ici :)
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
BigDecimal
", un Double aurait plus de "précision" (plus de chiffres).
Mon anglais n'est pas bon donc je vais juste écrire un exemple simple ici.
double a = 0.02;
double b = 0.03;
double c = b - a;
System.out.println(c);
BigDecimal _a = new BigDecimal("0.02");
BigDecimal _b = new BigDecimal("0.03");
BigDecimal _c = _b.subtract(_a);
System.out.println(_c);
Sortie du programme:
0.009999999999999998
0.01
Quelqu'un veut toujours utiliser le double? ;)
System.out.println(0.003f - 0.002f);
BigDecimal est exact:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Il y a deux différences principales par rapport au double:
La raison pour laquelle vous devez utiliser BigDecimal pour les calculs monétaires n'est pas qu'il peut représenter n'importe quel nombre, mais qu'il peut représenter tous les nombres qui peuvent être représentés sous forme décimale et qui incluent pratiquement tous les nombres dans le monde monétaire (vous ne transférez jamais 1/3 $ à quelqu'un).
Si vous écrivez une valeur fractionnaire comme une valeur 1 / 7
décimale, vous obtenez
1/7 = 0.142857142857142857142857142857142857142857...
avec une séquence infinie de 142857
. Comme vous ne pouvez écrire qu'un nombre fini de chiffres, vous introduisez inévitablement une erreur d'arrondi (ou de troncature).
Les nombres comme 1/10
ou 1/100
exprimés en nombres binaires avec une partie fractionnaire ont également un nombre infini de chiffres après la virgule décimale:
1/10 = binary 0.0001100110011001100110011001100110...
Doubles
stocker les valeurs sous forme binaire et peut donc introduire une erreur uniquement en convertissant un nombre décimal en nombre binaire, sans même faire d'arithmétique.
Les nombres décimaux (comme BigDecimal
), d'autre part, stockent chaque chiffre décimal tel quel. Cela signifie qu'un type décimal n'est pas plus précis qu'un type à virgule flottante binaire ou à virgule fixe dans un sens général (c'est-à-dire qu'il ne peut pas être stocké 1/7
sans perte de précision), mais il est plus précis pour les nombres qui ont un nombre fini de chiffres décimaux comme c'est souvent le cas pour les calculs d'argent.
Java BigDecimal
a l'avantage supplémentaire de pouvoir avoir un nombre arbitraire (mais fini) de chiffres des deux côtés de la virgule décimale, limité uniquement par la mémoire disponible.
BigDecimal est la bibliothèque numérique à précision arbitraire d'Oracle. BigDecimal fait partie du langage Java et est utile pour une variété d'applications allant du financier au scientifique (c'est là que je suis).
Il n'y a rien de mal à utiliser des doubles pour certains calculs. Supposons toutefois que vous vouliez calculer Math.Pi * Math.Pi / 6, c'est-à-dire la valeur de la fonction Riemann Zeta pour un argument réel de deux (un projet sur lequel je travaille actuellement). La division en virgule flottante vous présente un problème douloureux d'erreur d'arrondi.
BigDecimal, d'autre part, comprend de nombreuses options pour calculer des expressions avec une précision arbitraire. Les méthodes d'ajout, de multiplication et de division décrites dans la documentation Oracle ci-dessous "prennent la place" de +, * et / dans BigDecimal Java World:
http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html
La méthode compareTo est particulièrement utile dans les boucles while et for.
Soyez toutefois prudent lorsque vous utilisez des constructeurs pour BigDecimal. Le constructeur de chaîne est très utile dans de nombreux cas. Par exemple, le code
BigDecimal onethird = nouveau BigDecimal ("0.33333333333");
utilise une représentation sous forme de chaîne de 1/3 pour représenter ce nombre à répétition infinie avec un degré de précision spécifié. L'erreur d'arrondi est très probablement quelque part si profondément dans la machine virtuelle Java que les erreurs d'arrondi ne perturberont pas la plupart de vos calculs pratiques. Cependant, d'après mon expérience personnelle, j'ai vu des arrondissements se multiplier. La méthode setScale est importante à cet égard, comme le montre la documentation Oracle.
/* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
Si vous avez affaire au calcul, il existe des lois sur la façon de calculer et sur la précision à utiliser. Si vous échouez, vous ferez quelque chose d'illégal. La seule vraie raison est que la représentation binaire des décimales n'est pas précise. Comme Basil l'a dit simplement, un exemple est la meilleure explication. Pour compléter son exemple, voici ce qui se passe:
static void theDoubleProblem1() {
double d1 = 0.3;
double d2 = 0.2;
System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));
float f1 = 0.3f;
float f2 = 0.2f;
System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));
BigDecimal bd1 = new BigDecimal("0.3");
BigDecimal bd2 = new BigDecimal("0.2");
System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}
Production:
Double: 0,3 - 0,2 = 0.09999999999999998
Float: 0,3 - 0,2 = 0.10000001
BigDec: 0,3 - 0,2 = 0.1
Nous avons aussi cela:
static void theDoubleProblem2() {
double d1 = 10;
double d2 = 3;
System.out.println("Double:\t 10 / 3 = " + (d1 / d2));
float f1 = 10f;
float f2 = 3f;
System.out.println("Float:\t 10 / 3 = " + (f1 / f2));
// Exception!
BigDecimal bd3 = new BigDecimal("10");
BigDecimal bd4 = new BigDecimal("3");
System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}
Nous donne la sortie:
Double: 10 / 3 = 3.3333333333333335
Float: 10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion
Mais:
static void theDoubleProblem2() {
BigDecimal bd3 = new BigDecimal("10");
BigDecimal bd4 = new BigDecimal("3");
System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}
A la sortie:
BigDec: 10 / 3 = 3.3333