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 :

Souci en arithmétique


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre habitué
    Homme Profil pro
    Inscrit en
    Mars 2013
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2013
    Messages : 10
    Par défaut Souci en arithmétique
    Bonjour,

    Voici le code:

    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
     
    #include<stdio.h>
    #include<math.h>
     
    int main()
    {
       float n,d;
       int e,i;
     
       printf("Nombre=");
       scanf("%f",&n);
       d=n/1000.0;
       printf("Nbr divisé=%f\n",d);
     
       for(i=0;i<3;i++)
       {
          d=d*10.0;
          printf("d=d*10=%f\n",d);
          e=(int)d;
          printf("e=(int)d=%d\n",e);
          d=d-e;
          printf("d=d-e=%f\n",d);
       }
     
       return 0;
    }
    Si je renseigne le nombre 197 par exemple, voici le résultat:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    Nombre=197
    Nbr divisé=0.197000
    d=d*10=1.970000
    e=(int)d=1
    d=d-e=0.970000
    d=d*10=9.700001
    e=(int)d=9
    d=d-e=0.700001
    d=d*10=7.000008
    e=(int)d=7
    d=d-e=0.000008
    Tout se passe bien jusqu'à la 2e multiplication par 10, j'obtiens 9.700001. A la 3e, ça s'empire :-/
    Est-ce que le problème vient du fait que j'assigne la partie entière à "e"?

    Bonne journée
    Ben

  2. #2
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    C'est une question d'erreurs d'arrondi, toujours présentes lors de calculs à virgule flottante.

    La précision sera plus grande (et cette erreur d'arrondi sera donc moindre) si tu remplaces float par double, mais l'erreur ne disparaîtra jamais complètement.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  3. #3
    Membre habitué
    Homme Profil pro
    Inscrit en
    Mars 2013
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2013
    Messages : 10
    Par défaut
    J'ai modifié un peu le code:

    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
    #include<stdio.h>
    #include<math.h>
     
    int main()
    {
       float n,d;
       int e,i,b;
     
       printf("Nombre=");
       scanf("%f",&n);
     
       b=1+(int)(log(n)/log(10));
       printf("Longueur du nombre:%d\n",b);
       d=n/pow(10,b);
       printf("Nbr divisé=%f\n",d);
     
       for(i=0;i<b;i++)
       {
          d=d*10.0;
          printf("d=d*10=%f\n",d);
          e=(int)d;
          printf("e=(int)d=%d\n",e);
          d=d-e;
          printf("d=d-e=%f\n",d);
       }
     
       return 0;
    }
    Si on lance le programme avec 28, le résultat est assez étonnant, c'est une belle erreur d'arrondi :-(

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Nombre=28
    Longueur du nombre:2
    Nbr divisé=0.280000
    d=d*10=2.800000
    e=(int)d=2
    d=d-e=0.800000
    d=d*10=8.000000
    e=(int)d=7
    d=d-e=1.000000
    Mais effectivement, ça fonctionne mieux avec double

    Merci!

  4. #4
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Au fait, tu seras peut-être surpris de l'apprendre, mais le langage C autorise les noms de variable à plusieurs caractères (les compilateurs doivent supporter jusqu'à 32 caractères minimum). Tu peux donc donner à e un nom plus explicite, comme partieEntiere.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  5. #5
    Membre prolifique
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 835
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 835
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par BenDeVil Voir le message
    Si on lance le programme avec 28, le résultat est assez étonnant, c'est une belle erreur d'arrondi :-(

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Nombre=28
    Longueur du nombre:2
    Nbr divisé=0.280000
    d=d*10=2.800000
    e=(int)d=2
    d=d-e=0.800000
    d=d*10=8.000000
    e=(int)d=7
    d=d-e=1.000000
    Ben oui. Le nombre doit probablement être 7.9999999 ce qui fait que le printf affiche 8.0000 mais 7 quand tu le castes en int.

    Si tu es sous un Linux, tape "Python" dans une fenêtre console (shell). Tu verras une invite de ce type ">>>" t'invitant à taper des instructions Python
    Là tu tapes
    • a=28/10.0
    • a
    • a=a-int(a)
    • a
    • a*10

    Et tu verras 7.999999999998

    Ce problème est inhérent à tous les langages qui utilisent le principe de la mantisse et de l'exposant pour coder les nombres à virgule. Il n'y a que le vieux et archaïque COBOL qui s'en sort bien avec son pic 9(x)v9(y) permettant de définir des nombres avec x digits dont y après la virgule. C'est une des raisons majeures qui font qu'il est encore utilisé dans les banques.

    Ceci dit, il existe des librairies spécialisées dans le calcul en précision exacte des nombres décimaux. Sous Python justement t'as la librairie "decimal" pour ça. En C il en existe aussi (dont j'ai pas le nom en tête). Globalement le principe est le même: chaque digit du nombre occupe une case entière => 2.8 devient "2" et "8". Puis chaque case est calculée lors des opérations exactement de la même façon qu'on apprend à le faire au primaire.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  6. #6
    Membre habitué
    Homme Profil pro
    Inscrit en
    Mars 2013
    Messages
    10
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2013
    Messages : 10
    Par défaut
    Je veux bien comprendre qu'il peut y avoir des erreurs d'arrondis, mais dans ce cas si, mathématiquement parlant, il ne peut pas y en avoir. Ce ne sont que des divisions et des multiplications de puissance de 10!

    Est-ce que ça voudrait dire qu'à la base, le C est incapable de faire quelques calculs? Parce qu'il faut bien l'avouer, mon algorithme est assez basique. Ou il y a peut être un truc qui m'échappe.

  7. #7
    Membre Expert
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Par défaut
    Le C n'est pas en cause, ces artefacts sont inhérents à la représentation IEEE 754. Contrairement aux entiers, les nombres à virgule flottante ne sont pas conçus pour représenter exactement n'importe quel réel d'un intervalle donné. 0.1 par exemple n'est pas représentable, seule une approximation est disponible. La lecture de ce document t'apportera toutes les informations nécessaires.

    Si tu as besoin d'effectuer des calculs exacts (pour une application financière par exemple), il faut définir et utiliser un type spécifique. Sve@r t'as donné quelques pistes en ce sens.

  8. #8
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Le problème, c'est que le calcul à virgule flottante IEEE n'est pas fait en base 10, mais en base 2.
    Il faudrait donc des puissances de 2 plutôt que 10, pour être sûr d'éviter des erreurs d'arrondi.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  9. #9
    Membre prolifique
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 835
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 835
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par BenDeVil Voir le message
    Je veux bien comprendre qu'il peut y avoir des erreurs d'arrondis, mais dans ce cas si, mathématiquement parlant, il ne peut pas y en avoir. Ce ne sont que des divisions et des multiplications de puissance de 10!
    Parce que toi tu travailles en base 10 ça tombe juste. L'ordi, lui, travaille en base 2 de la façon suivante (pour la partie décimale):
    • multiplier la partie décimale par 2
    • récupérer la partie entière du résultat
    • mettre cette partie entière à 0 et si le résultat ne fait pas 0, alors recommencer

    Le nombre décimal en base 2 est alors l'ensemble concaténé de toutes les parties entières récupérées

    Exemple: 0.6875
    0.6875 * 2 = 1.375 => garder 1 et réutiliser 0.375
    0.375 * 2 = 0.75 => garder 0 et laisser 0.75
    0.75 * 2 = 1.5 => garder 1 et réutiliser 0.5
    0.5 * 2 = 1.0 => garder 1 et réutiliser 0.0. Ce qui reste faisant alors 0, on s'arrête. Et on a alors 0.6875 (base 10) = 0.1011 (base 2)

    Mais parfois ça ne tombe pas juste. Autre exemple: 0.9
    0.9 * 2 = 1.8 => garder 1 et réutiliser 0.8
    0.8 * 2 = 1.6 => garder 1 et réutiliser 0.6
    0.6 * 2 = 1.2 => garder 1 et réutiliser 0.2
    0.2 * 2 = 0.4 => garder 0 et laisser 0.4
    0.4 * 2 = 0.8 => garder 0 et laisser 0.8
    0.8 * 2 = ... et vlan, boucle infinie !!!
    Donc 0.9 (nombre décimal parfaitement manipulable par un gamin de 10 ans) se traduit en binaire par 0.11100 1100 1100 1100 1100 ... (à l'infini). Mais comme un ordi ne peut pas coder à l'infini et doit fatalement s'arrêter (pour l'exemple on va dire à 0.11100 1100) alors si on retransforme ce nombre en base 10, ça donne 1/2 + 1/4 + 1/8 + 1/64 + 1/128 soit 0.8984375 ce qui est très proche de 0.9 mais qui n'est pas exactement ce nombre et ne le sera jamais (même si la précision réelle va au-delà de mes 9 décimales d'exemple le principe reste le même).

    PS: tu remarqueras que pour mon premier exemple, je suis parti de 1/2+1/4+1/16 pour tomber juste (et avoir une suite de "1" et de "0" non symétrique pour que tu voies bien dans quel ordre on affiche le résultat); ce qui est exactement ce que préconise Medinoc avec ses puissances de 2...

    Citation Envoyé par BenDeVil Voir le message
    Est-ce que ça voudrait dire qu'à la base, le C est incapable de faire quelques calculs?
    Exactement. Et ce n'est pas que le C (voir mon exemple Python). Pour être précis ces langages sont incapables de travailler les nombres décimaux avec l'exactitude qu'on devrait avoir. Alors dans la vie de tous les jours d'un développeur (faire sa petite bdd pour gérer ses DVD de Babar ou afficher le pourcentage de visiteurs de sa chaine youtube) ça n'a pas d'influence mais quand tu commences à vouloir calculer un remboursement immobilier ça devient bien plus chaud.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

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

Discussions similaires

  1. ASM + DELPHI ... soucis ... mais top intéressant !
    Par - Robby - dans le forum Langage
    Réponses: 9
    Dernier message: 21/11/2003, 15h58
  2. [langage] ptit souci de syntaxe
    Par marouanitos dans le forum Langage
    Réponses: 2
    Dernier message: 26/09/2003, 10h28
  3. [File et Directory ListBox] Soucis de filtre
    Par Mercilius dans le forum Composants VCL
    Réponses: 8
    Dernier message: 04/04/2003, 16h17
  4. Réponses: 4
    Dernier message: 16/02/2003, 12h16
  5. Réponses: 2
    Dernier message: 03/10/2002, 17h24

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