Calculatrice Scientifique

1. Introduction

L'objectif de ce projet est de créer une calculatrice scientifique avec deux modes de fonctionnement. Le premier permet de résoudre des équations à une inconnue et le second d'évaluer une expression mathématique. Comme nous n'utiliserons aucun environnement graphique prédéfini, il faudra écrire une interface textuelle permettant d'afficher un menu et gérer les saisies de l'utilisateur.

Dans un premier temps, nous nous intéresserons donc à la structure générale du programme, avec les menus et ses sous-programmes. Nous donnerons aussi des fonctions de base venant s'ajouter à celles des librairies C utilisées. Nous écrirons en particulier une fonction permettant de convertir une chaine de caractères en nombre décimal.

Nous étudierons ensuite le mode résolution des équations à une inconnue. Les solutions de telles équations peuvent être exprimées à patir des coefficients de l'équation si et seulement le degré de l'équation est inférieur ou égal à 4. Nous nous contenterons d'équation du premier et second degré, avec des racines réelles. Les solutions seront données sous forme de nombres décimaux.

Enfin, dans une dernière partie nous regarderons le mode d'évaluation d'une expression mathématique sans variable acceptant plusieurs opérateurs classiques, des délimiteurs ainsi que des fonctions. Avant de donner le résultat, l'arbre de l'expression mathématique sera affiché à l'écran. Le résultat sera aussi donné sous forme décimal.

2. Structure et fonctionnalités générales

2.1. Structure générale

En suivant la spécification fournie avec le sujet, nous obtenons l'organisation suivante pour le programme :

La fonction principale main affiche un menu avec les différents choix possibles que l'utilisateur peut réaliser en saisissant un caractère. Il s'agira donc simplement d'une boucle qui analyse les caractères saisis par l'utilisateur et appelle les sous-programmes ou quitte le programme.

Nous découperons notre programme en trois fichiers main.c, trinome.c, evaluation.c correspondant au programme principal et aux deux sous-programmes. Le fichier main.c contiendra en outre des fonctions et définitions élémentaires utilisées par les deux autres sous-programmes, et qui seront déclarées dans le fichier declaration.h. Par ailleurs, chacun de ses sous-programmes inclura son fichier d'en-tête, à savoir trinome.h et evaluation.h, avec les déclarations qui lui sont propres.

La compilation du projet (voir le fichier Makefile) se fera donc avec les instructions suivantes :

calculatrice : main.o trinome.o evaluation.o
gcc -lm -o calculatrice -Wall main.o trinome.o evaluation.o

main.o : main.c
gcc -c -Wall main.c

trinome.o : trinome.c
gcc -c -Wall trinome.c

evaluation.o : evaluation.c
gcc -c -Wall evaluation.c

2.2. Structure des sous-programmes

Les deux sous-programmes demandent à l'utilisateur de rentrer une expression, l'analysent, effectuent des opérations en fonction de la saisie et libère la mémoire allouée pour cette chaine. Il est pratique pour l'utilisateur de pouvoir directement saisir une nouvelle expression. Ainsi, le sous-programme ré-itère ces intructions tant que l'utilisateur saisie une expression. Si l'utilisateur tape directement sur la touche entrée, le programme revient au menu principal.

2.3. Fonctions et définitions élémentaires

Il est tout d'abord utile de posséder des fonctions de base de la librairies C, on inclura donc stdlib.h. Comme on va utiliser des opérations de saisies et d'affichage, on inclut aussi stdio.h. Les librairie s ctype.h et string.h seront pratiques pour la gestion des caractères et des chaines de caractères. Enfin, la librairie math.h servira pour les opérations mathématiques, comme laissait présager le -lm dans l'édition de liens.

Lorsque les fonctions retourne des valeurs booléenne, il peut être plus clair d'utiliser explicitement les constantes VRAI et FAUX qui sont donc respectivement définies pour 1 et 0. Une fonction effacer_ecran() réalisera l'appel système clear.

Le programme utilise des malloc() pour la gestion d'objets dynamiques. En cas d'échec d'une allocation mémoire, il ne récupère pas l'erreur et quitte simplement le programme. Il précise toutefois le problème à l'utilisateur en affichant un message d'erreur avant la terminaison. La fonction erreur_memoire() se charge d'afficher ce message et de quitter le programme.

2.4. Gestion de saisies

Les saisies proposées à l'utilisateur sont soit un caractère alphanumérique (pour le menu) soit une chaine de caractères (pour les sous-programmes). La librairie standard C possède déjà une fonction getchar() permettant de lire un caractère. Toutefois, elle présente le désavantage de ne pas vider le buffer clavier. Une fonction saisir_caractere() apporte donc des améliorations et controle de plus que le caractère est bien un chiffre ou une lettre.

