Conserver la précision avec double en Java

Réponses:

151

Comme d'autres l'ont mentionné, vous voudrez probablement utiliser la BigDecimalclasse, si vous voulez avoir une représentation exacte de 11.4.

Maintenant, une petite explication sur pourquoi cela se produit:

Les types primitifs floatet doubleen Java sont des nombres à virgule flottante , où le nombre est stocké sous forme de représentation binaire d'une fraction et d'un exposant.

Plus spécifiquement, une valeur à virgule flottante double précision telle que le doubletype est une valeur 64 bits, où:

  • 1 bit indique le signe (positif ou négatif).
  • 11 bits pour l'exposant.
  • 52 bits pour les chiffres significatifs (la partie fractionnaire sous forme de binaire).

Ces parties sont combinées pour produire une doublereprésentation d'une valeur.

(Source: Wikipedia: Double précision )

Pour une description détaillée de la façon dont les valeurs à virgule flottante sont gérées en Java, reportez-vous à la Section 4.2.3: Types, formats et valeurs à virgule flottante de la spécification du langage Java.

Les byte, char, int, longtypes sont fichées numéros, qui sont representions exactes des nombres. Contrairement aux nombres à virgule fixe, les nombres à virgule flottante ne pourront parfois (supposer que "la plupart du temps") ne pourront pas renvoyer une représentation exacte d'un nombre. C'est la raison pour laquelle vous vous retrouvez à 11.399999999999la suite de 5.6 + 5.8.

Lorsque vous avez besoin d'une valeur exacte, telle que 1.5 ou 150.1005, vous voudrez utiliser l'un des types à virgule fixe, qui sera en mesure de représenter le nombre exactement.

Comme cela a déjà été mentionné à plusieurs reprises, Java a une BigDecimalclasse qui gère de très grands nombres et de très petits nombres.

À partir de la référence API Java pour la BigDecimalclasse:

Nombres décimaux signés immuables et de précision arbitraire. Un BigDecimal se compose d'une valeur entière non mise à l'échelle de précision arbitraire et d'une échelle d'entiers de 32 bits. S'il est nul ou positif, l'échelle correspond au nombre de chiffres à droite de la virgule décimale. S'il est négatif, la valeur non mise à l'échelle du nombre est multipliée par dix à la puissance de la négation de l'échelle. La valeur du nombre représenté par le BigDecimal est donc (unscaledValue × 10 ^ -scale).

Il y a eu de nombreuses questions sur Stack Overflow concernant la question des nombres à virgule flottante et sa précision. Voici une liste de questions connexes susceptibles de vous intéresser:

Si vous voulez vraiment entrer dans les moindres détails des nombres à virgule flottante, jetez un coup d'œil à ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante .

