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.
En suivant la spécification fournie avec le sujet, nous obtenons l'organisation suivante pour le programme :
main qui va permettre à
l'utilisateur de choisir un des deux sous-programmes ci-dessous ou de
quitter le programme.equation2d et
evaluerExpression qui se chargent des deux modes de
fonctionnement de la calculatrice.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
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.
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.
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.
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 :
+ ou - et
saute les espaces suivants.." 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.
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 :
equation2d : la fonction principale de
trinome.c. Son organisation générale a été décrite plus
haut dans le paragraphe "Structure des
sous-programmes".lireEquation : demande à l'utilisateur de saisir
l'équation. Il s'agit simplement d'une fonction appelant
saisir_chaine.verifierEquation et resoudreEquation et
afficherSolution : ces fonctions n'ont pas été réalisées
car leurs opérations sont réalisées plus ou moins simultanément.
Elles sont directement réalisée dans la fonction
equation2d.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.
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 où est un nombre décimal et est un entier.
typedef struct terme
{
double coefficient;
int degre;
struct terme *suivant;
} terme;
typedef terme* somme;
Par exemple sera représenté par une liste chainée à 3 éléments :
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.
*s.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.sauterEspace pour ignorer d'éventuels
caractères blancs.+ 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 :
interpreterNombre* et saute des espaces dans ce cas.X ou x. Si il est
absent et qu'aucun coefficient n'a été lu, retourne FAUX.
Sinon retourne VRAI.^ . Si il est présent, lit
obligatoirement un nombre. Sinon, vérifie un nombre seulement si un
chiffre suit le X.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 :
(espace)*(coefficient)(espace)*[(multiplié)(espace)*] par
exemple -2.7(espace)*[coefficient](espace)*[(multiplié)(espace)*]{X,x}(puissance)(degre)
par exemple X ou 2*x ou 4X^+2 ou
X^2.000.(espace)*[coefficient](espace)*[(multiplié)(espace)*]{X,x}(degre_bis)
par exemple X2 ou 3*X7 ou 8X3.où l'on a noté :
coefficient = expression régulière acceptée par
interpreterNombre.degre = expression régulière acceptée par
interpreterNombre.degre_bis = expression régulière acceptée par
interpreterNombre, mais commençant forcément par un
chiffre.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 de l'équation . 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.
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 :
evaluerExpression : la fonction principale de
evaluation.c. Son organisation générale a été décrite
plus haut dans le paragraphe "Structure des
sous-programmes".lireExpression : demande à l'utilisateur de saisir
l'expression. Elle effectue un appel à saisir_chaine.verifierExpression : vérifie la syntaxe et affiche un
message en cas d'erreur.expressionToListe : transforme la chaine saisie en
listelisteToArbre : transforme la liste en arbreafficherArbre : affiche l'arbre sous forme textuel.evaluerArbre : évalue la valeur de l'expression à partir
de l'arbre.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.
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] = {')','}',']'};
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 :
.", on analyse un
nombre. Il ne faut pas qu'il soit précédé d'un autre nombre ou d'une
parenthèse fermante. On déplace se déplace après le nombre.ouvertes. Il ne faut pas qu'il soit précédé d'un
séparateur fermant ou d'un nombre.{}) ou d'un
opérateur (qui attend alors un opérande). On vérifie aussi qu'un
séparateur du même type avait déjà été ouvert.operateurs_unaires. Il ne faut pas qu'il soit précédé
d'un nombre ou d'un séparateur fermant.operateurs,
deux cas se présentent. Si c'est un '-', on qu'il est
précédé d'un opérateur unaire, d'un opérateur binaire, d'un
séparateur ouvrant ou si l'on est au début de la chaine, alors on
l'interprète comme un opérateur unaire. Dans le cas contraire, le
symbole est interprété comme un opérateur binaire et les cas cités
précédemment sont justement ceux qui sont refusés.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.
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 :
debut est NULL, on retourne un arbre
vide.listeToArbre sur le fragment précédent l'opérateur et
pour fils droit le résultat sur le fragment suivant
l'opérateur.NULL et pour fils droit le
résultat de listeToArbre appliquée au reste de la
liste. Notons que le '-' peut être interprété comme
un opérateur unaire et on obtient donc un résultat différent de
celui préconisé par le sujet (un 0 pour fils gauche).(...) : on ignore
les séparateurs et on appelle listeToArbre sur ce qu'il
contient.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.
É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
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
. 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 :
/%%^. Ceci se produit pour si et ou alors si et .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 .
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
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
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
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
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
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.
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.
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
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
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.
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
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
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
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
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
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éroRentrez 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
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
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
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.
Le code source est disponible en annexe.