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

C++ Discussion :

disfonctionnement floor avec double


Sujet :

C++

  1. #1
    Membre à l'essai
    Inscrit en
    Juillet 2007
    Messages
    36
    Détails du profil
    Informations forums :
    Inscription : Juillet 2007
    Messages : 36
    Points : 15
    Points
    15
    Par défaut disfonctionnement floor avec double
    Bonjour,
    je voudrais créer un constructeur de fraction à partir d'un double.
    Voici le code en question :

    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
    20
    ZFraction::ZFraction(double decimal)
    {
        int compteur(0);
     
     
        while(floor(decimal)!=decimal)
        {
     
            decimal*=10.0;
            compteur++;
        }
     
     
     
        m_numerateur=decimal;
        m_denominateur=pow(10.0,compteur);
     
     
        simplifier();
    }
    Quand je teste avec un double valant 0.401, la valeur de floor(decimal) vaut 400 au lieu de 401 quand j'arrive au troisième tour de boucle, ce qui est surprenant.
    Merci pour vos réponses.

  2. #2
    Membre éprouvé
    Homme Profil pro
    Ingénieur R&D
    Inscrit en
    Mai 2016
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Ingénieur R&D
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2016
    Messages : 313
    Points : 1 237
    Points
    1 237
    Par défaut
    Bonjour
    0.401 n'a pas de représentation exacte en type 'double' (=IEEE-754 64 bits).
    Affiche les résultats successifs avec 17 chiffres de précision, et tu verras qu'à la troisième itération, decimal=400.99999...., produisant 400 par floor().
    C'est un problème de représentation des nombres et d'algorithme, la fonction floor() fonctionne correctement.

  3. #3
    Membre à l'essai
    Inscrit en
    Juillet 2007
    Messages
    36
    Détails du profil
    Informations forums :
    Inscription : Juillet 2007
    Messages : 36
    Points : 15
    Points
    15
    Par défaut
    Mais comment palier à ce problème?
    Peut-être en convertissant le double en string, et en calculant le nombre de chiffres après la virgule?

  4. #4
    Expert confirmé

    Inscrit en
    Août 2006
    Messages
    3 942
    Détails du profil
    Informations forums :
    Inscription : Août 2006
    Messages : 3 942
    Points : 5 654
    Points
    5 654
    Par défaut
    Bonjour,
    Citation Envoyé par sgu35 Voir le message
    Mais comment palier à ce problème?
    Peut-être en convertissant le double en string, et en calculant le nombre de chiffres après la virgule?
    Tu ne peux pas éviter ce problème, dû à la représentation des flottants.

    Tu peux le réduire quand même en utilisant une bibliothèque muli-précision,
    la plus connue est GMP, je te laisse chercher, c'est facile à trouver.

    MAIS, ce n'est pas la panacée, ça ne fait que reculer le problème,
    tout en augmentant notablement le temps de calcul.
    Si les cons volaient, il ferait nuit à midi.

  5. #5
    Membre éprouvé
    Homme Profil pro
    Ingénieur R&D
    Inscrit en
    Mai 2016
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Ingénieur R&D
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2016
    Messages : 313
    Points : 1 237
    Points
    1 237
    Par défaut
    sgu35 :
    Tu peux en effet passer par des conversions en chaines de caractères, en contrôlant bien la précision, et laisser à la conversion standard le soin de gérer les arrondis.
    Mais ce n'est pas très élégant, surtout si le nombre provient à l'origine d'une saisie par l'utilisateur (conversion chaine -> double, et ensuite reconversion en chaine...).
    On peut aussi gérer explicitement les arrondis 'au plus proche', après avoir fixé une précison a priori, c'est souvent la méthode retenue : floor(400.999...) = 400, mais floor(400.999.... +0.5) = 401

    Mais le plus propre est de reconnaitre que 0.401 (par exemple) n'existe pas en double/IEEE-754, tout comme d'ailleurs la plupart des nombres non entiers que pourrait saisir un utilisateur, et manipuler directement la bonne représentation, c'est à dire en général une suite de chiffres décimaux dans une chaine de caractères.
    Ton constructeur devrait accepter et traiter une chaine.
    En entrée : chaine = aaaaa.ddddd
    Fractionner la chaine et convertir en deux entier n1=(aaaaa) et n2=(ddddd)
    Ensuite, traiter ces deux entiers, sans utiliser le type 'double'.
    Il faut bien sûr faire attention aux dépassements de capacité, traiter correctement les nombres négatifs si applicable, etc.
    On peut également définir une classe pour traiter directement les nombres en virgule flottante en représentation décimale, ça existe déjà certainement si tu ne veux pas le faire toi-même.

    Voir aussi les caractéristiques des différents types numériques, pour faire attention à la portabilité :
    http://www.cplusplus.com/reference/l...umeric_limits/

    Complément : vérifie quand même si ton compilateur favori implémente la nouvelle version de la norme IEEE-754 (2008), qui inclut les nombres décimaux. Ca parait être le cas en gcc, comme extension non standardisée (mais peut-être pas en C++).

  6. #6
    Membre à l'essai
    Inscrit en
    Juillet 2007
    Messages
    36
    Détails du profil
    Informations forums :
    Inscription : Juillet 2007
    Messages : 36
    Points : 15
    Points
    15
    Par défaut
    Bonjour,
    j'ai utilisé des strings pour récupérer la partie décimale et la partie entière. Avec cela, le numérateur est correct, mais le dénominateur pose problème. En effet si on teste avec 0.32 ou 0.56, 10 à la puissance 2 retourne 99...

    Après quelques essais, j'ai trouvé que si je mettais 10.0000001 à la puissance n, pow retourne 100 pour ces double.
    J'ai aussi essayé de tester avec des double avec beaucoup de chiffres après la virgule mais il me met au maximum 6 chiffres après la virgule, peut-être est-ce qu'on atteint la limite de précision du langage c++.

  7. #7
    Membre éprouvé
    Homme Profil pro
    Ingénieur R&D
    Inscrit en
    Mai 2016
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Ingénieur R&D
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2016
    Messages : 313
    Points : 1 237
    Points
    1 237
    Par défaut
    Bonjour

    Tu ne t'en sortiras pas comme ça sans comprendre les propriétés des nombres définis en virgule flottante. Et le nombre de chiffres affichés se règle avec std::setprecision (si on utilise les flux), ou des formats % (avec les formats C).

    Tout d'abord, il faudrait savoir d'où provient le nombre passé en paramètre au constructeur de ta classe, la démarche n'est pas la même suivant qu'il s'agit d'une saisie utilisateur ou d'un résultat de calcul.
    Mais j'ai l'impression que tu testes avec des saisies utilisateur. Donc partons de ce principe.
    Dans ce cas, pour ce que tu veux faire, il ne faut pas utiliser le type 'double'.

    1ère étape : le nombre est saisi en tant que chaine de caractère
    std::string x;
    std::cout << "x = ";
    std::cin >> x;

    Cela assure qu'il n'y a aucune erreur d'arrondi caché, x contiendra "0.32", et non pas 0.3199999999.... si x était un 'double'.

    2ème étape : cette chaine est transmise au constructeur de ta classe
    La partie décimale contient 2 caractères => dénominateur = 100 (et il ne faut évidemment pas utiliser la fonction "pow" standard pour déduire cela, cette fonction travaille sur des flottants).
    Uniquement par du traitement de chaine de caractère, en repérant le ".", cette chaine est fractionnée en deux entiers : n=0 et dec=32
    La fraction est (n*denominateur + dec, denominateur)

    Avec cette procédure, qui utilise uniquement des entiers et du traitement de chaines, il n'y a aucun problème d'arrondi, il faut juste tester correctement la validité de la chaine et faire attention aux limites des entiers fixes standards (ou utiliser des entiers de longueur arbitraire comme on en trouve dans une bibliothèque comme gmp).

  8. #8
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Si tu veux te faire du mal et du bien à la fois, tu peux lire (au moins le début de) cet article : https://www.itu.dk/~sestoft/bachelor...54_article.pdf

    Wikipedia est déjà aussi une bonne lecture pour comprendre ce qu'implique la représentation floating point : https://en.wikipedia.org/wiki/Floating-point_arithmetic

  9. #9
    Membre à l'essai
    Inscrit en
    Juillet 2007
    Messages
    36
    Détails du profil
    Informations forums :
    Inscription : Juillet 2007
    Messages : 36
    Points : 15
    Points
    15
    Par défaut
    J'ai utilisé les string pour résoudre ce problème, voici le code final :
    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
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    ZFraction::ZFraction(double decimal)
    {
     
        string chaineDecimal("");
        ostringstream oss;
        oss.precision(10);//précision de 10 chiffres significatifs
        oss<<decimal;
        chaineDecimal=oss.str();
     
        int positionPoint=chaineDecimal.find('.',0);
        if(positionPoint==-1)
        {
            int longueurChaineDecimal=chaineDecimal.length();
            string exposant=chaineDecimal.substr(chaineDecimal.length()-1);
     
            char premierChiffre;
            int indicePremierChiffre(0);
     
            if(chaineDecimal[0]=='-')
            {
                indicePremierChiffre=1;
            }
            else
            {
                indicePremierChiffre=0;
            }
     
            premierChiffre=chaineDecimal[indicePremierChiffre];
            chaineDecimal[indicePremierChiffre]='0';
            chaineDecimal[indicePremierChiffre+1]='.';
     
            for(int i(2+indicePremierChiffre);i<longueurChaineDecimal;i++)
            {
                chaineDecimal[i]='0';
            }
     
            for(int i=longueurChaineDecimal;i<stol(exposant)+1+indicePremierChiffre;i++)
            {
                chaineDecimal+='0';
            }
            chaineDecimal+=premierChiffre;
     
            positionPoint=chaineDecimal.find('.',0);
        }
     
        chaineDecimal.erase(positionPoint,1);
     
        m_numerateur=stol(chaineDecimal);
     
        int nbChiffresApresVirgule=chaineDecimal.length()-positionPoint;
     
        string chaineDenominateur("1");
        for(int i=0;i<nbChiffresApresVirgule;i++)
        {
            chaineDenominateur+='0';
        }
     
        m_denominateur=stol(chaineDenominateur);
     
     
    }
    Le code est un peu long mais il gère les nombres avec un chiffre significatif à plus de 4 zéros après la virgule comme 0.00001. Il gère aussi les décimaux négatifs comme -0.00001.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Problème étrange de précision avec double
    Par titoine1978 dans le forum DirectX
    Réponses: 4
    Dernier message: 22/02/2006, 09h26
  2. requete avec double jointure externe
    Par cdu dans le forum Langage SQL
    Réponses: 8
    Dernier message: 04/01/2006, 14h54
  3. requete avec double tri
    Par noarno dans le forum Access
    Réponses: 1
    Dernier message: 15/11/2005, 16h55
  4. [debutant][swt] CellEditor / CellModifier avec double clic
    Par antares24 dans le forum SWT/JFace
    Réponses: 2
    Dernier message: 10/05/2005, 02h25
  5. Réponses: 5
    Dernier message: 19/07/2004, 11h16

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