Comment calculer correctement avec BigDecimal de Java? [dupliquer]

Oct 26 2020

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

1 morras Oct 26 2020 at 20:32

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

4 Progman Oct 26 2020 at 20:23

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 de 1, avec une échelle de 1), mais il est en fait égal à 0.1000000000000000055511151231257827021181583404541015625. C'est parce que 0.1ne peut pas être représenté exactement comme un double(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
1 WJS Oct 26 2020 at 20:58

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