web-dev-qa-db-fra.com

Conserver la précision avec double in Java

public class doublePrecision {
    public static void main(String[] args) {

        double total = 0;
        total += 5.6;
        total += 5.8;
        System.out.println(total);
    }
}

Le code ci-dessus est imprimé:

11.399999999999

Comment pourrais-je obtenir ceci pour simplement imprimer (ou pouvoir l'utiliser comme) 11.4?

131
Deinumite

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

Maintenant, une petite explication sur la raison pour laquelle cela se produit:

Les types primitifs float et double dans Java sont virgule flottante , où le nombre est stocké sous la forme d'une représentation binaire d'un fraction et un exposant.

Plus spécifiquement, une valeur à virgule flottante double précision telle que le type double est une valeur de 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 décimale sous forme binaire).

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

(Source: Wikipedia: Double precision )

Pour une description détaillée de la manière dont les valeurs en virgule flottante sont gérées en Java, voir le Section 4.2.3: Types de point flottant, formats et valeurs de Java Langage Spécification.

Les types byte, char, int, long sont point fixe , qui sont des représentations exactes des nombres. Contrairement aux nombres à virgule fixe, les nombres à virgule flottante seront parfois incapables de renvoyer une représentation exacte d'un nombre. C'est la raison pour laquelle vous vous retrouvez avec 11.399999999999 à la 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 pourra représenter le nombre avec précision.

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

De la référence de l'API Java pour la classe BigDecimal:

Nombres décimaux signés immuables de précision arbitraire. Un BigDecimal consiste en une valeur non mise à l’échelle avec un nombre entier de précision arbitraire et une échelle d’entier sur 32 bits. Si zéro ou positif, l'échelle correspond au nombre de chiffres à droite du point décimal. Si elle est négative, la valeur non échelonnée 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 beaucoup de questions sur le dépassement de pile concernant la question des nombres en virgule flottante et sa précision. Voici une liste de questions connexes pouvant présenter un intérêt:

Si vous voulez vraiment entrer dans les détails sérieux des nombres à virgule flottante, jetez un oeil à Ce que tout informaticien devrait savoir sur l'arithmétique en virgule flottante .

135
coobird

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

33.3333333333333285963817615993320941925048828125

En divisant par 100, on obtient:

0.333333333333333285963817615993320941925048828125

qui n'est pas non plus représentable sous forme de nombre double précision, il est donc arrondi à la valeur représentable la plus proche, qui est exactement:

0.3333333333333332593184650249895639717578887939453125

Lorsque vous imprimez cette valeur, elle est arrondie encore une fois à 17 chiffres décimaux, donnant:

0.33333333333333326
101
Stephen Canon

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

Écrire des méthodes pour additionner, soustraire, multiplier et diviser ainsi qu’une méthode toDouble. De cette façon, vous pouvez éviter les flotteurs lors des calculs.

EDIT: mise en oeuvre 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);
    }
}

}
23
Viral Shah

Observez que vous auriez le même problème si vous utilisiez l'arithmétique décimale à précision limitée et que vous souhaitiez traiter avec 1/3: 0.333333333 * 3 égal à 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 cinquièmes. Donc, leur représentation flottante n’est pas exacte, tout comme 0.3333 ne correspond pas exactement à 1/3.

Si tous les nombres que vous utilisez sont des nombres décimaux non récurrents et que vous voulez des résultats exacts, utilisez BigDecimal. Ou, comme d’autres l’ont déjà dit, si vos valeurs ressemblent à de l’argent en ce sens qu’elles sont toutes des multiples de 0,01, ou de 0,001, ou quelque chose comme cela, multipliez le tout par une puissance fixe de 10 et utilisez int ou long (addition et soustraction sont trivial: attention à la multiplication).

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.Formatter ou String.format. Dans la chaîne de formatage, spécifiez une précision inférieure à la précision complète d'un double. Par exemple, 11,399999999999 est égal à 11,4 pour 10 chiffres significatifs, de sorte que le résultat sera presque aussi précis et 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 du calcul 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 "instable" opposé à "stable" en ce qui concerne les erreurs d’arrondi). Si vous ne faites que l'ajout de quelques valeurs, alors j'imagine que le fait de supprimer une précision d'une décimale suffit à régler le problème. Expérience.

15
Steve Jessop

Vous voudrez peut-être 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 avec quelle précision vous voulez que le résultat soit. setScale () est votre ami .. :)

Ok, car il me reste trop de temps, voici un exemple de code correspondant à 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 mon nouveau langage préféré, Groovy, voici un exemple plus net 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
9
Vinny

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

Jetez un œil à http://docs.python.org/tutorial/floatingpoint.html pour une explication plus détaillée. (C’est sur le Python, mais Java et C++ ont le même "problème".)

La solution dépend de la nature exacte de votre problème:

  • Si vous n'aimez pas voir tous ces chiffres parasites, corrigez le formatage de votre chaîne. N'affichez pas plus de 15 chiffres significatifs (ou 7 pour le caractère flottant).
  • Si c'est l'inexactitude de vos chiffres qui casse des choses comme les déclarations "if", vous devez écrire if (abs (x - 7.3) <TOLERANCE) au lieu de if (x == 7.3).
  • Si vous travaillez avec de l'argent, vous voudrez probablement un point fixe décimal. Stockez un nombre entier de centimes ou la plus petite unité de votre devise.
  • (TRÈS INCORRECTE) Si vous avez besoin de plus de 53 bits de précision (15 à 16 chiffres significatifs) de précision, utilisez un type à virgule flottante de haute précision, comme BigDecimal.
5
dan04

Vous vous heurtez à la limite de précision du type double.

Java.Math possède des fonctionnalités arithmétiques de précision arbitraire.

5
John

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

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

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

Te donnera:

11.40
5
Draemon

Je suis sûr que vous auriez pu en faire un exemple à trois lignes. :)

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

5
Dustin
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
}
4
sravan

Utilisez Java.math.BigDecimal

Les doubles étant des fractions binaires internes, ils ne peuvent parfois pas représenter les fractions décimales à la décimale exacte.

3
Kevin Crowell

Les ordinateurs stockent les numéros en binaire et ne peuvent pas représenter des nombres tels que 33.333333333 ou 100.0 exactement. C'est l'une des choses les plus délicates à propos de l'utilisation des doubles. Vous devrez juste arrondir la réponse avant de la montrer à un utilisateur. Heureusement, dans la plupart des applications, vous n’avez de toute façon pas besoin de autant de décimales.

2
Jay Askren

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

Il n'y a aucun moyen de représenter 1/3 en tant que float. 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 for Java prétend fonctionner avec des nombres à virgule flottante en précision arbitraire, mais je ne l'ai jamais utilisé. Vaut probablement le coup d'oeil. http://www.apfloat.org/apfloat_Java /

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

2
Spike

Multipliez tout par 100 et stockez-le dans des centaines de centimes.

2
Paul Tomblin

Les doubles sont approximations des nombres décimaux de votre source Java. Vous constatez la conséquence de l'inadéquation entre le double (qui est une valeur codée 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 esthétique.

1
S.Lott

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

1
Adam Jaskiewicz

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;
}
0
user5734382

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

Le nouvel appel ressemblerait à ceci:

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

Utilisez setScale () pour définir le nombre de précision de la décimale à utiliser.

0
Dark Castle

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
0
Mr.Cat