coobird
la source
3
En fait, il y a généralement 53 bits significatifs car le 1 avant le point "décimal" est implicite pour toutes les valeurs sauf dénormalisées, ce qui donne un peu de précision supplémentaire. Par exemple, 3 est stocké sous la forme (1.) 1000 ... x 2 ^ 1 tandis que 0,5 est stocké sous la forme (1.) 0000 ... x 2 ^ -1 Lorsque la valeur est dénormalisée (tous les bits d'exposant sont à zéro), il peut, et sera généralement moins de chiffres significatifs, par exemple 1 x 2 ^ -1030 est stocké sous la forme (0.) 00000001 x 2 ^ -1022 donc sept chiffres significatifs ont été sacrifiés à l'échelle.
Sarah Phillips
1
Il convient de noter que bien que ce BigDecimalsoit beaucoup plus lent que doubledans ce cas, il n'est pas nécessaire car le double a 15 décimales de précision, il vous suffit d'arrondir.
Peter Lawrey
2
@PeterLawrey Il a 15 chiffres décimaux de précision, s'ils sont tous avant la virgule décimale. Tout peut arriver après la virgule décimale, en raison de l'incommensurabilité des fractions décimales et binaires.
Marquis of Lorne
@EJP Vous avez raison, il a environ 15 chiffres significatifs de précision. Il peut être 16 mais il est plus sûr de supposer qu'il est 15 ou peut-être 14.
Peter Lawrey
La correction de @PeterLawrey EJP était due à ma question: stackoverflow.com/questions/36344758/ ... pourriez-vous s'il vous plaît expliquer pourquoi ce n'est pas exactement 15 et dans quelles situations cela peut être 16 ou 14?
Shivam Sinha le
103

Lorsque vous entrez un nombre double, par exemple, 33.33333333333333la valeur que vous obtenez est en fait la valeur de double précision représentable la plus proche, qui est exactement:

33.3333333333333285963817615993320941925048828125

Diviser cela par 100 donne:

0.333333333333333285963817615993320941925048828125

qui n'est pas non plus représentable en tant que nombre à double précision, donc encore une fois, il est arrondi à la valeur représentable la plus proche, qui est exactement:

0.3333333333333332593184650249895639717578887939453125

Lorsque vous imprimez cette valeur, elle est à nouveau arrondie à 17 chiffres décimaux, ce qui donne:

0.33333333333333326
Stephen Canon
la source
114
À tous ceux qui liront ceci à l'avenir et se demandent pourquoi la réponse n'a rien à voir avec la question: un modérateur a décidé de fusionner la question à laquelle j'ai (et d'autres) répondu avec cette question, plutôt différente.
Stephen Canon
Comment connaissez-vous la valeur double exacte?
Michael Yaworski
@mikeyaworski en.wikipedia.org/wiki/Double-precision_floating-point_format Voir des exemples de double précision
Jaydee
23

Si vous souhaitez simplement traiter les valeurs sous forme de fractions, vous pouvez créer une classe Fraction contenant un champ de numérateur et de dénominateur.

Écrivez des méthodes pour ajouter, soustraire, multiplier et diviser ainsi qu'une méthode toDouble. De cette façon, vous pouvez éviter les flottants pendant les calculs.

EDIT: mise en œuvre rapide,

public class Fraction {

private int numerator;
private int denominator;

public Fraction(int n, int d){
    numerator = n;
    denominator = d;
}

public double toDouble(){
    return ((double)numerator)/((double)denominator);
}


public static Fraction add(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop + bTop, a.denominator * b.denominator);
    }
    else{
        return new Fraction(a.numerator + b.numerator, a.denominator);
    }
}

public static Fraction divide(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}

public static Fraction multiply(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}

public static Fraction subtract(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop-bTop, a.denominator*b.denominator);
    }
    else{
        return new Fraction(a.numerator - b.numerator, a.denominator);
    }
}

}
Viral Shah
la source
1
Sûrement numeratoret denominatordevrait être ints? Pourquoi voudriez-vous une précision en virgule flottante?
Samir Talwar
Je suppose que ce n'est pas vraiment nécessaire, mais cela évite de lancer la fonction toDouble pour que le code se lit mieux.
Viral Shah
5
ViralShah: Il introduit également potentiellement une erreur en virgule flottante lorsqu'il s'agit d'opérations mathématiques. Étant donné que le but de cet exercice est précisément d'éviter cela, il semble prudent de le modifier.
Samir Talwar
Édité pour utiliser des ints au lieu de doubles, pour les raisons mentionnées par Samir Talwar ci-dessus.
Viral Shah
3
Cette implémentation des fractions pose des problèmes car elle ne les réduit pas à une forme la plus simple. 2/3 * 1/2 donne 2/6 où vous voulez vraiment que la réponse soit 1/3. Idéalement, dans le constructeur, vous voulez trouver le pgcd du numérateur et du diviseur et diviser les deux par cela.
Salix alba le
15

Observez que vous auriez le même problème si vous utilisiez une arithmétique décimale à précision limitée et que vous vouliez traiter 1/3: 0,3333333333 * 3 est 0,999999999, et non 1,00000000.

Malheureusement, 5.6, 5.8 et 11.4 ne sont tout simplement pas des nombres ronds en binaire, car ils impliquent des quintes. Ainsi, leur représentation flottante n'est pas exacte, tout comme 0,3333 n'est pas exactement 1/3.

