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 :

Problème de résultat d'une opération mathématique


Sujet :

C

  1. #1
    Membre à l'essai
    Inscrit en
    Mars 2009
    Messages
    13
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 13
    Points : 10
    Points
    10
    Par défaut Problème de résultat d'une opération mathématique
    Bonjour,

    Je me lance dans la programmation en C sur microcontrôleur et je rencontre un problème d'ordre bizarre, voire inexpliqué.

    Voici le code qui pose un soucis :
    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
     
    void Display_Cursor(signed int lat, signed int lon) {
      unsigned char latitude_y, longitude_x;
     
      //Latitude and Longitude scaling for 128x64 display:
      //Latitude: Input range is -90 to 90 degrees
      //Longitude: Input range is -180 to 180 degrees
      latitude_y = ((61*(90-lat))/180)+1;
      longitude_x = ((125*(lon+180))/360)+1;
     
      //Cursor drawing:
      Glcd_H_Line(0, 127, latitude_y, 2);
      Glcd_V_Line(0, 63, longitude_x, 2);
     
      //Display latitude and longitude "translation" to fit on the screen
      IntToStr(latitude_y,dispLatStr);
      IntToStr(longitude_x,dispLonStr);
      Glcd_Write_Text(dispLatStr,0,6,1);
      Glcd_Write_Text(dispLonStr,0,7,1);
     
      //Blinking
      Delay_ms(500);
      Glcd_Fill(0x00);
    }
    Le problème c'est que la ligne verticale (longitude) ne s'affiche pas car elle est hors de l'écran (position longitude=196 sur un écran de 128 pixels). Cela peut être solutionné en affectant la valeur au paramètre "lon".
    Pour résumer :
    - Si le paramètre "lon" de la fonction est utilisé tel quel (sa valeur est de 166 à l'appel de la fonction), le résultat du calcul est faux.
    - Si je met "lon = 166;" dans la fonction, le résultat est correct.

    Ma question est simple : comment se fait-il qu'avec une même valeur, le résultat d'un calcul puisse être différent ?
    Question bonus : pourquoi ce problème n'est pas rencontré pour le calcul de la latitude ?

    Si vous avez besoin de plus de renseignements, n'hésitez pas.
    Merci d'avance pour vos réponses.

  2. #2
    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
    Quel est ton micro-contrôleur (µC) ? Est-ce un µC 8 bits ?

    Tu peux nous dire la valeur rendue par sizeof int ?

  3. #3
    Membre à l'essai
    Inscrit en
    Mars 2009
    Messages
    13
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 13
    Points : 10
    Points
    10
    Par défaut
    Effectivement, j'ai omis les infos d'environnement. Les voici donc :
    IDE : MikroC
    µC : 8bit 18F4250.

    Pour ce qui est du siezof int, je vous répond demain, car je n'ai pas la carte de dev sous la main.

  4. #4
    Membre émérite
    Avatar de skeud
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2011
    Messages
    1 091
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 1 091
    Points : 2 724
    Points
    2 724
    Billets dans le blog
    1
    Par défaut
    C'est un µC en 8 bit du coup lors de ton calcul:
    125*(166+180)=43250.
    Or 43250 = 1010100011110010 donc pour pouvoir stocker ce nombre, il te faut un espace de 16bit (sur un pc, ça passe nikel comme c'est du 32 ou 64bit).

    Par contre sur ton µC c'est du 8bit donc tu dépasse ta valeur INT_MAX. C'est donc normal que ton calcul soit faux.

    Pour la solution, je n'ais pas assez de connaissances sur ce genre de programmation pour te répondre, mais tu as une piste .
    Pas de solution, pas de probleme

    Une réponse utile (ou +1) ->
    Une réponse inutile ou pas d'accord -> et expliquer pourquoi
    Une réponse à votre question


  5. #5
    Membre éclairé
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Points : 719
    Points
    719
    Par défaut
    Si Bktero et skeud ont raison (et je pense qu'ils ont raison), alors il reste une question sans réponse :

    comment se fait-il qu'avec une même valeur, le résultat d'un calcul puisse être différent ?
    Ma théorie, c'est que si tu écris la valeur de variable directement dans le source de la fonction, alors d'une certaine manière, pour le compilateur, ce n'est plus une variable, c'est une constante.
    Il peut donc remplacer ce code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    longitude_x = ((125*(lon+180))/360)+1;
    par ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    longitude_x = ((125*(166+180))/360)+1;
    et donc, comme optimisation, virer le calcul et coder directement la valeur résultante dans longitude_x.
    Mais ton compilateur ne souffre pas des mêmes problèmes de précision, donc la valeur résultante est "juste".

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 374
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 374
    Points : 23 631
    Points
    23 631
    Par défaut
    Hello à tous !

    Citation Envoyé par Porkipic Voir le message
    Ma question est simple : comment se fait-il qu'avec une même valeur, le résultat d'un calcul puisse être différent ?
    Il faudrait le code du niveau juste au-dessus pour voir comment ton paramètre est transmis. Et d'une manière générale, il faudrait vérifier au debugger que la valeur arrive bien intacte au début du code de ta fonction.

    Question bonus : pourquoi ce problème n'est pas rencontré pour le calcul de la latitude ?
    Parce que la formule n'est pas la même non plus et qu'à ce stade, le mystère n'est pas élucidé.

    Citation Envoyé par skeud Voir le message
    C'est un µC en 8 bit du coup lors de ton calcul:
    Pendant très longtemps, on a travaillé avec des machines 8 bits, en temps qu'ordinateur de bureau (notamment les Thomson dans les écoles) et on manipulait des nombres plus larges. Heureusement ! La seule différence est que le calcul se fait en plusieurs étapes plutôt que tenir en une seule instruction assembleur, ce qui a d'ailleurs favorisé l'abandon de sa pratique au profit du C. Mais le C stipule en principe qu'un short int doit mesurer au moins 16 bits et qu'un int ne peut être moins large. Il est possible que sur certaines plateformes, ces valeurs soit revues à la baisse mais c'est très peu probable sans raison valable et ça n'a pas lieu d'être sur un micro-contrôleur récent et grand public comme le PIC 18Fxxxx.

    125*(166+180)=43250.
    Or 43250 = 1010100011110010 donc pour pouvoir stocker ce nombre, il te faut un espace de 16bit (sur un pc, ça passe nikel comme c'est du 32 ou 64bit).
    À mon avis, c'est surtout ça le problème : on ne peut stocker 43250 que sur 16 bits non signés ! Le type int naturel est signé par défaut et c'est également ce format qui est utilisé par les arguments de la fonction. Si le format int est sur 16 bits par défaut sur cette cible, il n'y a pas de raison que le compilateur le promeuve automatiquement à un format plus grand. D'où la question très pertinente de Bktero.

    Citation Envoyé par phi1981 Voir le message
    Ma théorie, c'est que si tu écris la valeur de variable directement dans le source de la fonction, alors d'une certaine manière, pour le compilateur, ce n'est plus une variable, c'est une constante.
    Il peut donc remplacer ce code […] et donc, comme optimisation, virer le calcul et coder directement la valeur résultante dans longitude_x. Mais ton compilateur ne souffre pas des mêmes problèmes de précision, donc la valeur résultante est "juste".
    Très pertinent également, et c'est probablement pour cette raison que le calcul fonctionne dans ce cas si la « panne » ne vient pas d'une erreur de transmission en amont.

  7. #7
    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
    La question était pertinente car je me suis déjà fait avoir C'est d'ailleurs mon premier souvenir en micro-controleur, un calcul du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int b = ...;
    f(b);
     
    // dans f :
    int c = Y * b / Z;
    Le Y * b dépassait la valeur possible pour un int et c'était foutu ! D'ailleurs je me demande même si ce n'était pas des char et non des int mais que les int faisaient 8 bits. Car oui, Obsidian, je me méfierais de tout avec des compilateurs un peu exotiques pour micro-contrôleurs ! Même de int qui feraient 8 bits ! Le compilateur C18 pour PIC18 propose par exemple un type short long qui fait 24 bits.

    Mon idée ici était donc bien la même que skeud. Obsidian a "affiné". Et phi1981 a répondu à la question que je continuais à me poser.

    Tournée de !

  8. #8
    Membre à l'essai
    Inscrit en
    Mars 2009
    Messages
    13
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 13
    Points : 10
    Points
    10
    Par défaut
    Hola, vous vous êtes déchainés pendant la nuit. Ça fait plaisir d'avoir des réponses qui font avancer le schmilblick en se levant.

    Donc le sizeof(int)=2, donc il devrait y avoir assez de place pour stocker le nombre.
    Et sizeof(char)=1 or latitude_y et longitude_x sont déclarés comme ceci dans la fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    unsigned char latitude_y, longitude_x;
    Donc si je suis bien vos explications, il faudrait que je change la déclaration par ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    unsigned int latitude_y, longitude_x;
    Merci de l’explication sur le travail de compilation qui transforme une fonction en constante, je tâcherais de m'en rappeler. Juste pour confirmer que j'ai bien compris :
    En passant par une constante comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    longitude_x = ((125*(166+180))/360)+1;
    Le µc ne travaille pas pour effectuer le calcul mais stocke le résultat sous forme de constante calculée par le compilateur.

    En passant par un variable comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    longitude_x = ((125*(lon+180))/360)+1;
    Le µc travaille pour effectuer le calcul, mais n'a pas assez de place pour stocker les résultats intermédiaires de l'opération.
    Mais cela soulève d'autres questions ...

    Je teste ça ce WE et vous donne la réponse lundi (pas d'internet chez moi).

  9. #9
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 374
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 374
    Points : 23 631
    Points
    23 631
    Par défaut
    Hello,

    Citation Envoyé par Porkipic Voir le message
    Donc le sizeof(int)=2, donc il devrait y avoir assez de place pour stocker le nombre.
    Uniquement en non signé. Et là encore, pour ne pas dépasser 65536, ta longitude ne doit pas excéder 344°, ni être négative. Ce qui pose évidemment un problème.

    Et sizeof(char)=1 or latitude_y et longitude_x sont déclarés comme ceci dans la fonction :
    sizeof(char) vaut toujours 1. Pour être fixés, il nous faudrait également la valeur de CHAR_BIT, mais il y a peu de chances pour qu'elle soit différente de 8.

    Donc si je suis bien vos explications, il faudrait que je change la déclaration par ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    unsigned int latitude_y, longitude_x;
    Ce n'est pas si simple, malheureusement. Le C n'effectue pas ses calculs sur le format de la lvalue mais promeut toute l'expression vers le type le plus « large » des opérandes avant de caster le résultat si nécessaire.
    Ensuite, il faut voir également si les fonctions qui utilisent ces variables a posteriori sont faites ou non pour traiter des valeurs négatives…

  10. #10
    Membre émérite
    Avatar de skeud
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2011
    Messages
    1 091
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 1 091
    Points : 2 724
    Points
    2 724
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par Porkipic Voir le message
    Donc le sizeof(int)=2, donc il devrait y avoir assez de place pour stocker le nombre.

    Cela veut dire que ton int possède 16bit, mais attention c'est un signed int donc en fait tu ne possède que 15bit pour ton stockage.
    Personnellement, je dirais que tu devrais modifier ta variable d'entrée en unsigned int et le tour est joué. Par contre vérifie bien que ton calcul ne déppassera jamais 65535, et normalement tu devrais avoir un résultat correct.

    Sinon pour évité tout problème, passe toute tes variable en long, la tu n'auras plus de problème de dépassement.

    PS: habituellement, pour les calculs de coordonnées (écran), on utilise uniquement des unsigned int pour simplifier les calculs du proc, j'avais lu un article la dessus, comme quoi les compilo optimise mieux les unsigned int que les int lors des calculs.
    Pas de solution, pas de probleme

    Une réponse utile (ou +1) ->
    Une réponse inutile ou pas d'accord -> et expliquer pourquoi
    Une réponse à votre question


  11. #11
    Membre à l'essai
    Inscrit en
    Mars 2009
    Messages
    13
    Détails du profil
    Informations forums :
    Inscription : Mars 2009
    Messages : 13
    Points : 10
    Points
    10
    Par défaut
    Comme l'a proposé Skeud, j'ai passé les variables en long et ça fonctionne nickel.
    Il ne me reste plus qu'a comprendre comment faire des maths avec des chiffres très élevé ou a virgule... Mais c'est une autre histoire.

    Merci de votre aide à tous et de vos explications.
    Sujet passé en résolu.

  12. #12
    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
    Avec un micro-contrôleur 8 bits, n'espère pas trop faire des maths trop intensivement. La première raison est celle que tu viens de voir : un int fait 16 bits dans ton cas, tes longs risquent de ne pas dépasser 32 bits. Adieu le 64 bits sans des opérations un peu plus évoluées. Et donc on arrive à la seconde raison qui est que des µC 8 bits sont rarement des bêtes de courses. Celui que tu utilises possède une puissance de calculs de 10 Mips* : http://www.microchip.com/wwwproducts...cName=en010297 Si tu as des calculs compliqués, il te faudra donc un certain temps. S'ils sont réalisés ponctuellement, OK ; s'ils doivent être exécutés très régulièrement, cela peut alourdir considérablement la charge CPU.

    Même remarque sur les flottants : ce sera forcément plus lent qu'avec des entiers. Demande toi si tu as vraiment besoin des décimales. Des fois, on fait les calculs en flottants entiers et on divise par 100 au moment de l'affichage, ça peut suffire.

    Sans vouloir te dire "ce n'est pas possible", je veux simplement te dire "sois conscient des limites de ton matériel"

    * A titre de comparaison (même si les architectures ne sont pas les mêmes et que donc la comparaison n'est pas méga fiable), un Cortex M0 d'ARM peut carburer 45 MIPS (à 50 MHz, d'après Wiki http://en.wikipedia.org/wiki/Instruc...ons_per_second). Pourtant, Cortex M0 est un relativement petit µC (bien que ce soit un 32 bits donc forcément une taille au dessus).

  13. #13
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 374
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 374
    Points : 23 631
    Points
    23 631
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Avec un micro-contrôleur 8 bits, n'espère pas trop faire des maths trop intensivement. La première raison est celle que tu viens de voir : un int fait 16 bits dans ton cas, tes longs risquent de ne pas dépasser 32 bits. Adieu le 64 bits sans des opérations un peu plus évoluées. Et donc on arrive à la seconde raison qui est que des µC 8 bits sont rarement des bêtes de courses. Celui que tu utilises possède une puissance de calculs de 10 Mips* : http://www.microchip.com/wwwproducts...cName=en010297 Si tu as des calculs compliqués, il te faudra donc un certain temps. S'ils sont réalisés ponctuellement, OK ; s'ils doivent être exécutés très régulièrement, cela peut alourdir considérablement la charge CPU.
    C'est vrai mais il ne faut pas oublier que l'informatique évolue très vite et ce qui nous apparaît comme de la préhistoire était encore tout-à-fait acceptable il y a quelques années. D'autre part, les processeurs huit bits ont l'avantage de forcer le programmeur à travailler au niveau de l'octet qui reste encore aujourd'hui l'unité de référence même sur les grosses machines. En outre, même si cela commence désormais à être vraiment lointain, il ne faut pas oublier que les ordinateurs familiaux huit bits ont été la norme pendant très longtemps, et que c'était la manière la plus efficace de faire des mathématiques (c'était lent, ok, mais pas insupportable). Enfin, il y a toujours le domaine des calculatrices de poche, qui sont de petits ordinateurs à elles seules et qui sont dédiées à cela (même si le nombre de calculs qu'elles effectuent par secondes est limité par l'utilisateur, également).

    Quelque chose de très intéressant également : les calculatrices 4 opérations « jetables » ou premier prix. Aujourd'hui, on peut raisonnablement penser qu'elles fonctionnent avec des micro-processeurs quand même mais il y a encore une dizaine d'années, c'était les seules machines numériques répandues capables de faire des calculs en se basant uniquement sur un automate (au sens industriel), sans recourir forcément à un logiciel.

    Même remarque sur les flottants : ce sera forcément plus lent qu'avec des entiers. Demande toi si tu as vraiment besoin des décimales. Des fois, on fait les calculs en flottants et on divise par 100 au moment de l'affichage, ça peut suffire.
    Tu voulais peut-être dire « en entiers ».

    Une autre astuce également : utiliser des décimales multiples de la base de travail plutôt que sur celles dont on a l'habitude : par exemple, en travaillant avec des 256èmes plutôt qu'avec des centièmes, on obtient une partie fractionnaire contenue dans un octet entier. En travaillant sur 16 bits, par exemple dans AX sur PC, on récupère automatiquement la partie entière dans AH et la partie rationnelle dans AL sans avoir à effectuer la moindre opération. Ça se faisait sur les x86 jusqu'au 286, mais également sur les huit bits car les principaux processeurs de l'époque, le Z80 et chez nous le 6809, étaient capables de grouper une paire d'accumulateurs 8 bits en un registre virtuel 16 bits, compatible avec les modes d'adressages et les autres registres d'index.

    Ça veut dire également qu'en ce qui concerne les routines de calcul, ça vaut le coup de revenir faire un peu d'assembleur ou, à tout le moins, d'aller voir comment sont implémentées les routines de la bibliothèque C standard et runtime. Sur les micro-contrôleurs modestes, par exemple, il n'y a pas d'instruction de division car cette opération est compliquée et qu'elle implique la mise en place d'un système « d'exception » pour gérer le cas de la division par zéro. Il y a donc forcément un logiciel quelque part pour obtenir les quotients et les modulos réclamés par « / » et « % ».

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

Discussions similaires

  1. [MySQL] [SQL] problème affichage résultat d'une requête
    Par mitmit dans le forum PHP & Base de données
    Réponses: 2
    Dernier message: 30/04/2007, 11h14
  2. Réponses: 19
    Dernier message: 21/11/2006, 11h57
  3. Réponses: 5
    Dernier message: 17/06/2006, 12h33
  4. [MySQL] problème de résultat avec une requête
    Par jexl dans le forum PHP & Base de données
    Réponses: 3
    Dernier message: 29/03/2006, 23h23
  5. [SQL] Problème de résultat avec une requête
    Par raptorman dans le forum PHP & Base de données
    Réponses: 2
    Dernier message: 04/01/2006, 17h16

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