Comment calculer correctement avec BigDecimal de Java? [dupliquer]
Comme je l'ai compris, BigDecimalest là pour traiter correctement les nombres avec des décimales fixes (c'est-à-dire l'argent). Voici un petit programme que j'ai écrit:
import java.math.*;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal(11.22, new MathContext(2, RoundingMode.HALF_UP));
System.out.println("a: " + a);
BigDecimal b = a.add(new BigDecimal(0.04));
System.out.println("b: " + b);
}
}
Je m'attendais à voir:
a: 11.22
b: 11.26
Mais ce que j'ai, c'est:
a: 11
b: 11.040000000000000000832667268468867405317723751068115234375
J'ai défini adeux décimales, mais il ne les imprime pas tous les deux, et il les oublie même et les arrondit à un entier. Pourquoi? bdevrait ajouter 0,04 et savoir de apour avoir deux décimales également. C'était du moins ce à quoi je m'attendais.
Comment cela est-il résolu correctement en utilisant BigDecimal edit: avec deux valeurs doubles en entrée et un nombre connu de décimales? [C'est-à-dire, parce qu'une API ne me donne rien d'autre que ces deux doubles.] (Je sais qu'il existe d'autres moyens de calculer de manière fiable avec de l'argent (en commençant par calculer en cents avec int), mais cela sort du cadre de ma question.)
Réponses
Comme @Progman l'a également écrit, l'utilisation du double constructeur créera une représentation décimale exacte de la valeur double, ce qui est déconseillé dans la documentation .
Cependant, la raison pour laquelle vous obtenez 11 au lieu de 11,22 est que vous avez défini la précision de votre MathContext sur 2. La
précision est le nombre de chiffres utilisés et non le nombre de décimales. Donc, si vous modifiez votre code pour utiliser 4 comme précision, vous obtiendrez le résultat
a: 11.22
b: 11.260000000000000000832667268468867405317723751068115234375
Toujours avec le problème de la double valeur utilisée, mais maintenant avec plus de décimales!
La définition de la précision en nombre de chiffres se trouve dans la documentation de la documentation de la classe MathContext
N'utilisez pas le constructeur BigDecimal avec l' doubleargument pour exactement cette raison:
- Les résultats de ce constructeur peuvent être quelque peu imprévisibles. On pourrait supposer que l'écriture
new BigDecimal(0.1)en Java crée un BigDecimal qui est exactement égal à0.1(une valeur non mise à l'échelle de1, avec une échelle de1), mais il est en fait égal à0.1000000000000000055511151231257827021181583404541015625. C'est parce que0.1ne peut pas être représenté exactement comme undouble(ou, pour cette question, comme une fraction binaire de toute longueur finie). Ainsi, la valeur qui est transmise au constructeur n'est pas exactement égale à0.1, malgré les apparences.
Utilisez le constructeur avec l' Stringargument :
public static void main(String[] args) {
BigDecimal a = new BigDecimal("11.22");
System.out.println("a: " + a);
BigDecimal b = a.add(new BigDecimal("0.04"));
System.out.println("b: " + b);
}
Cela générera la sortie comme prévu:
a: 11.22
b: 11.26
Le problème avec l'utilisation de valeurs doubles au lieu de chaînes comme argument BigDecimalest qu'elles ne sont généralement pas exactes et entraîneront la répétition des expansions décimales. En fait, seuls les nombres à virgule flottante qui sont des puissances de 5et ou 2dans le dénominateur peuvent être représentés exactement comme un flottant ou un double (par exemple 1/20 = .05 1/4 = .25, 1/5 = .2). C'est parce que 5et 2sont les seuls facteurs premiers de base 10et renverront une expansion finie (c'est-à-dire non répétitive) de la fraction. Toute autre valeur entraînera une expansion décimale répétée (par exemple 1/3 = 0,3333333333, 1/6 = 0,16666666), et c'est ce qui cause votre problème.
En spécifiant des chaînes au lieu de doubles, BigDecimalpeut fonctionner sur la valeur souhaitée attendue par opposition à la valeur binaire sur laquelle il a un contrôle limité ou aucun contrôle.
Vos valeurs avaient-elles été les suivantes.
BigDecimal a = new BigDecimal(11.25);
System.out.println("a: " + a);
BigDecimal b = a.add(new BigDecimal(.50));
System.out.println("b: " + b);
La sortie aurait été
11.25
11.75
Parce que les deux parties fractionnaires et la somme résultante n'ont que 5 et 2 comme diviseurs.
Pour cette raison, vous devez spécifier une représentation sous forme de chaîne de valeurs à virgule flottante lors de l'initialisation des BigDecimalobjets.
Pour plus d'informations sur la représentation interne des nombres à virgule flottante, consultez IEEE 754