web-dev-qa-db-fra.com

Double vs BigDecimal?

Je dois calculer des variables à virgule flottante et mon collègue me suggère d'utiliser BigDecimal au lieu de double car ce sera plus précis. Mais je veux savoir ce que c'est et comment tirer le meilleur parti de BigDecimal?

260
Truong Ha

Un BigDecimal est une manière exacte de représenter des nombres. Un Double a une certaine précision. Travailler avec des doubles de différentes magnitudes (par exemple, d1=1000.0 et d2=0.001) peut entraîner la suppression complète de 0.001 lors de la somme, car la différence de magnitude est si grande. Avec BigDecimal cela ne se produirait pas.

L'inconvénient de BigDecimal est qu'il est plus lent et qu'il est un peu plus difficile de programmer les algorithmes de cette façon (en raison de +-* et / n'étant pas surchargé).

Si vous avez affaire à de l'argent ou si la précision est indispensable, utilisez BigDecimal. Sinon, Doubles a tendance à être assez bon.

Je recommande de lire le javadoc de BigDecimal car ils expliquent les choses mieux que moi ici :)

386
extraneon

Mon anglais n'est pas bon, je vais donc é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 encore utiliser le double? ;)

146
Basil

Il y a deux différences principales avec double:

  • Précision arbitraire, tout comme BigInteger, ils peuvent contenir le nombre de précision arbitraire et la taille
  • Base 10 au lieu de la base 2, un BigDecimal est n * 10 ^ échelle où n est un grand entier signé arbitraire et échelle peut être considérée comme le nombre de chiffres pour déplacer le point décimal à gauche ou à droite

Si vous utilisez BigDecimal pour les calculs monétaires, ce n’est pas parce qu’il peut représenter n’importe quel nombre, mais qu’il peut représenter tous les nombres pouvant être représentés sous forme décimale et comprenant pratiquement tous les chiffres du monde monétaire (vous ne transférez jamais 1/3 $ à quelqu'un).

43
Meros

Si vous écrivez une valeur fractionnaire telle que 1 / 7 en tant que valeur 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 introduirez inévitablement une erreur d'arrondi (ou de troncature).

Des nombres tels que 1/10 ou 1/100, exprimés sous forme de nombres binaires avec une partie fractionnaire, ont également un nombre infini de chiffres après le point décimal:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles stocke les valeurs sous forme binaire et peut donc introduire une erreur uniquement en convertissant un nombre décimal en un nombre binaire, sans même effectuer de calcul arithmétique.

Les nombres décimaux (comme BigDecimal) 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 ou à virgule flottante au sens général (par exemple, il ne peut pas stocker 1/7 sans perte de précision), mais il est plus précis pour les nombres dont le nombre est fini. nombre de chiffres décimaux, comme c'est souvent le cas pour les calculs d'argent.

Le BigDecimal de Java présente l’avantage supplémentaire de pouvoir comporter un nombre arbitraire (mais fini) de chiffres des deux côtés du point décimal, 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 toute une gamme d’applications allant des applications financières aux applications scientifiques (c’est là que je suis en quelque sorte).

Il n'y a rien de mal à utiliser des doubles pour certains calculs. Supposons cependant 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 pose un problème douloureux d’erreur d’arrondi.

BigDecimal, en revanche, inclut 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 Monde:

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.

Faites toutefois attention 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 = new BigDecimal ("0.33333333333");

utilise une représentation de chaîne de 1/3 pour représenter ce nombre qui se répète à l'infini avec un degré de précision spécifié. L'erreur d'arrondi est probablement si profonde que la JVM ne perturbera pas la plupart de vos calculs pratiques. De mon expérience personnelle, j’ai cependant vu une vague d’arrondi. La méthode setScale est importante à cet égard, comme le montre la documentation Oracle.

7
fishermanhat

Si vous traitez avec le calcul, il y a des lois sur la façon dont vous devriez calculer et quelle précision vous devriez utiliser. Si vous échouez, vous ferez quelque chose d'illégal. La seule raison réelle est que la représentation en bits des décimales n'est pas précise. Comme le dit si simplement Basil, un exemple est la meilleure explication. Juste 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)));
}

Sortie:

Double:  0,3 - 0,2 = 0.09999999999999998
Float:   0,3 - 0,2 = 0.10000001
BigDec:  0,3 - 0,2 = 0.1

Nous avons aussi ça:

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 
6
jfajunior