tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
ISIMA
  • twitter
  • linkedin
  • viadeo

[C] TP : Calculatrice

Date de première publication web : 2014/11/27

L'objectif de ce TP est de faire une petite calculatrice en mode texte. On met en œuvre les pointeurs de fonctions et un affichage graphique peut être proposé.

Le programme utilise les fonctions mathématiques, il faut donc faire l'édition des liens avec la bibliothèque mathématique.

Un squelette d'application C est disponible ici :

git clone https://gitlab.com/kiux/skel.git

Mise en bouche

Pincettes...

  1. Écrire une fonction identification() qui reçoit en entrée une chaîne de caractères correspondant à une expression mathématique simple et teste si elle appartient au tableau de chaînes de caractères OPER_NAMES suivant :
const char * OPER_NAMES[] = { "x", "sin(x)", "cos(x)", "log(x)", "exp(x)", NULL }; 

La présence de la valeur de fin NULL permet de ne pas avoir à connaître le nombre d'éléments du tableau.

Si la chaîne lue est trouvée, on retourne :

typedef enum ope {
  NONE = -1, ID , SIN, COS, LOG, EXP
} OP;

Vous aurez traduit que ID est pour la fonction identite et que NONE est une valeur qui ne correspond à aucune des fonctions précédentes.


TEST(identification_avec_indice) {
  CHECK(  0 == identification("x"));
  CHECK( -1 == identification("x\n"));
  CHECK(  1 == identification("sin(x)"));
  CHECK(  2 == identification("cos(x)"));
  CHECK(  3 == identification("log(x)"));
  CHECK(  4 == identification("exp(x)"));
  CHECK( -1 == identification("t"));
}


TEST(identification_avec_enum) {
  CHECK(   ID == identification("x"));
  CHECK( NONE == identification("x\n"));
  CHECK(  SIN == identification("sin(x)"));
  CHECK(  COS == identification("cos(x)"));
  CHECK(  LOG == identification("log(x)"));
  CHECK(  EXP == identification("exp(x)"));
  CHECK( NONE == identification("t"));
}
  1. Écrire la fonction evalf() qui accepte en entrée une valeur réelle double précision et un indice ou une énumération correspondant à une expression mathématique identifiée par la fonction précédente. Ainsi evalf(0.0, SIN) "==" 0.0. Si vous le voulez, vous pouvez renvoyer le nombre NAN - Not A Number - lorsque l'identification a mené à quelque chose d'inconnu.

  TEST(control_eval_f) {
   CHECK( EQ( 0.0, evalf( 0.0, ID)));
   CHECK( EQ( 0.0, evalf( 0.0, SIN)));
   CHECK( EQ( 1.0, evalf( 0.0, COS)));
   CHECK( EQ( 1.0, evalf( 0.0, EXP)));

   CHECK( EQ(      M_PI , evalf( M_PI, ID)));
   CHECK( EQ(       0.0 , evalf( M_PI, SIN)));
   CHECK( EQ(      -1.0 , evalf( M_PI, COS)));
   CHECK( EQ( exp(M_PI) , evalf( M_PI, EXP)));
   
   CHECK( EQ( 0.0 , evalf( M_PI/2.0, COS)));
}
  1. Écrire une fonction calcul() qui reçoit en entrée un intervalle [a, b], un pas de progression delta dans cet intervalle, un identificateur d'expression (indice ou énumération) et un fichier dans lequel écrire. Cette fonction évalue l'expression "identifiée" pour toutes les valeurs entre a et b par pas de delta et écrit dans le fichier précisé (par exemple, la sortie standard) chaque résultat d'évaluation (séparer les résultats par des espaces).
  1. Écrire un programme principal qui demande à l'utilisateur la fonction à afficher, l'intervalle d'affichage et la progression et qui affiche tout cela.

Nous rappelons que la fonction privilégiée pour faire une saisie texte est fgets() et que pour convertir des chaînes de caractères en nombre, vous pouvez utiliser sscanf() ou les fonctions suivantes :

Pincettes...

On va essayer de simplifier l'écriture de la fonction d'évaluation evalf() en utilisant un tableau de pointeurs de fonction comme celui-ci :

