IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Java Discussion :

Précision de calcul


Sujet :

Java

  1. #1
    Membre confirmé Avatar de Mucho
    Inscrit en
    Décembre 2005
    Messages
    221
    Détails du profil
    Informations forums :
    Inscription : Décembre 2005
    Messages : 221
    Par défaut Précision de calcul
    Une petite question sur la précision de calcul,

    j'ai trouvé pas mal de messages sur le forum qui parlent de ce problème mais pas de réponse que je comprenne vraiment.
    Voilà un exemple tout simple que je ne comprend pas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    double a = 230.13075;
        double b = 178.17765;
     
        double result = a - b;
    Dans ce cas result vaut 51.953100000000006
    Je ne sais pas trop comment corriger le problème ... pour un calcul aussi simple !

    Et avec des float c'est pire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    float a = 230.13075f;
        float b = 178.17765f;
     
        float result = a - b;
    Result vaut 51.95311 au lieu de 51.9531

    Est-ce que quelqu'un à une méthode claire pour avoir un calcul exact ?

  2. #2
    Modérateur
    Avatar de dinobogan
    Homme Profil pro
    ingénieur
    Inscrit en
    Juin 2007
    Messages
    4 073
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations professionnelles :
    Activité : ingénieur
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 4 073
    Par défaut
    C'est un problème de représentation interne propre au système, pas au langage.
    Tu as essayé avec java.math.BigDecimal ? Il me semble que ça résout le problème (pas le temps de vérifier...)
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java
    Que la force de la puissance soit avec le courage de ta sagesse.

  3. #3
    Membre confirmé Avatar de Mucho
    Inscrit en
    Décembre 2005
    Messages
    221
    Détails du profil
    Informations forums :
    Inscription : Décembre 2005
    Messages : 221
    Par défaut
    Merci mais ...
    J'avais aussi essayé BigDecimal et ça ne semble pas résoudre le problème :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    BigDecimal a = new BigDecimal(230.13075);
    BigDecimal b = new BigDecimal(178.17765);
     
    double result = a.subtract(b, MathContext.UNLIMITED).doubleValue();
    result vaut alors 51.953100000000006

  4. #4
    Membre éprouvé
    Avatar de David Gimelle
    Profil pro
    Développeur Java
    Inscrit en
    Janvier 2007
    Messages
    79
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Janvier 2007
    Messages : 79
    Par défaut Precision
    Si tu veux garder la precision des BigDecimal, reste en bigdecimal et ne les transforme pas en double. Vue que les doubles ne te garantissent pas la precision que tu souhaites:

    Exemple :
    MathContext mContext=new MathContext(10);

    BigDecimal bdA= new BigDecimal(a,mContext);
    BigDecimal bdB=new BigDecimal(b,mContext);
    BigDecimal res=bdA.subtract(bdB,mContext);

    System.out.println("res :"+res);

    David Gimelle
    Developpeur J2EE
    http://getj2ee.over-blog.com

  5. #5
    Expert confirmé

    Inscrit en
    Août 2006
    Messages
    3 967
    Détails du profil
    Informations forums :
    Inscription : Août 2006
    Messages : 3 967
    Par défaut
    Gio,

    En repassant en double, tu recrées le problème : le nombre de valeurs exactement représentables avec des flottants à taille fixe est très petit par rapport à la dynamique possible (c'est à dire par rapport aux valeurs acceptées par les variables de ce type).
    Il est donc rarissime de tomber sur une de ces valeurs.

  6. #6
    Membre confirmé Avatar de Mucho
    Inscrit en
    Décembre 2005
    Messages
    221
    Détails du profil
    Informations forums :
    Inscription : Décembre 2005
    Messages : 221
    Par défaut
    Un test plus complet :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    BigDecimal a = new BigDecimal(230.13075);
    BigDecimal b = new BigDecimal(178.17765);
    double test2 = 51.9531;
    BigDecimal test3 = new BigDecimal(51.9531);
     
    double result1 = a.subtract(b, MathContext.UNLIMITED).doubleValue();
    double result2 = test2;
    double result3 = test3.doubleValue();
    result1 = 51.953100000000006
    result2 = 51.9531 // donc un double peut sans problème prendre cette valeur
    result3 = 51.9531 // donc il semble que la conversion en double n'affecte pas la valeur

    En repassant en double, tu recrées le problème
    Je ne comprends pas pourquoi (cf test3 et result3 ci-dessus)

    Si tu veux garder la precision des BigDecimal, reste en bigdecimal et ne les transforme pas en double. Vue que les doubles ne te garantissent pas la precision que tu souhaites
    Mais pourtant les doubles (et même les floats il me semble) peuvent sans problème contenir le résultat (cf test2 et test3 ci-dessus).
    En fait je voudrais juste faire une fonction qui revoie un double avec la bonne valeur (i.e. la variable result2 ou result3 ci-dessus)

  7. #7
    Membre éprouvé
    Avatar de David Gimelle
    Profil pro
    Développeur Java
    Inscrit en
    Janvier 2007
    Messages
    79
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Janvier 2007
    Messages : 79
    Par défaut
    Ce bout de code devrais resoudre ton probleme :

    // Fixe le scale a 10 digit
    MathContext mContext=new MathContext(10);

    // calcule avec 10 digit
    BigDecimal bdA= new BigDecimal(a,mContext);
    BigDecimal bdB=new BigDecimal(b,mContext);
    BigDecimal res=bdA.subtract(bdB,mContext);
    System.out.println("res :"+res);

    // une fois le calcule termine tu peux le transformer en double
    double d = res.doubleValue();
    System.out.println("d:"+d);

  8. #8
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Salut,


    Comme cela a été dit, les float/double ont une imprécision plus ou moins grande qui peut fausser les résultats. Or comme tu utilises des doubles pour initialiser tes BigDecimal tu conserves cette "imprécision" !

    La preuve :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    BigDecimal a = new BigDecimal(230.13075);
    BigDecimal b = new BigDecimal(178.17765);
     
    System.out.println(a);
    System.out.println(b);
    Le code ci dessus t'affiche ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    230.130750000000006139089236967265605926513671875
    178.177649999999999863575794734060764312744140625
    Il s'agit de l'approximation représentant les doubles 230.13075 et 178.17765, et lorsque tu fait la soustraction des deux tu obtiens 51.953100000000006275513442233204841613769531250...



    Donc tu as deux solutions :
    • Soit tu as besoin de calcul super-précis, et tu utilises la classe BigDecimal mais en aucun cas des floats ou des doubles qu'il te faut bannir !
    • Soit tu peux de contenter d'une précision moindre, ce qui est le cas dans la plupart des applications, et tu formates les float/double afin de n'afficher qu'une nombre limité de décimales afin de te préserver des erreurs d'approximations.



    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    	double a = 230.13075;
    	double b = 178.17765;
    	double c = a - b;
     
    	// Affichage par défaut (avec les approximations) :
    	System.out.println(a);
    	System.out.println(b);
    	System.out.println(c);
     
    	System.out.println();
     
    	// On utilise un formatter avec un maximum de 8 décimales :
    	NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
    	nf.setMaximumFractionDigits(8);
     
    	// Affichage OK (sans les approximations) :
    	System.out.println( nf.format(a) );
    	System.out.println( nf.format(b) );
    	System.out.println( nf.format(c) );
    Ce qui donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    230.13075
    178.17765
    51.953100000000006
     
    230.13075
    178.17765
    51.9531
    a++

  9. #9
    Membre confirmé Avatar de Mucho
    Inscrit en
    Décembre 2005
    Messages
    221
    Détails du profil
    Informations forums :
    Inscription : Décembre 2005
    Messages : 221
    Par défaut
    Citation Envoyé par David Gimelle
    Ce bout de code devrais resoudre ton probleme :

    // Fixe le scale a 10 digit
    MathContext mContext=new MathContext(10);
    ...
    Effectivement celà résoud bien le problème de cet exemple mais la précision à 10 digits me semble arbitraire et pourrait ne pas fonctionner pour d'autres exemples.
    Et si j'ai bien compris celà revient a faire un calcul avec des doubles et arrondir le résultat à 10 digits. C'est ça ?

    Citation Envoyé par adiGuba
    BigDecimal a = new BigDecimal(230.13075);

    System.out.println(a); // 230.130750000000006139089236967265605926513671875
    Justement je ne comprend pas du tout ce fonctionnement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    double da = 230.13075;
    BigDecimal a = new BigDecimal(da, MathContext.UNLIMITED);
     
    System.out.println(da);   // 230.13075
    System.out.println(a);    // 230.130750000000006139089236967265605926513671875
    Du coup je ne comprend pas à quoi sert ce MathContext.UNLIMITED puisque il semble que la valeur du double (da) soit la bonne et pourtant la conversion en BigDecimal perd de la précision malgré le MathContext.UNLIMITED (alors qu'il me semble que la javadoc définie cette opération comme exacte : When a MathContext object is supplied with a precision setting of 0 (for example, MathContext.UNLIMITED), arithmetic operations are exact)

  10. #10
    Membre éprouvé
    Avatar de David Gimelle
    Profil pro
    Développeur Java
    Inscrit en
    Janvier 2007
    Messages
    79
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Janvier 2007
    Messages : 79
    Par défaut
    Oui le 10 digit est arbitraire.

    Si tu initialise un BigDecimal avec un double tu vas enregistrer avec un "precision" un double qui a une valeur pas tres precise.

    (Mais assez precise quand meme, car dans la vrai vie un resultat avec plus de 14 chiffres apres la virgule en général ca n'a pas bcp de sens.)

    Ce bout de code devrais te donner une idee :

    BigDecimal b1 = new BigDecimal(230.13075); // Initalise avec un double
    BigDecimal b2 = new BigDecimal("230.13075");// avec un String
    BigDecimal b3 = new BigDecimal(230.13075,new MathContext(10)); // avec un double sur 10 digit

    System.out.println("b1:"+b1);
    System.out.println("b2:"+b2);
    System.out.println("b3:"+b3);

    Resultat :
    [testng] b1:230.130750000000006139089236967265605926513671875
    [testng] b2:230.13075
    [testng] b3:230.1307500

  11. #11
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Salut,


    Comme cela a été dit, les floaT/double ne sont pas précis.
    En fait lorsque tu fais :
    da vaut en réalité 230.130750000000006139089236967265605926513671875

    Et pour pallier à cette imprécision les doubles sont "tronqué" à l'affichage ce qui fait que tu as bien 230.13075...

    Le problème vient du fait que les imprécisions peuvent se cumuler pendant les calculs et alourdir le problème au point d'être visible.


    Donc encore une fois tu as deux solutions :
    • Utiliser des BigDecimal initialisé avec des String ou des int/long et bannir les float/double, ceci afin d'éviter toutes imprécisions !
    • Utiliser des float/double en tronquant le résultat afin de limiter les problèmes.



    C'est comme ca !


    a++

Discussions similaires

  1. Précision de calcul
    Par elglantosimpatico dans le forum MATLAB
    Réponses: 7
    Dernier message: 19/04/2012, 11h21
  2. scipy.poly1d : précision du calcul des racines
    Par ryced dans le forum Calcul scientifique
    Réponses: 5
    Dernier message: 25/01/2010, 10h04
  3. précision de calcul de fsolve
    Par Nabuchodonosor15 dans le forum MATLAB
    Réponses: 3
    Dernier message: 22/07/2009, 13h34
  4. Choisir la précision pour calculs en nombres flottants
    Par ciol2.6.12 dans le forum Algorithmes et structures de données
    Réponses: 2
    Dernier message: 02/06/2008, 14h14
  5. Précision de calculs trigo.
    Par Clad3 dans le forum C++
    Réponses: 11
    Dernier message: 23/10/2007, 14h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo