Double vs BigDecimal?

303

Je dois calculer quelques variables à virgule flottante et mon collègue me suggère de les utiliser à la BigDecimalplace doublecar ce sera plus précis. Mais je veux savoir de quoi il s'agit et comment en tirer le meilleur parti BigDecimal?

Truong Ha
la source
Découvrez celui-ci; stackoverflow.com/questions/322749/…
Espen Schulstad

Réponses:

446

A BigDecimalest une façon exacte de représenter les nombres. A Doublea une certaine précision. Travailler avec des doubles de différentes grandeurs (disons d1=1000.0et d2=0.001) pourrait entraîner 0.001une chute totale lors de la sommation car la différence de grandeur est si grande. Avec BigDecimalcela ne se produirait pas.

L'inconvénient BigDecimalest 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 Doublesont tendance à être assez bons.

Je recommande la lecture de la javadoc de BigDecimalcomme ils le font mieux expliquer les choses que je fais ici :)

extranéon
la source
Oui, je calcule le prix du stock, donc je pense que BigDecimal est utile dans ce cas.
Truong Ha
5
@Truong Ha: Lorsque vous travaillez avec des prix, vous souhaitez utiliser BigDecimal. Et si vous les stockez dans la base de données, vous voulez quelque chose de similaire.
extraneon
98
Dire que "BigDecimal est une façon exacte de représenter les nombres" est trompeur. 1/3 et 1/7 ne peuvent pas être exprimés exactement dans un système numérique de base 10 (BigDecimal) ou dans un système numérique de base 2 (flottant ou double). 1/3 pourrait être exprimé exactement en base 3, base 6, base 9, base 12, etc. et 1/7 pourrait être exprimé exactement en base 7, base 14, base 21, etc. Les avantages BigDecimal sont qu'il s'agit d'une précision arbitraire et que les humains sont habitués aux erreurs d'arrondi que vous obtenez en base 10.
procrastinate_later
3
Bon point sur le fait qu'il soit plus lent, m'aide à comprendre pourquoi le code de l'équilibreur de charge du ruban Netflix traite des doubles, puis a des lignes comme celle-ci:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
michaelok
@extraneon Je pense que vous voulez dire "si la précision est un must, utilisez BigDecimal", un Double aurait plus de "précision" (plus de chiffres).
jspinella
164

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? ;)

Basilic
la source
11
@eldjon Ce n'est pas vrai, regardez cet exemple: BigDecimal two = new BigDecimal ("2"); BigDecimal huit = nouveau BigDecimal ("8"); System.out.println (two.divide (huit)); Cela imprime 0,25.
Ludvig W
4
double forevr: D
vach
Néanmoins, si vous utilisez un flotteur à la place, vous obtenez la même précision que BigDecimal dans ce cas, mais des performances bien meilleures
EliuX
3
@EliuX Float peut fonctionner avec 0.03-0.02, mais d'autres valeurs sont encore imprécises: System.out.println(0.003f - 0.002f);BigDecimal est exact:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin
50

Il y a deux différences principales par rapport au double:

  • Précision arbitraire, de manière similaire à BigInteger, ils peuvent contenir un nombre de précision et de taille arbitraires
  • Base 10 au lieu de Base 2, un BigDecimal est une échelle n * 10 ^ où n est un entier signé arbitrairement grand et l'échelle peut être considérée comme le nombre de chiffres pour déplacer le séparateur décimal vers la gauche ou la droite

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).

Meros
la source
2
Cette réponse explique vraiment la différence et la raison d'utiliser BigDecimal sur le double. Les problèmes de performance sont secondaires.
Vortex
Ce n'est pas vrai à 100%. Vous avez écrit qu'un BigDecimal est "échelle n * 10 ^". Java ne fait cela que pour les nombres négatifs. Si correct serait: "unscaledValue × 10 ^ -scale". Pour les nombres positifs, le BigDecimal se compose d'une "valeur non mise à l'échelle entière de précision arbitraire et d'une échelle entière de 32 bits" tandis que l'échelle est le nombre de chiffres à droite de la virgule décimale.
la main de NOD
25

Si vous écrivez une valeur fractionnaire comme une valeur 1 / 7dé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/10ou 1/100exprimé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/7sans 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 BigDecimala 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.

Olivier Jacot-Descombes
la source
7

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.

pêcheur
la source
BigDecimal fait partie de la bibliothèque numérique de précision arbitraire de Java . «En interne» n'a pas de sens dans ce contexte, d'autant plus qu'il a été écrit par IBM.
Marquis de Lorne
@EJP: J'ai examiné la classe BigDecimal et j'ai appris que seule une partie de celle-ci était écrite par IBM. Commentaire sur les droits d'auteur ci-dessous: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK
7

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 
jfajunior
la source