Introduction à l`informatique – Mini
Transcription
Introduction à l`informatique – Mini
Introduction à l’informatique – Mini-projet Outil de manipulation de polynômes – Corrigé Septembre 2005 Avant-propos important Ce document présente une solution possible, ce n’est ni la seule, ni la meilleure. 1 Représentation informatique et la famille de ses Un polynôme est totalement défini (mathématiquement) par son degré coefficients . On a donc tout intérêt à choisir une représentation informatique proche, à savoir qu’un polynôme informatique sera défini par : – une variable entière (de type int) représentant son degré ; – une variable de type tableau de réels (de type double), telle que soit le premier élément du tableau, le second. . . Notons tout de suite quelques points importants de notre modélisation : – on rappelle que le degré est l’entier minimal au-dessus duquel les termes sont nuls ; on a donc, par définition, ! Notre modèle informatique doit garantir la même contrainte ; – on représente le polynôme nul par son seul degré, pris par convention égal à (attention : mathématiquement parlant, le degré d’un polynôme nul est !) ; – le type int ne permet pas de coder un degré quelconque, mais seulement les degrés inférieurs ou égaux à 2147483647; – comme nous ne savons pas utiliser des tableaux de taille variable, on utilisera des tableaux d’une taille fixée par une macro du langage C, le degré maximum étant donc égal à cette taille maximale moins 1 ; il faudra prendre garde lors de certaines manipulations de polynômes à ne pas dépasser cette limite ! On pourra ainsi utiliser les définitions suivantes : #define DEGRE_MAX 31 typedef double polynome[DEGRE_MAX+1]; ... polynome P; /* P est un polynome */ int p; /* p est le degre du polynome P */ ... /* on definit P = X^2 - 2 X + 1 */ p=2; P[0]=1.; P[1]=-2.; P[2]=1; ... 1 Par la suite, nous utiliserons toujours la convention de désigner un polynôme par un identificateur en majuscules, le degré du polynôme étant désigné par le même identificateur en minuscules. 2 Affichage d’un polynôme L’énoncé indique que l’on souhaite un affichage «joli», du type : P = 3 X^4 + X^2 - 1.2 X + 4 ; que l’on peut décomposer en : <nom>= <monôme de plus haut degré><autres monômes>; La particularité du monôme de plus haut degré est que l’on n’affiche pas le signe + si le terme est positif. 2.1 Nom Il n’existe aucun moyen en langage C pour obtenir une chaîne de caractères représentant le nom d’une variable. Le «nom» du polynôme doit donc être précisé par le programmeur, qui doit donc prendre garde à ne pas se tromper ! Ceci permet déjà d’écrire le prototype de la fonction d’affichage : void affiche_polynome (char *nom,int p,polynome P); ... affiche_polynome("P",p,P); affiche_polynome("Q",q,Q); ... 2.2 Affichage des monômes L’affichage de chacun des monômes (par degré décroissant) s’effectue comme suit : – si le coefficient est nul, on n’affiche rien ! – si le coefficient est strictement négatif, on affiche un signe - ; sinon, si ce n’est pas le monôme de plus haut degré, on affiche un signe + ; – si la valeur absolue du coefficient n’est pas égale à 1 ou bien si c’est le monôme de degré 0, on affiche cette valeur absolue ; – si le degré du monôme est supérieur ou égal à 2, on affiche X^ suivi du degré ; sinon, si le degré du monôme est égal à 1, on affiche X ; Afin d’aérer un peu l’affichage, on peut ajouter un blanc avant le signe, avant la valeur absolue du coefficient, avant l’affichage du X. En ce qui concerne l’affichage de la valeur, on utilise la fonction de bibliothèque printf, en utilisant le format %g, qui réalise les simplifications voulues : affichage des entiers sous forme entière, suppression des 0 inutiles en fin de partie décimale. 2.3 Cas du polynôme nul Si l’on exécute l’algorithme précédent sur le polynôme nul, on constate que rien n’est affiché ! Il faut donc traiter ce cas particulier, par exemple en testant a priori si l’on affiche le polynôme nul. 2 2.4 Un codage possible Voici donc un codage possible de cette fonction : void affiche_polynome(char *nom,int p,polynome P) { int i; double v; (void) printf("%s =",nom); if (p==-1) { (void) printf(" 0 ;\n"); return; } for (i=p;i>=0;i--) { if (P[i]==0) continue; if (P[i] < 0) { v=-P[i]; (void) printf(" -"); } else { v=P[i]; if (i!=p) (void) printf(" +"); } if (v!=1. || i==0) (void) printf(" %g",v); if (i > 1) (void) printf(" X^%d",i); else if (i==1) (void) printf(" X"); } (void) printf(" ;\n"); } 3 Calcul de la fonction polynômiale en un point On veut donc calculer, pour donné : Comme on ne dispose pas en C d’opérateur d’exponentiation, on peut calculer les qui donne un premier codage possible : double eval_polynome (double x,int p,polynome P) { double r,x_i; int i; for (i=0,r=0.,x_i=1.;i<=p;i++,x_i*=x) r+=(P[i]*x_i); return r; } 3 de façon itérative, ce On peut gagner un peu en complexité (plus précisément, on peut diviser le nombre de multiplications par 2) en remarquant que l’on peut aussi écrire (schéma de Horner): ce que l’on peut coder en : double eval_polynome (double x,int p,polynome P) { double r; int i; for (i=p,r=0.;i>=0;i--) r=P[i]+x*r; return r; } 4 Dérivation d’un polynôme Pas vraiment de problème pour cette procédure, il faut simplement faire attention : – au cas particulier du polynôme nul ; – aux paramètres de la procédure, comprenant le polynôme initial et son degré (paramètres d’entrée), ainsi que le polynôme dérivé (paramètre de sortie), le degré du polynôme dérivé étant le résultat de la procédure. ce qui peut se coder par : int derive_polynome (int p,polynome P,polynome Q) { int i; if (p==-1) return -1; for (i=p;i>=1;i--) Q[i-1]=i*P[i]; return p-1; } 5 Addition de deux polynômes Cette fonction n’est pas aussi simple qu’elle paraît. . . En effet, il faut prendre garde aux points suivants : – on ne peut pas déterminer a priori le degré du polynôme résultat, car certains coefficients peuvent se compenser ; – on doit faire attention aux termes que l’on somme si les degrés sont différents ! en effet, les termes d’indice strictement supérieur au degré n’ont pas de valeur définie (ne surtout pas penser qu’ils sont «automatiquement» nuls !) ; Voici un codage possible pour cette fonction d’addition, qui admet comme paramètres les deux polynômes à ajouter et leurs degrés, ainsi que le polynôme résultat : le degré de ce résultat est rendu comme résultat de la procédure. int ajoute_polynomes (int p,polynome P,int q,polynome Q, 4 polynome A) { int i; if (p < q) { for (i=0;i<=p;i++) A[i]=P[i]+Q[i]; for (;i<=q;i++) A[i]=Q[i]; return q; } if (q < p) { for (i=0;i<=q;i++) A[i]=P[i]+Q[i]; for (;i<=p;i++) A[i]=P[i]; return p; } /* on a p=q, on a donc, pour tout i, A[i]=P[i]+Q[i] */ for (i=0;i<=p;i++) A[i]=P[i]+Q[i]; /* mais on doit verifier la valeur du degre !!! */ for (i=p;i>=0;i--) if (A[i]!=0) return i; return -1; } Les 4 dernières lignes de code servent à recalculer le degré du polynôme résultat si certains termes s’annulent. Remarque 1 : la fonction écrite ci-dessus est mathématiquement correcte ; il y a cependant fort à parier que si l’on multiplie le nombre de manipulations, on va finir par générer des coefficients presque nuls, c’est-à-dire qu’un calcul exact aurait donné un résultat nul, mais que les approximations de l’ordinateur ont généré des valeurs très petites, mais non nulles. Il est assez difficile de rendre les programmes robustes à ce genre d’imprécisions... Remarque 2 : si est le polynôme nul (c’est-à-dire si p vaut -1), on obtient une fonction de copie du polynôme Q dans le polynôme A ! Nous utiliserons cette fonctionnalité ultérieurement. 6 Multiplication de deux polynômes Si l’on note U le polynôme produit des deux polynômes P et Q non nuls, on sait que l’on a les relations suivantes : On doit donc tout d’abord faire attention au fait que, comme nous utilisons des polynômes de degré borné, on n’est pas toujours capable de calculer un résultat ! Il faut donc, dans ce cas, déterminer ce que l’on fait : retourner un résultat nul, afficher un message d’erreur, arrêter le programme. . . En supposant que l’on puisse calculer le résultat, il faut faire attention, là encore, à la gestion des indices 5 de tableau. En effet, les indices et doivent vérifier les conditions suivantes : soit encore : soit encore : soit enfin, en regroupant les deux inégalités : On obtient donc la formule permettant de calculer : Il est alors facile de réaliser le codage de la procédure de multiplication: int multiplie_polynomes (int p,polynome P,int q,polynome Q, polynome U) { int i,k,imax,u; if (p==-1 || q==-1) return -1; u=p+q; if (u > DEGRE_MAX) return -1; for (k=0;k<=u;k++) { i=((k < q) ? 0 : (k-q)); imax=((p < k) ? p : k); for (U[k]=0;i<=imax;i++) U[k]+=P[i]*Q[k-i]; } return u; } Remarque 1 : on a utilisé l’expression conditionnelle du langage C, dont la forme est exp1 ? exp2 : exp3 qui est évaluée comme suit : – on évalue l’expression exp1 ; si elle est vraie (non nulle), alors le résultat de l’expression conditionnelle est le résultat de l’évaluation de exp2 ; – sinon, le résultat de l’expression conditionnelle est le résultat de l’évaluation de exp3. 6 Remarque 2 : encore une fois, si la procédure est mathématiquement correcte, elle peut présenter des défauts ; en effet, il est possible que le produit (informatique) de deux termes non nuls soit nul (mécanisme d’underflow) ; il faudrait, encore une fois, adapter nos procédures à ce problème, ce qui est loin d’être trivial. 7 Polynôme défini par ses racines On suppose connues les racines nôme est unique) recherché est : , on sait que le polynôme (vu les contraintes, ce poly cette propriété est vraie que les racines soient deux à deux distinctes (simples) ou éventuellement égales (multiples). On voit donc qu’il est très simple de coder cette fonction à partir de celle réalisant le produit de deux polynômes ! Il suffit de calculer par itération le polynôme c’est-à-dire : On a besoin pour réaliser ce calcul d’une variable temporaire de type polynôme T, et voici l’algorithme de calcul de P : définir pour i variant de 1 à n-1 faire définir calculer recopier dans finpour On obtient alors le codage suivant : int polynome_racines (int n,double R[],polynome P) { int p,e,t,i; polynome E,T; if (n<=0) return -1; /* on definit P_0 = X - R[0] */ P[0]=-R[0];P[1]=1.; p=1; for (i=1;i<n;i++) { /* E = X - R[i] */ E[0]=-R[i];E[1]=1.; e=1; /* on calcule T = P x E */ t=multiplie_polynomes(p,P,e,E,T); /* on recopie T dans P */ p=ajoute_polynomes(-1,0,t,T,P); } 7 return p; } La procédure retourne comme résultat le degré du polynôme calculé. On pourrait améliorer (un peu) la complexité de cette procédure en sortant de la boucle les deux instructions E[1]=1; et e=1; (elles ne dépendent pas de l’indice de boucle). 8 Polynôme défini par des valeurs Soient réels deux à deux distincts, l’unique polynôme de degré inférieur ou égal à tel que, pour tout (dit de Bernstein) : réels, alors on sait que , est le polynôme On retrouve dans cette expression l’utilisation des procédures d’addition et de multiplication. Voici un codage possible de cette procédure : int polynome_bernstein (int n,double X[],double Y[],polynome B) { int i,j,b,p,e,t; polynome P,E,T; if (n<=0 || n > DEGRE_MAX) return -1; /* on initialise B = 0 */ b=-1; for (i=0;i<=n;i++) { if (Y[i]==0) continue; /* on initialise P = y_i */ P[0]=Y[i]; p=0; for (j=0;j<=n;j++) { if (j==i) continue; /* on definit E = (X-x_j)/(x_i-x_j) */ E[0]=-X[j]/(X[i]-X[j]); E[1]=1./(X[i]-X[j]); e=1; /* on calcule T = P x E */ t=multiplie_polynomes(p,P,e,E,T); /* on recopie T dans P */ p=ajoute_polynomes(-1,0,t,T,P); } /* on calcule T = B + P */ t=ajoute_polynomes(b,B,p,P,T); /* on recopie T dans B */ b=ajoute_polynomes(-1,0,t,T,B); } return b; } 8 9 Recherche d’une racine d’un polynôme L’énoncé précise simplement : rechercher une racine d’un polynôme sur un intervalle fait deux sous-problèmes : – déterminer si le polynôme s’annule sur . Il y a en ; – si oui, rechercher une racine. 9.1 Existence d’une racine Nous allons proposons une solution approchée du premier problème. Elle consiste à évaluer le poly points régulièrement espacés de l’intervalle, et à calculer le minimum et le maximum de ces nôme en évaluations : si le minimum est négatif ou nul et le maximum positif ou nul, alors on peut affirmer que le polynôme a au moins une racine sur l’intervalle. En supplément, la procédure calculera les abscisses pour lesquelles le minimum et le maximum (approchés !) sont atteints. Voici un codage possible pour cette procédure : #define NMAX 40 int minmax_polynome (int p,polynome P,double a,double b, double *min,double *max,double *xmin,double *xmax) { int i; double x,Px,delta; Px=eval_polynome(a,p,P); *min=*max=Px; *xmin=*xmax=a; delta=(b-a)/NMAX; for (i=1;i<=NMAX;i++) { x=a+i*delta; Px=eval_polynome(x,p,P); if (Px < *min) { *min=Px; *xmin=x; } else if (Px > *max) { *max=Px; *xmax=x; } } return (*min <= 0) && (*max >= 0); } Remarque 1 : on a utilisé des paramètres de la procédure de type «adresse de double», car la procédure doit calculer 4 valeurs, et il n’est pas possible que cette procédure rende comme résultat ces 4 valeurs ; c’est donc à la procédure appelante de fournir les 4 adresses mémoire où stocker ces valeurs. Remarque 2 : la procédure rend un résultat qui est vrai (non nul) si le polynôme s’annule, et faux (nul) sinon. Encore une fois, ceci est soumis à d’éventuels erreurs de précision. 9 9.2 Calcul d’une racine Une fois la procédure précédente définie, il est assez simple de programmer le calcul d’une racine. On commence par calculer le minimum et le maximum du polynôme sur l’intervalle : – si le polynôme s’annule, on connaît deux abscisses où le polynôme prend des valeurs de signes différents, et il suffit de lancer une procédure de recherche (dichotomie, sécante) ; – sinon, on conclut qu’il n’y a pas de racine sur l’intervalle donné (ce n’est pas vrai mathématiquement parlant, mais il est hors de propos de rechercher le résultat mathématique exact). Voici un codage possible utilisant une méthode de dichotomie : #define EPSI (1.e-6) int cherche_racine (int p,polynome P,double a,double b,double *r) { double min,max,xmin,xmax,x,Px; if (minmax_polynome(p,P,a,b,&min,&max,&xmin,&xmax)==0) return 0; for (;;) { x=(xmin+xmax)/2.; Px=eval_polynome(x,p,P); if ((fabs(Px) < EPSI) || (fabs(xmin-xmax) < EPSI)) { *r=x; return 1; } if (Px < 0) xmin=x; else xmax=x; } } Voici une version utilisant une méthode de la sécante : #define EPSI (1.e-6) int cherche_racine (int p,polynome P,double a,double b,double *r) { double min,max,xmin,xmax,x,Px; if (minmax_polynome(p,P,a,b,&min,&max,&xmin,&xmax)==0) return 0; for (;;) { x=(min*xmax-max*xmin)/(min-max); Px=eval_polynome(x,p,P); if ((fabs(Px) < EPSI) || (fabs(xmin-xmax) < EPSI)) { *r=x; return 1; } if (Px < 0) { min=Px; xmin=x; } 10 else { max=Px; xmax=x; } } } Question subsidiaire : et que se passe-t-il si le polynôme est nul? 10 Représentation graphique d’un polynôme Le programme d’exemple donné permet de : – créer une fenêtre graphique de taille donnée, permettant de représenter un pavé de ; – tracer des segments de droites dont les coordonnées des extrémités sont dans le pavé de ci-dessus ; – écrire du texte ; Ceci est suffisant pour notre procédure graphique. Celle-ci peut par exemple : – calculer les extrémums du polynôme sur le segment (grâce à la procédure minmax_polynome), ce qui permet de calculer le pavé voulu pour la fenêtre graphique ; – tracer la courbe représentant le polynôme par une succession de segments (on peut utiliser un nombre de segments identique à celui utilisé pour la procédure minmax_polynome) ; – si le polynôme s’annule, rechercher une racine, tracer une droite verticale à l’abscisse correspondate, une droite horizontale à l’ordonnée nulle ; Un petit point de détail : si le polynôme est constant, le minimum et le maximum sont identiques. . . et la fonction init_window fournie en exemple plante ! int plot_polynome (int p,polynome P,char *nom, double a,double b) { int i,winid,ya_racine; double x,y,delta,xmin,xmax,min,max,rac,xrac,yrac; char buf[1024]; /* on evalue le minmax du polynome sur l’intervalle */ ya_racine=minmax_polynome(p,P,a,b,&min,&max,&xmin,&xmax); /* si le polynome est constant, on utilise une fenetre */ /* d’affichage a +-10 par rapport a la valeur ; sinon, */ /* on utilise une fenetre allant du min au max */ if ((max-min) < EPSI) winid=init_window(nom,800,600,a,b,min-10,max+10); else winid=init_window(nom,800,600,a,b,min,max); if (winid==-1) return -1; /* on passe en ’backbuffer’, on efface tout (en blanc) */ (void) backbuffer(); color(WHITE);clear(); /* on trace la courbe de la fonction polynomiale : une */ /* succession de NMAX segments */ 11 x=a; y=eval_polynome(x,p,P); move2(x,y); color(BLUE); delta=(b-a)/NMAX; for (i=1;i <= NMAX;i++) { x=a+delta*i; y=eval_polynome(x,p,P); draw2(x,y); } /* si le polynome a au moins une racine sur [a;b], on /* trace une ligne rouge horizontale a y=0, puis on /* cherche une racine ; on trace une ligne verticale /* a l’abscisse de cette racine if (ya_racine) { color(RED); move2(a,0); draw2(b,0); if (cherche_racine(p,P,a,b,&rac)==0) (void) fprintf(stderr,"Y’a un bug !!\n"); else { move2(rac,min); draw2(rac,max); if (2*rac < (a+b)) { xrac=rac+0.01*(b-a); leftjustify(); } else { xrac=rac-0.01*(b-a); rightjustify(); } if (0 < (min+max)) yrac=0.1*min+0.9*max; else yrac=0.9*min+0.1*max; (void) sprintf(buf,"x=%g",rac); font("small"); move2(xrac,yrac); drawstr(buf); } } font("large"); leftjustify(); move2(a+0.1*(b-a),(min+max)/2.); color(BLACK); drawstr(nom); swapbuffers(); if (getkey()==’q’) { windel(winid); return -1; } return winid; } 12 */ */ */ */ Question subsidiaire : comment sont positionnés les textes affichés dans la fenêtre graphique? 11 Le programme principal En respectant les indications de l’énoncé, le programme principal enchaîne les opérations demandées. ) et les 7 variables Il utilise 7 variables de type «polynôme» (S sert à représenter le polynôme associées pour les degrés, un tableau de racines rac permettant de définir le polynôme U, deux tableaux de valeurs x et y permettant de définir le polynôme V. int main (int argc,char *argv[]) { polynome P,Q,R,S,T,U,V; int p,q,r,s,t,u,v; double rac[4],x[4],y[4],racine; /* on definit P = X^4 - X^2 + X - 1 et on l’affiche */ p=4;P[0]=-1;P[1]=1;P[2]=-1;P[3]=0;P[4]=1; affiche_polynome("P",p,P); /* on calcule et on affiche P(2) */ (void) printf("P(2)=%g\n",eval_polynome(2,p,P)); /* on calcule Q=P’ et on affiche Q */ q=derive_polynome(p,P,Q); affiche_polynome("Q",q,Q); /* on calcule R=P+Q et on affiche R */ r=ajoute_polynomes(p,P,q,Q,R); affiche_polynome("R",r,R); /* on definit S=X^2+X-1, on calcule T=SR, on affiche T */ s=2;S[0]=-1;S[1]=1;S[2]=1; t=multiplie_polynomes(s,S,r,R,T); affiche_polynome("T",t,T); /* on recherche une racine de T sur [-2;2] */ if (cherche_racine(t,T,-2,2,&racine)) printf("T a pour racine %g dans [-2;2]\n",racine); else printf("T n’a pas de racine dans [-2;2] ??\n"); /* on calcule U defini par ses racines -1,0,2,3 */ rac[0]=-1;rac[1]=0;rac[2]=2;rac[3]=3; u=polynome_racines(4,rac,U); affiche_polynome("U",u,U); plot_polynome(u,U,"U",-2,4); /* on calcule V tel que V(-1)=2, V(0)=-1, V(1)=2, V(2)=1 */ x[0]=-1;y[0]=2; x[1]=0;y[1]=-1; x[2]=1;y[2]=2; x[3]=2;y[3]=1; v=polynome_bernstein(3,x,y,V); affiche_polynome("V",v,V); plot_polynome(v,V,"V",-2,3); return 0; } 13