Pour la gestion de chaine de caractères, nous utilisons la fonction fgets qui permet de limiter la longueur maximale et ainsi éviter les débordements mémoire. Cette longueur sera définie dans la variable globale int longueur_maximale et pourra être paramètrée par l'utilisateur. Nous ajoutons donc un sous-programme parametrer_longueur() se chargeant de l'opération et une entrée dans le menu. Plus précisément, nous utiliserons une fonction saisir_chaine(char **s) qui réalise une allocation mémoire, fait pointer *s sur le début de la zone allouée et y stocke le résultat du fgets.

Enfin, les sous-programmes peuvent détectées des erreurs dans les expressions saisies par l'utilisateur. Un message d'erreur est alors affiché par le biais de la fonction erreur_saisie(char *message). Elle appelle juste le printf avec "Erreur de saisie : " suivi du message.

2.5. Analyse des expressions saisies

L'analyse de l'expression saisie par l'utilisateur se fait en parcourant la chaine de caractères avec un pointeur. Plusieurs opérations se rencontrent alors fréquemment. Par exemple, les espaces rentrés par l'utilisateurs ne sont généralement pas pertinents. Une fonction sauterEspace(char **chaine) permet ainsi de déplacer le pointeur *chaine après les caractères blancs (c'est-à-dire ce qui sont donnés par la fonction isspace) .

Pour une étudier ponctuellement un caractère on utilise la fonction verifier_caracterer(char **chaine, char caractere) qui vérifie que le caractère pointé par *chaine est, en ignorant la casse, égal à caractere. Cette fonction renvoie VRAI ou FAUX et avance le curseur sur le caractère suivant dans le premier cas, ce qui est généralement ce que l'on désire.

Enfin la fonction interpreterNombre(char **chaine, double* valeur) est utilisée dans les sous-programmes pour lire un nombre décimal, et n'est utilisée qu'avec des nombres sans signe dans evaluation.c. Elle effectue les opérations suivantes :

  1. Vérifie la présence d'un signe + ou - et saute les espaces suivants.
  2. Lecture de la partie entière, c'est-à-dire tant que l'on rencontre des chiffres.
  3. Vérifie la présence d'un point "." et lit alors la partie décimale tant que l'on rencontre des chiffres.

Les expressions régulières lues par la fonction s'écrivent donc [{+, -}](espace)*{0 à 9}*[.{0 à 9}*] où les crochets signifient un élément facultatif, les accolades un ensemble de possibilités pour un caractère, et l'étoile la répétition de zéro, un ou plusieurs caractères. La fonction indique si rien n'a été lu, c'est-à-dire VRAI si l'expression est vide.

3. Résolution d'équation du second degré

3.1. Présentation générale de equation2d

Le code source des fonctionnalités décrites dans ce paragraphe est contenu dans les fichiers trinome.c et trinome.h. Voici les fonctions proposées dans le sujet :

La chaine de caractère codant l'équation est stockée dans une variable equation_saisie par le biais de la fonction lireEquation. Une équation comporte deux membres de même structure séparés par le symbole égal. La première opération consiste donc à vérifier la présence de ce symbole et à découper la chaine en deux sous-chaines membre gauche et membre_droit, puis à leur appliquer un même traitement. Si le symbole égal n'a pas été trouvé, un message d'erreur est affiché et l'analyse s'arrête. Les expressions acceptées par la calculatrice sont donc de la forme (interpreterSomme)=(interpreterSomme).

Une fois chaque membre analysée et convertit en une structure de données, on utilise la fonction reduireEquation qui regroupe les termes et les fait tous passer dans le membre gauche. On supprime d'éventuels termes nuls à l'aide d'une fonction supprimerTermesNuls. On affiche ensuite l'expression simplfier via une fonction afficherSomme. On vérifie que l'équation est bien du premier ou second degré on applique l'algorithme de résolution classique. On affiche si il y a zéro, une, deux solutions réelles ainsi que leurs valeurs décimales approchées. Enfin, on libère la mémoire utilisée, notamment grace à libererSomme.

3.2. Structure de données pour les membres

Le traitement des chaines représentant les membres consiste à les convertir en une nouvelle structure codant les termes de l'équation. La structure retenue est une liste chainée donc chaque élément code un terme de la forme a X n a est un nombre décimal et b est un entier.

typedef struct terme
{
  double coefficient;
  int degre;
  struct terme *suivant;
} terme;

typedef terme* somme;

Par exemple X 2 2 x + 3 2 sera représenté par une liste chainée à 3 éléments :

Liste chainée représentant la somme X^2 - 2x + 3/2 coefficient = 1.0 degre = 2 *suivant = coefficient = -2.0 degre = 1 *suivant = coefficient = 1.5 degre = 0 *suivant = NULL

3.3. Conversion en liste chainée

La conversion de chaque membre en liste chainée est réalisée par la fonction interpreterSomme(char *chaine, somme *s) qui retourne VRAI si et seulement si aucune erreur ne s'est produite. Elle parcourt la chaine et réalise les opérations ci-dessous. A la sortie de la boucle, si on est bien arrivé à la fin de la chaine, on renvoie VRAI et FAUX sinon.

  1. Allocation d'une zone mémoire pour stocker un terme. Si c'est le premier, stocke le pointeur dans *s.
  2. Appel à interpreterTerme. Cette fonction VRAI si et seulement si aucune erreur ne s'est produite. Dans ce cas, elle déplace le curseur de la chaine analysée. Dans le cas contraire, interpreterSomme retourne FAUX.
  3. Appel à sauterEspace pour ignorer d'éventuels caractères blancs.
  4. Continue tant que l'on a un symbole + ou -.

L'expression régulière acceptée par interpreterSomme est donc :

(interpreterTerme)(espace)*((interpreterTerme commençant par un + ou un -)(espace)*)*

La fonction interpreterTerme analyse un terme de la façon suivante :

  1. saute des espaces
  2. essaye de lire un coefficient avec interpreterNombre
  3. saute des espaces
  4. Si le coefficient a été lu, lit un éventuel caractère * et saute des espaces dans ce cas.
  5. Vérifie le caractère X ou x. Si il est absent et qu'aucun coefficient n'a été lu, retourne FAUX. Sinon retourne VRAI.
  6. Vérifie le caractère ^ . Si il est présent, lit obligatoirement un nombre. Sinon, vérifie un nombre seulement si un chiffre suit le X.
  7. Retoune VRAI ou FAUX selon que l'exposant est un entier naturel ou non.

L'expression régulière acceptée par interpreterTerme est donc donné par un des schémas suivants :

où l'on a noté :

3.4. Réduction de l'équation

Une fois nos deux listes chainées gauche et droite obtenue, on va en créer une troisième où l'on va stockée les termes de gauche et les opposées des termes de droite. Ceci est réalisé par la fonction reduireEquation qui prend en paramètre gauche et droite et un pointeur vers la nouvelle liste reduction. Cette fonction appelle à chaque fois ajouterTerme qui ajoute un terme dans une liste d'éléments triés selon leur degré décroissant en fusionnant les termes de même degré.

La liste reduction étant initialement vide, elle est initialement triée selon les degrés décroissant et avec un et un seul terme par degré. A la fin de la l'opération, elle possède donc encore cette propriété. En additionnant les termes de même degré, on peut être conduit à des termes avec des coefficients nuls. Une fonction supprimerTermesNuls se charge alors d'éliminer ces termes indésirés de la liste reduction.

Une fois la liste réduite, il est facile d'obtenir ses coefficients a , b , c de l'équation a X 2 + bX + c = 0 . La fonction obtenirCoefficients réalise cette opération : elle parcourt la liste et stocke les valeurs des coefficients en analysant les degrés des termes. Si un degré strictement supérieur à 2 est trouvé, la fonction retourne FAUX et VRAI sinon.

4. Évaluation d'une expression mathématique

4.1. Présentation générale de evaluationExpression

Le code source des fonctionnalités décrites dans ce paragraphe est contenu dans les fichiers evaluation.c et evaluation.h. Les fonctions proposées dans le sujet ont été reprises :

La fonction evaluerExpression effectue ces opérations dans l'ordre. Après la conversion en liste, la liste est affichée à l'aide d'une fonction afficherListe. De plus à la fin des opérations, les objets alloués sont libérés, notamment grace aux fonctions libererListe et libererArbre.

4.2. Quelques déclarations utiles

La calculatrice est capable de reconnaitre plusieurs symboles, par exemple un opérateur binaire + ou un séparateur ouvrant (. Pour faciliter la lecture du code, on va utiliser une énumération des types d'éléments. Les opérateurs binaires sont simplement des caractères que l'on stocke dans un tableau operateurs comportant NB_OP elements. ces éléments sont rangés par ordre de priorité. De la même façon on va stocker les fonctions, qui sont des chaines de caractères, dans un tableau operateurs_unaires à NB_OP_UNAIRE éléments. Enfin les NB_SEP délimiteurs, sont stockés dans des tableaux de caractères separateurs_ouvrants et separateurs_fermants.

enum type_element {Indefini, Nombre, OperateurUnaire, OperateurBinaire, SeparateurOuvrant, SeparateurFermant};

#define NB_OP 6
char operateurs[NB_OP] = {'^', '%', '/', '*', '-', '+'};

#define NB_OP_UNAIRE 2
char operateurs_unaires[NB_OP_UNAIRE][5] = {"sin", "cos"};

#define NB_SEP 3
char separateurs_ouvrants[NB_SEP] = {'(','{','['};
char separateurs_fermants[NB_SEP] = {')','}',']'};

4.3. Vérification de l'expression

Dans cette étape, on se contente de vérifier l'expression en parcourant la chaine de caractères. A la place de la fonction interpreterNombre, on utilisera une version simplifiée sauterNombre qui déplace le curseur après le nombre.

Une première vérification concerne la correspondance entre séparateurs Ouvrant/Fermant. On ne peut se contenter de compter les séparateurs ouvrants et les séparateurs fermants et de vérifier qu'il y en a autant ou sinon on pourrait accepter des expressions du type (]){[} qui sont incorrectes. On va donc utiliser un tableau d'entiers ouvertes[NB_SEP] qui compte combien de parenthèses sont ouvertes pour chaque séparateur. Une erreur se produit si il reste des parenthèses ouvertes à la fin de la vérification ou si on tente de fermer une parenthèse alors qu'aucune n'a été ouverte.

Il est aussi pratique de savoir pour l'élément analysé quel type d'élément le précèdait de façon à valider ou non sa présence. Par exemple on ne peut avoir d'opérateur binaire au début de la séquence, deux nombres accolés ou encore une parenthèse fermante après un opérateur unaire. On utilise pour cela un entier precedent, initialement fixé à Indefini. Pour le dernier élément, il faut aussi vérifier que ce n'est pas un opérateur car alors il attend un opérande.

Le parcourt de l'expression réalise à chaque étape une des opérations suivantes. Sauf pour les espaces, on met à jour precedent :

4.4. Conversion en liste

On utilise un type liste chainée similaire à celui utilisée précédemment. Un élément nombre est stocké avec un type de l'énumération type_element. Pour un nombre on stocke sa valeur. Pour un SeparateurOuvrant, un SeparateurFermant ou un OperateurBinaire, on stocke le code ASCII du caractère utilisé, comme indiqué dans le sujet. Pour les trois OperateurUnaire retenus, on stocke le code ASCII des caractères '-', 'c' ou 's' respectivement pour opposé, cosinus et sinus.

typedef struct element
{
  int type;
  double valeur;
  struct element *suivant;
} element;

typedef element* Liste;

La conversion en liste reprend exactement les instructions du paragraphe précédent, mais en ne vérifiant pas les cas d'erreurs, puisqu'on suppose que cela a déjà été réalisé auparavant. On créer une liste chainée resultat et à chaque fois que l'on reconnait un terme, on le stocke dans la liste à l'aide d'une fonction ajouterElement. Cette fonction prend en paramètre le pointeur où l'on stocke l'élément, son type et sa valeur. Notons que pour les nombres, il faut aussi utiliser interpreterNombre à la place de sauterNombre.

4.5. Conversion en arbre

La structure d'arbre utilisée est la suivante. Les champs type et valeur sont les même que pour la liste.

typedef struct feuille
{
  int type;
  double valeur;
  struct feuille *fils_gauche;
  struct feuille *fils_droit;
} feuille;

typedef feuille* arbre;

La conversion de la liste en arbre est réalisée de façon récursive. La fonction listeToArbre(Liste debut, Liste fin, arbre *a) prend pour paramètre la borne inférieur (incluse) et supérieur (exclue) du fragment de liste à analyser, ainsi que le pointeur servant de racine à l'arbre. Ainsi si expression_liste est la liste obtenue précédemment et expression_arbre la variable où l'on souhaite stocker le résultat, evaluerExpression va appeler listeToArbre(expression_liste, NULL, &expression_arbre).

La fonction listeToArbre effectue donc une des opérations suivantes :

4.6. Détermination de l'opérateur à développer

Pour déterminer l'opérateur binaire que l'on doit développer, on parcourt le fragment de liste à analyser. Lorsque l'on rencontre un délimiteur ouvrant, on ignore son contenu. Pour cela, on utilise une fonction sauterParentheses qui déplace le curseur jusqu'à la fermeture du délimiteur fermant correspondant.

Lorsque l'on arrive sur le premier opérateur binaire, il n'est pas sur que l'on doive le développer. En effet, il faut tenir compte de la priorité des opérateurs, par exemple :

D'une façon générale, on constate que l'opérateur binaire rencontré est développable si et seulement si les opérateurs binaires suivants sont de priorité strictement supérieur. Pour uniformiser l'implantation, on considère que deux opérateurs distincts ne peuvent pas avoir de priorité égale, de sorte que l'on peut directement assimiler la priorité de l'opérateur à sa position dans le tableau operateurs = {'^', '%', '/', '*', '-', '+'}. La fonction peutDevelopperIci se charge d'effectuer la vérification à chaque rencontre d'un opérateur binaire.

Pour les deux fonctions sauterParentheses et peutDevelopperIci, il est nécessaire de récupérer, à partir du code ASCII donné dans la liste chainée, la position d'un séparateur ou d'un opérateur dans un des trois tableaux operateurs, separateurs_ouvrants, separateurs_fermants. Cette opération est réalisée dans la fonction numeroFromASCII.

4.7. Affichage de l'arbre

Étant donné que tous les affichages sont réalisés en mode texte, il peut sembler difficile d'afficher l'arbre. En effet le texte ne peut être affiché que de haut en bas et de gauche à droite (en affichant des chaines de caractère et des sauts de lignes). On peut toutefois réaliser des décalages sur une ligne en affichant des espaces (cela est réalisé par une fonction afficherIndentation). L'arbre ayant une structure récursive, nous utilisons une fonction récursive. Comme conseillé dans le sujet, on affiche l'arbre à l'horizontal, c'est-à-dire un arbre "couché". Cela permet de conserver un ordre dans les appels récursifs : en haut le fils droit, puis le noeud, puis en bas le fils gauche. Pour indiquer la profondeur, on augmente l'indentation à chaque appel récursif. Ci-dessous, le résultat de l'exemple de l'énoncé pour l'expression (0/69)*(78/58)-(5/45)*(27-61).

                                 --> 61.000000
                                |
                      --> '-' --|
                                |
                                 --> 27.000000
                     |
           --> '*' --|
                     |
                                 --> 45.000000
                                |
                      --> '/' --|
                                |
                                 --> 5.000000
          |
--> '-' --|
          |
                                 --> 58.000000
                                |
                      --> '/' --|
                                |
                                 --> 78.000000
                     |
           --> '*' --|
                     |
                                 --> 69.000000
                                |
                      --> '/' --|
                                |
                                 --> 0.000000

4.8. Évalutation de l'arbre

L'évaluation de l'arbre est effectué par une fonction récursive evaluerArbre qui est très facile à programmer une fois l'expression convertie en arbre. Toutefois même si une expression est syntaxiquement correcte, il peut rester des erreurs qui ne sont détectables qu'à l'évaluation par exemple la "division par zéro" dans l'expression 5 sin ( 9 3 2 ) . Par conséquent, la fonction evaluerArbre doit être capable de renvoyer une information indiquant qu'une erreur de calcul s'est produite et faire remonter l'information récursivement. La méthode choisie est de réaliser un passage par pointeur pour le résultat du calcul et de retourner une valeur booléenne indiquant l'échec ou le succès du calcul : int evaluerArbre(arbre a, double *r).

Les erreurs d'évaluation possible sont :

Le résultat de l'opération est toujours exprimée en double. Il est donc affiché avec une valeur approchée et peut être ± .

5. Quelques tests

5.1. Mode équation du second degré

5.1.1. Délimitation des deux membres

L'équation doit contenir un et seul signe égal. Si il y en a aucun, une erreur est détectée. Si il y en au moins 2, la délimitation est effectuée au premier signe, et les autres seront interprétés comme des erreurs de syntaxe dans le membre droit.

Rentrez une équation du second degré : 5x^2
Erreur de saisie : Caractère égal non trouvé

Rentrez une équation du second degré : 3x^2 = 5 = 9
Erreur de saisie : Syntaxe du membre droit incorrect

5.1.2. Erreur de syntaxe dans un des membres

Rentrez une équation du second degré : a = 2
Erreur de saisie : Syntaxe du membre gauche incorrect

Rentrez une équation du second degré : 3 = b
Erreur de saisie : Syntaxe du membre droit incorrect

Rentrez une équation du second degré : x + = 1
Erreur de saisie : Syntaxe du membre gauche incorrect

Rentrez une équation du second degré : x -=0
Erreur de saisie : Syntaxe du membre gauche incorrect

Rentrez une équation du second degré : x-+6=0
Erreur de saisie : Syntaxe du membre gauche incorrect

Rentrez une équation du second degré : x^ + x + 1 = 0
Erreur de saisie : Syntaxe du membre gauche incorrect

5.1.3. Gestion des espaces

La calculatrice accepte les espaces lorsqu'ils ne rendent pas ambiguë l'expression. Cela permet d'aérer l'écriture de l'équation.

Rentrez une équation du second degré : x = 1           + 2+3+4      + 5      +6+7   +8+    9

1.000000X + -45.000000 = 0
X = 45.000000

Rentrez une équation du second degré : 1+2+3+4=-5-6-7-8-9+x

-1.000000X + 45.000000 = 0
X = 45.000000

Rentrez une équation du second degré : x 2 = 0
Erreur de saisie : Syntaxe du membre gauche incorrect

Rentrez une équation du second degré : x+2 = x2

-1.000000X^2 + 1.000000X + 2.000000 = 0
Deux solutions : X1 = -1.000000 et X2 = 2.000000

Rentrez une équation du second degré : x2=0

1.000000X^2 = 0
Une solution : X = -0.000000

5.1.4. Simplification et élimination des termes nuls

Rentrez une équation du second degré :  2x + 5x + 10x + 20*x - 5x^2 + 3x - 1x + x2 - 5 = 6 + x - 7x + 8x2 - 20

-12.000000X^2 + 45.000000X + 9.000000 = 0
Deux solutions : X1 = -0.190339 et X2 = 3.940339

Rentrez une équation du second degré : 6X2 -2x + x - 6 + 6x -2 = 2 +x^2 +3x + 2*x

5.000000X^2 + -10.000000 = 0
Deux solutions : X1 = 1.414214 et X2 = -1.414214

5.1.5. Reconnaissance des nombres décimaux

Rentrez une équation du second degré : x = 1.23

1.000000X + -1.230000 = 0
X = 1.230000

Rentrez une équation du second degré : x = .09

1.000000X + -0.090000 = 0
X = 0.090000

Rentrez une équation du second degré : x = 1.

1.000000X + -1.000000 = 0
X = 1.000000

5.1.6. Les différents types de réponses

Rentrez une équation du second degré : x^2 = 9

1.000000X^2 + -9.000000 = 0
Deux solutions : X1 = 3.000000 et X2 = -3.000000

Rentrez une équation du second degré : x^2 + 2x + 1 = 0

1.000000X^2 + 2.000000X + 1.000000 = 0
Une solution : X = -1.000000

Rentrez une équation du second degré : 2x - 4 = 6

2.000000X + -10.000000 = 0
X = 5.000000

Rentrez une équation du second degré : x^2 = -1

1.000000X^2 + 1.000000 = 0
Pas de solutions réelles.

5.1.7. Équations valides mais non acceptées

La calculatrice refuse les expressions sans x ou de degré strictement supérieur à 2.

Rentrez une équation du second degré : x = x + 1

-1.000000 = 0
Equation non acceptée par la calculatrice.

Rentrez une équation du second degré : x^2 = x^2

0 = 0
Equation non acceptée par la calculatrice.

Rentrez une équation du second degré : x^4 + 2x^2 + 1 = 0

1.000000X^4 + 2.000000X^2 + 1.000000 = 0
Equation non acceptée par la calculatrice.

5.1.8. Autres exemples d'équation

Rentrez une équation du second degré : x^2-x-1=0

1.000000X^2 + -1.000000X + -1.000000 = 0
Deux solutions : X1 = 1.618034 et X2 = -0.618034 (X1 est le nombre d'or)
Rentrez une équation du second degré : 1+2x+3x^2+4+5x+6x^2=0

9.000000X^2 + 7.000000X + 5.000000 = 0
Pas de solutions réelles.

Rentrez une équation du second degré : x + 3x + 2 - 2x  + 2x + 4x + x^2 =  2 - 2x^9 + 4*x^9 - 4 + x + 2x^2 -2*x9

-1.000000X^2 + 7.000000X + 4.000000 = 0
Deux solutions : X1 = -0.531129 et X2 = 7.531129

5.2. Mode évaluation d'expression mathématique

5.2.1. Erreurs de délimiteurs

Rentrez une expression mathématique : 5+()
Erreur de saisie : Symbole fermant non attendu

Rentrez une expression mathématique : (4/3}+{2/5)
Erreur de saisie : Symbole fermant ne correspondant pas à un symbole ouvrant.

Rentrez une expression mathématique : (2+)
Erreur de saisie : Symbole fermant non attendu
Rentrez une expression mathématique : (32+{3*6}-6
Erreur de saisie : Symbole fermant attendu.

Rentrez une expression mathématique : 2(3+2)
Erreur de saisie : Symbole ouvrant non attendu

5.2.2. Erreurs d'opérateurs

Rentrez une expression mathématique : x%2
Erreur de saisie : Opérateur inconnu.

Rentrez une expression mathématique : 2/sin
Erreur de saisie : Opérande attendu.

Rentrez une expression mathématique : 3cos2
Erreur de saisie : Opérateur unaire non attendu.

Rentrez une expression mathématique : 4*[^8]
Erreur de saisie : Opérateur binaire non attendu.

5.2.3. Erreurs de nombre

Rentrez une expression mathématique : 2.8  .9
Erreur de saisie : Nombre non attendu

Rentrez une expression mathématique : (5*3)9.0
Erreur de saisie : Nombre non attendu

5.2.4. Simple somme de quatre termes

Rentrez une expression mathématique : 1+2+3+4

1.000000+2.000000+3.000000+4.000000

           --> 4.000000
          |
--> '+' --|
          |
                      --> 3.000000
                     |
           --> '+' --|
                     |
                                 --> 2.000000
                                |
                      --> '+' --|
                                |
                                 --> 1.000000

Résultat : 10.000000

5.2.5. Somme avec délimiteurs

Rentrez une expression mathématique : {[1+(2+3)]+4}

{[1.000000+(2.000000+3.000000)]+4.000000}

           --> 4.000000
          |
--> '+' --|
          |
                                 --> 3.000000
                                |
                      --> '+' --|
                                |
                                 --> 2.000000
                     |
           --> '+' --|
                     |
                      --> 1.000000

Résultat : 10.000000

5.2.6. Priorités des opérateurs

La priorité des opérateurs binaires a été décrites précédemment. Les opérateurs unaires sont prioritaires sur les opérateurs binaires.

Rentrez une expression mathématique : 2/3^4

2.000000/3.000000^4.000000

                      --> 4.000000
                     |
           --> '^' --|
                     |
                      --> 3.000000
          |
--> '/' --|
          |
           --> 2.000000

Résultat : 0.024691

Rentrez une expression mathématique : 2/3-4

2.000000/3.000000-4.000000

           --> 4.000000
          |
--> '-' --|
          |
                      --> 3.000000
                     |
           --> '/' --|
                     |
                      --> 2.000000

Résultat : -3.333333

Rentrez une expression mathématique : 2/3/4

2.000000/3.000000/4.000000

           --> 4.000000
          |
--> '/' --|
          |
                      --> 3.000000
                     |
           --> '/' --|
                     |
                      --> 2.000000

Résultat : 0.166667

Rentrez une expression mathématique : sin2^2-sin4

sin2.000000^2.000000-sin4.000000

                       --> 4.000000
                     |
           --> sin --|
          |
--> '-' --|
          |
                      --> 2.000000
                     |
           --> '^' --|
                     |
                                  --> 2.000000
                                |
                      --> sin --|

Résultat : 1.583624

5.2.7. Opérateurs unaires

La calculatrice est capable d'interpréter le "moins" en un opérateur unaire ou binaire. Elle peut aussi interpréter les compositions d'opérateurs unaires, avec des délimiteurs explicites ou implicites.

Rentrez une expression mathématique : 2-(-4)

2.000000-(-4.000000)

                       --> 4.000000
                     |
           --> '-' --|
          |
--> '-' --|
          |
           --> 2.000000

Résultat : 6.000000

Rentrez une expression mathématique : ---4

---4.000000

                                    --> 4.000000
                                  |
                        --> '-' --|
                      |
            --> '-' --|
          |
--> '-' --|

Résultat : -4.000000

sin(cos(-{sin[cos(2.000000)]}))

                                                            --> 2.000000
                                                          |
                                                --> cos --|
                                              |
                                    --> sin --|
                                  |
                        --> '-' --|
                      |
            --> cos --|
          |
--> sin --|

Résultat : 0.795239

Rentrez une expression mathématique : sin(cos-sincos2)

sin(cos-sincos2.000000)

                                                            --> 2.000000
                                                          |
                                                --> cos --|
                                              |
                                    --> sin --|
                                  |
                        --> '-' --|
                      |
            --> cos --|
          |
--> sin --|

Résultat : 0.795239

5.2.8. Erreurs d'évaluation

Rentrez une expression mathématique : 5^.5/sin(9-3^2)

5.000000^0.500000/sin(9.000000-3.000000^2.000000)

                                             --> 2.000000
                                            |
                                  --> '^' --|
                                            |
                                             --> 3.000000
                                 |
                       --> '-' --|
                                 |
                                  --> 9.000000
                     |
           --> sin --|
          |
--> '/' --|
          |
                      --> 0.500000
                     |
           --> '^' --|
                     |
                      --> 5.000000

Division par zéro
Rentrez une expression mathématique : 2%[3/2]

2.000000%[3.000000/2.000000]

                      --> 2.000000
                     |
           --> '/' --|
                     |
                      --> 3.000000
          |
--> '%' --|
          |
           --> 2.000000

Arguments de la division euclidienne non entiers

Rentrez une expression mathématique : -9%sin0

-9.000000%sin0.000000

                       --> 0.000000
                     |
           --> sin --|
          |
--> '%' --|
          |
                       --> 9.000000
                     |
           --> '-' --|

Division euclidienne par zéro

Rentrez une expression mathématique : (-3/2)^-2.5

(-3.000000/2.000000)^-2.500000

                       --> 2.500000
                     |
           --> '-' --|
          |
--> '^' --|
          |
                      --> 2.000000
                     |
           --> '/' --|
                     |
                                  --> 3.000000
                                |
                      --> '-' --|

Arguments de l'exponentiation non valides

Rentrez une expression mathématique : 0^-5

0.000000^-5.000000

                       --> 5.000000
                     |
           --> '-' --|
          |
--> '^' --|
          |
           --> 0.000000

Arguments de l'exponentiation non valides

5.2.9. Limite de calcul

La calculatrice arrondit les expressions et peut indiquer +inf ou -inf.

Rentrez une expression mathématique : 10^100000

10.000000^100000.000000

           --> 100000.000000
          |
--> '^' --|
          |
           --> 10.000000

Résultat : inf

Rentrez une expression mathématique : cos2-[cos1+sin1]*[cos1-sin1]

cos2.000000-[cos1.000000+sin1.000000]*[cos1.000000-sin1.000000]

                                             --> 1.000000
                                           |
                                 --> sin --|
                                |
                      --> '-' --|
                                |
                                             --> 1.000000
                                           |
                                 --> cos --|
                     |
           --> '*' --|
                     |
                                             --> 1.000000
                                           |
                                 --> sin --|
                                |
                      --> '+' --|
                                |
                                             --> 1.000000
                                           |
                                 --> cos --|
          |
--> '-' --|
          |
                       --> 2.000000
                     |
           --> cos --|

Résultat : -0.000000

5.2.10. Autres exemples d'expression

Rentrez une expression mathématique : 2-((cos8)+7)-{sin(3/2)*5}+9

2.000000-((cos8.000000)+7.000000)-{sin(3.000000/2.000000)*5.000000}+9.000000

           --> 9.000000
          |
--> '+' --|
          |
                                 --> 5.000000
                                |
                      --> '*' --|
                                |
                                                        --> 2.000000
                                                       |
                                             --> '/' --|
                                                       |
                                                        --> 3.000000
                                           |
                                 --> sin --|
                     |
           --> '-' --|
                     |
                                            --> 7.000000
                                           |
                                 --> '+' --|
                                           |
                                                        --> 8.000000
                                                      |
                                            --> cos --|
                                |
                      --> '-' --|
                                |
                                 --> 2.000000

Résultat : -0.841975

Rentrez une expression mathématique : 9%4+2/5^7*3-1

9.000000%4.000000+2.000000/5.000000^7.000000*3.000000-1.000000

                      --> 1.000000
                     |
           --> '-' --|
                     |
                                 --> 3.000000
                                |
                      --> '*' --|
                                |
                                                       --> 7.000000
                                                      |
                                            --> '^' --|
                                                      |
                                                       --> 5.000000
                                           |
                                 --> '/' --|
                                           |
                                            --> 2.000000
          |
--> '+' --|
          |
                      --> 4.000000
                     |
           --> '%' --|
                     |
                      --> 9.000000

Résultat : 0.000077

Rentrez une expression mathématique : cos(3.1415926535/2)

cos(3.141593/2.000000)

                       --> 2.000000
                      |
            --> '/' --|
                      |
                       --> 3.141593
          |
--> cos --|

Résultat : 0.000000

6. Conclusion

L'objectif du projet était de réaliser une calculatrice scientifique fonctionnant selon deux modes : un solveur d'équation du second degré et un évaluateur d'expression mathématique. La calculatrice devait fonctionner de façon purement textuel. Les consignes de l'énoncé ont été assez bien respectées, sauf pour la structure de equation2d qui vérifie et interprète l'expression simultanément, pour les priorités des opérateurs binaires et pour le codage des opérateurs unaires.

Dans une première partie nous avons mis en place le menu et défini la structure générale du programme et de ses deux sous-programmes. Nous avons aussi donner des définitions de fonctions élémentaires pour gérer les erreurs d'allocation mémoire ou afficher des erreurs de saisie. Nous avons étudié la gestion des saisies et ajouter une option dans le menu permettant de paramètrer la longueur des chaines de caractère saisies. Enfin, nous avons présenter des fonctions d'analyse de saisie, notamment une fonction d'interprétation de nombre décimaux.

Nous nous sommes ensuite interessés au premier mode de fonctionnement : le solveur d'équation. La calculette découpe l'expression saisie en deux membres et les converti en liste chainée de termes. Elle met l'expression sous forme réduite en regroupant les termes de même degré et en ne gardant que les termes dont le coefficient multiplicatif est non nul. Une fois ces opérations effectuées, elle vérifie que le degré de l'équation est correcte et donne les solutions réelles, s'ils en existent.

Le mode évaluation d'expression a été étudié dans la troisième partie. Cette fois-ci, la calculatrice vérifie que l'expression est bien formée avant de la convertir en liste chainée, puis en arbre binaire. Elle affiche alors l'arbre de la formule à l'horizontal et donne le résultat de l'expression ou une erreur si une opération n'est pas permise. La calculatrice est capable d'interpréter les délimiteurs et opérateurs du sujet, mais son implantation a été réalisée de manière à ce que l'on puisse facilement ajouter de nouveaux opérateurs.

La dernière partie comporte différents tests, aussi bien pour le solveur que pour l'interpréteur. Un grand nombre d'exemples y sont donnés, à la fois pour les détections d'erreurs, l'interprétation d'expressions conformes à ceux de la consigne ou encore des caractéristiques de fonctionnement propre à l'implantation mis en oeuvre.

7. Annexes

Le code source est disponible en annexe.

Cette page est conforme aux normes du W3C - Auteur : Frédéric WANG - Dernière mise à jour : samedi 13 septembre 2008
Valid XHTML 1.1 Valid MathML 2.0 Valid SVG Valid CSS Amaya, the W3C browser/editor Firefox