Si tous les nombres que vous utilisez sont des décimales non récurrentes et que vous voulez des résultats exacts, utilisez BigDecimal. Ou comme d'autres l'ont dit, si vos valeurs sont comme de l'argent dans le sens où elles sont toutes un multiple de 0,01 ou 0,001, ou quelque chose du genre, multipliez tout par une puissance fixe de 10 et utilisez int ou long (l'addition et la soustraction sont trivial: attention aux multiplications).

Cependant, si vous êtes satisfait du binaire pour le calcul, mais que vous voulez simplement imprimer les choses dans un format légèrement plus convivial, essayez java.util.Formatterou String.format. Dans la chaîne de format, spécifiez une précision inférieure à la précision totale d'un double. Pour 10 chiffres significatifs, disons que 11,399999999999 est 11,4, le résultat sera donc presque aussi précis et plus lisible par l'homme dans les cas où le résultat binaire est très proche d'une valeur ne nécessitant que quelques décimales.

La précision à spécifier dépend un peu de la quantité de calculs que vous avez fait avec vos nombres - en général plus vous en faites, plus l'erreur s'accumule, mais certains algorithmes l'accumulent beaucoup plus rapidement que d'autres (ils sont appelés "instables" car par opposition à «stable» en ce qui concerne les erreurs d'arrondi). Si tout ce que vous faites est d'ajouter quelques valeurs, je suppose que le fait de ne laisser qu'une décimale de précision réglera les choses. Expérience.

Steve Jessop
la source
3
Non, n'utilisez pas le double avec des valeurs monétaires! Vous avez besoin de précision avec de l'argent, utilisez plutôt BigDecimal. Sinon, votre réponse est bonne. Tout ce dont vous avez besoin de précision, utilisez BigDecimal, si la précision n'est pas si importante, vous pouvez utiliser float ou double.
MetroidFan2002
1
La question n'indique plus ou n'implique plus que l'argent est impliqué. Je dis spécifiquement d'utiliser BigDecimal ou des entiers pour de l'argent. Quel est le problème?
Steve Jessop
1
Et égal à "ne pas utiliser le double pour l'argent" est "ne pas utiliser BigDecimal ou le double pour les tiers". Mais parfois, un problème implique une division, auquel cas toutes les bases non divisibles par tous les facteurs premiers de tous les dénominateurs sont à peu près également mauvaises.
Steve Jessop
1
.9999 = 1 si votre précision est inférieure à 4 chiffres significatifs
Brian Leahy
9

Vous pouvez envisager d'utiliser la classe java.math.BigDecimal de java si vous avez vraiment besoin de mathématiques de précision. Voici un bon article d'Oracle / Sun sur le cas de BigDecimal . Bien que vous ne puissiez jamais représenter 1/3 comme quelqu'un l'a mentionné, vous pouvez avoir le pouvoir de décider exactement à quel point vous voulez que le résultat soit précis. setScale () est votre ami .. :)

Ok, parce que j'ai beaucoup trop de temps libre pour le moment, voici un exemple de code qui se rapporte à votre question:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * [email protected]
 * on Mar 17, 2010 at  11:05:16 PM
 */
public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is " + result);
       //will print : result is 0.3333333333333333


    }
}

et pour brancher ma nouvelle langue préférée, Groovy, voici un exemple plus soigné de la même chose:

import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println "result is " + first/second   // will print: result is 0.33333333333333
Vinny
la source
5

À peu près sûr que vous auriez pu en faire un exemple en trois lignes. :)

Si vous voulez une précision exacte, utilisez BigDecimal. Sinon, vous pouvez utiliser des nombres entiers multipliés par 10 ^ quelle que soit la précision souhaitée.

Dustin
la source
5

Comme d'autres l'ont noté, toutes les valeurs décimales ne peuvent pas être représentées sous forme binaire car la valeur décimale est basée sur des puissances de 10 et binaire sur des puissances de deux.

Si la précision compte, utilisez BigDecimal, mais si vous voulez juste une sortie conviviale:

System.out.printf("%.2f\n", total);

Te donnera:

11.40
Draemon
la source
5

