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

Documents pareils