double (*OPER_FN [])(double) = { identite, sin, cos, log, exp, erreur };

identite() et erreur() sont deux fonctions à écrire avec le même prototype que les autres fonctions mathématiques. identite(t) renvoie la valeur t. erreur(t) affiche un message d'erreur sur la sortie d'erreur par exemple.

Écrire la fonction evalp() avec le même prototype que evalf(). Si le paramètre correspondant à la fonction est mauvais, la fonction erreur est appelée !

On peut aussi d'affranchir de l'énumération OP si la fonction d'identification renvoie directement le pointeur de l'opération à effectuer !

Deuxième partie

La chaîne lue comporte maintenant deux expressions du type défini dans la première partie séparées par un des opérateurs suivants :
+, -, *, /. On va par exemple lire la chaine suivante : "x + sin(x)"

Version simple ?

Vous utiliserez la fonction fgets() pour lire la chaîne et la fonction sscanf() de la manière suivante pour séparer les expressions et l'opérateur :

sscanf (chaine,"%s %c %s", exp1, &op, exp2);

Réaliser un programme qui évalue l'expression lue dans l'intervalle [a,b] avec un pas de delta. Identifiez de vous même les fonctions élémentaires dont vous avez besoin pour limiter le travail, et celles qu'il faudra modifier pour réaliser ce programme. Limitez au maximum des erreurs (segmentation fault, erreurs de lecture, …).

Passage aux pointeurs de fonctions

C'est un peu plus sioux que pour la première partie. Je vous propose de regarder les regarder les codes ASCII des opérateurs et de les ordonner. Le tableau de pointeurs de fonction est défini avec les fonctions que l'on prendra le soin de d'écrire et éventuellement des pointeurs NULL.

double (*tab2[]) (double, double) = { mul, add, NULL, sou, NULL, dyv };

div() est une fonction standard. L'ordre des opérateurs n'est pas contractuel

Il suffit alors de faire un tab2[c-'*'](x,y) pour avoir le résultat.

Plat de résistance

Si vous vous êtes pris au jeu, on peut aller plus loin et faire les choses suivantes :

La notation polonaise inverse est une manière d'évaluer une expression mathématique sans utiliser de parenthèse. L'intérêt est que l'évaluation est très simple et se fait de gauche à droite en empilant et dépilant les éléments

Il est également aisé de convertir une expression parenthésée en notation polonaise inverse.

Calculer sin(x) + 3 revient à évaluer x sin 3 +

Structure de stockage pour l'évaluation

Je vais limiter arficiellement le nombre d'opérations (symboles) à effectuer à une taille donnée (par exemple 256). Un symbole peut être :

Je vous propose cet éventail de possibilités avec une structure contenant une énumération et une union :-)

Voici la liste des symboles possibles :

enum e{
     VAL, VAR, F1, F2, FIN
};

qui correspondent à l'union suivante :

union u {
     double   val;
     double * var;
     double (*f1)(double);
     double (*f2)(double, double);
};

Il faudra donc traduire la phrase de l'utilisateur par une suite de symboles

struct s {
     enum  e type;
     union u data;
};

struct s donnees[256];

Structure de pile

Pour la structure de pile, il suffit de prendre un tableau de dimension maximale fixée et de connaître le nombre d'éléments de la pile.

struct pile {
  struct s tab[CAPACITE];
  int taille;
};

Code

Vous avez pu noter que les variables sont connues par leur adresse (soient éléments d'un tableau de variables, soit nommées directement :

double x;
struct s symb;
symb.type      = VAR;
symb.data.var  = &x;

Pour la reconnaissance des variables, on peut se limiter à une liste finie (x et t dans un premier temps par exemple).

Voici ce que je vous conseille de faire :

Vous pouvez supposer que la chaine de symboles est correcte (c'est-à-dire que les erreurs éventuelles seraient détectées en amont, à la création par exemple

Pour convertir une chaine en nombre réél, vous pouvez également utiliser la fonction strtod(). On peut savoir si la conversion a échoué.

Si tout cela ne vous donne pas envie de faire une calculatrice graphique !

Have Fun !