Vous ne pouvez pas, car 7.3 n'a pas de représentation finie en binaire. Le plus proche que vous pouvez obtenir est 2054767329987789/2 ** 48 = 7,3 + 1/1407374883553280.

Jetez un œil à http://docs.python.org/tutorial/floatingpoint.html pour plus d'explications. (C'est sur le site Web de Python, mais Java et C ++ ont le même «problème».)

La solution dépend exactement de votre problème:

  • Si c'est simplement que vous n'aimez pas voir tous ces chiffres de bruit, corrigez le formatage de votre chaîne. N'affichez pas plus de 15 chiffres significatifs (ou 7 pour float).
  • Si c'est parce que l'inexactitude de vos nombres rompt des choses comme les déclarations "if", alors vous devriez écrire if (abs (x - 7.3) <TOLERANCE) au lieu de if (x == 7.3).
  • Si vous travaillez avec de l'argent, ce que vous voulez probablement vraiment, c'est un point fixe décimal. Stockez un nombre entier de cents ou quelle que soit la plus petite unité de votre devise.
  • (TRÈS IMPROBABLE) Si vous avez besoin de plus de 53 bits significatifs (15-16 chiffres significatifs) de précision, utilisez un type à virgule flottante de haute précision, comme BigDecimal.
dan04
la source
7.3 peut ne pas avoir une représentation finie en binaire, mais j'obtiens certainement -7.3 quand j'essaye la même chose en C ++
mauvais nom d'utilisateur
2
mauvais nom d'utilisateur: Non, vous ne le faites pas. Il s'affiche simplement de cette façon. Utilisez le format "% .17g" (ou mieux encore, "% .51g") pour voir la vraie réponse.
dan04
4
private void getRound() {
    // this is very simple and interesting 
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is " + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we 
            //  can use formate option in String 
           // which takes 2 parameters one is formte specifier which 
           // shows dicimal places another double value 
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}
sravan
la source
3

Utilisez java.math.BigDecimal

Les doubles sont des fractions binaires en interne, de sorte qu'ils ne peuvent parfois pas représenter des fractions décimales à la décimale exacte.

Kevin Crowell
la source
1
-1 pour recommander aveuglément BigDecimal. Si vous n'avez pas réellement besoin d' arithmétique décimale (c'est-à-dire si vous faites des calculs avec de l'argent), BigDecimal ne vous aide pas. Il ne résout pas toutes vos erreurs en virgule flottante: vous devez toujours traiter 1/3 * 3 = 0.99999999999999999999999999 et sqrt (2) ** 2 = 1.999999999999999999999999999. De plus, BigDecimal entraîne une énorme pénalité de vitesse. Pire encore, en raison du manque de surcharge d'opérateurs de Java, vous devez réécrire tout votre code.
dan04
2
@ dan04 - Si vous faites un calcul avec de l'argent, pourquoi utiliser une représentation flottante en sachant l'erreur inhérente ... Comme il n'y a pas de fraction de centimes, vous pouvez utiliser des décimales et calculer des centimes au lieu d'utiliser un dollar approximatif, vous avez le montant exact en centimes. Si vous voulez vraiment la fraction de cent, utilisez un long et calculez des milliers de centimes. De plus, l'OP ne faisait aucune mention de nombres irrationnels, tout ce qui le préoccupait était l'addition. Lisez attentivement le message et comprenez le problème avant de répondre, cela pourrait vous éviter de vous embarrasser.
Newtopian
3
@Newtopian: Je n'ai pas de quoi être gêné. Le PO n'a fait AUCUNE mention d'argent, ni aucune indication que son problème ait une décimale inhérente.
dan04
@ dan04 - Non, l'OP n'a pas ... VOUS l'avez fait Et aveuglément a offert une opinion hors contexte à ce qui était probablement une réponse parfaitement acceptable étant donné le peu de détails fournis
Newtopian
2

Multipliez tout par 100 et stockez-le en centimes.

Paul Tomblin
la source
2
@Draemon - regardez le message avant la dernière édition - tout ce truc "shoppingTotal", "calcGST" et "calcPST" me ressemble à de l'argent.
Paul Tomblin
2

Les ordinateurs stockent les nombres en binaire et ne peuvent pas réellement représenter des nombres tels que 33,333333333 ou 100,0 exactement. C'est l'une des choses délicates à propos de l'utilisation des doubles. Vous devrez simplement arrondir la réponse avant de la montrer à un utilisateur. Heureusement, dans la plupart des applications, vous n'avez pas besoin d'autant de décimales.

Jay Askren
la source
Je fais des calculs de cotes, je préférerais avoir la plus grande précision possible. Mais je comprends qu'il y a des limites
Aly
2

Les nombres à virgule flottante diffèrent des nombres réels en ce que pour tout nombre à virgule flottante donné, il existe un nombre à virgule flottante immédiatement supérieur. Identique aux entiers. Il n'y a pas d'entier entre 1 et 2.

Il n'y a aucun moyen de représenter 1/3 comme un flotteur. Il y a un flotteur en dessous et un flotteur au-dessus, et il y a une certaine distance entre eux. Et 1/3 est dans cet espace.

Apfloat pour Java prétend fonctionner avec des nombres à virgule flottante de précision arbitraire, mais je ne l'ai jamais utilisé. Vaut probablement le détour. http://www.apfloat.org/apfloat_java/

Une question similaire a été posée ici avant la bibliothèque haute précision à virgule flottante Java

Pointe
la source
1

Les doubles sont des approximations des nombres décimaux dans votre source Java. Vous voyez la conséquence de la discordance entre le double (qui est une valeur codée en binaire) et votre source (qui est codée en décimal).

Java produit l'approximation binaire la plus proche. Vous pouvez utiliser java.text.DecimalFormat pour afficher une valeur décimale plus belle.

S.Lott
la source
1

Utilisez un BigDecimal. Il vous permet même de spécifier des règles d'arrondi (comme ROUND_HALF_EVEN, qui minimisera l'erreur statistique en arrondissant au voisin pair si les deux sont de la même distance; c'est-à-dire que 1,5 et 2,5 sont arrondis à 2).

Adam Jaskiewicz
la source
1

Réponse courte: utilisez toujours BigDecimal et assurez-vous que vous utilisez le constructeur avec l' argument String , pas le double.

De retour à votre exemple, le code suivant imprimera 11.4, comme vous le souhaitez.

public class doublePrecision {
    public static void main(String[] args) {
      BigDecimal total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6"));
      total = total.add(new BigDecimal("5.8"));
      System.out.println(total);
    }
}
jackycflau
la source
0

Découvrez BigDecimal, il gère les problèmes liés à l'arithmétique en virgule flottante comme ça.

Le nouvel appel ressemblerait à ceci:

term[number].coefficient.add(co);

Utilisez setScale () pour définir le nombre de décimales à utiliser.

Château sombre
la source
0

Pourquoi ne pas utiliser la méthode round () de la classe Math?

// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4
Monsieur Chat
la source
0

Si vous n'avez pas d'autre choix que d'utiliser des valeurs doubles, vous pouvez utiliser le code ci-dessous.

public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision + "f", sum);
    sum = Double.parseDouble(s);
    return sum;
}
user5734382
la source
-1

Ne gaspillez pas vos efforts en utilisant BigDecimal. Dans 99,99999% des cas, vous n'en avez pas besoin. java double type est bien sûr approximatif mais dans presque tous les cas, il est suffisamment précis. Notez que vous avez une erreur au 14e chiffre significatif. C'est vraiment négligeable!

Pour obtenir une bonne sortie, utilisez:

System.out.printf("%.2f\n", total);
Maciek D.
la source
2
Je pense qu'il est préoccupé par la sortie, pas par la précision numérique. et BigDecimal ne serait d'aucune aide si vous par exemple. divisez par trois. Cela peut même aggraver les choses ...
Maciek D.
Vous ne devriez jamais jamais utiliser la virgule flottante pour de l'argent. J'ai vu des retouches majeures imposées à un entrepreneur qui a enfreint cette règle malgré ses instructions.
Marquis of Lorne