Exemple du sinus (ch. 2)
Transcription
Exemple du sinus (ch. 2)
Cours C-5 1 Cours C-5 2 Quand et pourquoi programmer ? Exemple du sinus (ch. 2) Introduction au langage C #include <stdio.h> #include <stdlib.h> Cours 5 #include <math.h> int main(int argc, char * argv[]) { double U, u, x, epsilon ; int z, w, n, fini ; z=-2 ; w=0 ; n=0 ; fini=0 ; x=M PI/4 ; U=u=x ; epsilon=1e-8 ; while( !fini) { z=z+8 ; w=w+z ; n++ ; u=-u*x*x/(double)w ; U=U+u ; fini = n>fabs(x)/2 && fabs(u)<epsilon ; } printf("sin(%.12g)=%.12g [%d]\n",x,U,n) ; exit(0) ; Du problème au programme Jean-Jacques Girardot / Marc Roelens Septembre 2007 } Girardot/Roelens Septembre 2007 Cours C-5 3 Girardot/Roelens Septembre 2007 Cours C-5 4 Autres approches du sinus Le calcul « brutal » Une autre méthode s = x; t = 1.0 ; i = 1; do { /* Calcul du factoriel 2*i+1 */ j = 2*i+1 ; f = 1.0 ; while (j>0) { f = f * j ; j=j-1 ; } /* Calcul du terme courant */ t = pow(-1,i) * pow (x, 2*i+1) / f ; /* Sommation pour obtenir le sinus */ s = s+t ; i = i+1 ; } while (fabs(t) > epsilon) ; Girardot/Roelens ◦ Utilisation de la fonction prédéfinie sin s=sin(x) ; ◦ Comment comparer ces méthodes ? ! critère d’efficacité : quelle est la plus performante ? ! test de durée d’exécution ! justesse et robustesse ? ! test sur divers arguments • cas particulier, cas généraux ! coût de développement et d’utilisation. ! finalité ? Septembre 2007 Girardot/Roelens Septembre 2007 Cours C-5 5 Cours C-5 6 Tests de validation Test de performances ◦ Utilisation de la fonction shell « time » : #include <stdio.h> #include <math.h> #define NBT 10 #define EPS 1e-5 double tsts[NBT]= { 0.0, M PI/4, M PI/4+EPS, M PI/2, M PI-EPS, M PI, M PI+EPS, 2*M PI, -M PI, -1.0 } ; int main(int argc, char * argv[]) { int i ; double x, s ; for (i=0 ; i<NBT ; i++) { x = tsts[i] ; s = sin(x) ; printf("sin(%.12g)=%.12g\n", x, s) ; } return 0 ; } Girardot/Roelens [P]$ time syssin sin(0.785398163397)=0.70710678118 real 0m0.091s user 0m0.001s sys 0m0.002s [P]$ ◦ Pour rendre significatifs les chiffres oftenus : faire une boucle : for (k=0 ; k<1000 ; k++) { ...sin(x)... } ◦ Masquer les temps d’impression [P]$ time mysin >/dev/null ◦ Résultats (avec un facteur 107) : real user sys Septembre 2007 Cours C-5 [5] 7 0m2.159s 0m2.116s 0m0.003s Girardot/Roelens Septembre 2007 Cours C-5 8 Comparaison des 3 sinus ◦ Performances Calcul d’intervalle de temps 1. sinus brutal : 46.781s 2. sinus (ch. 2) : 6.529s Problème informatique typique : calculer l’intervalle de temps écoulé entre deux dates 3. sinus natif : 2.116s ◦ Que savons nous du problème ? ◦ Comment se ramener à des problèmes plus simples ? ! Unix (Linux) utilise le nombre de secondes écoulées depuis le premier janvier 1970 ! Idée : calculer le nombre de secondes écoulées depuis une date référence ! Prendre en compte les années bissextiles • algorithme de calcul des années bissextiles ? ! Durées des années pleines, puis de l’année courante ◦ Justesse et robustesse 1. sinus brutal : pb. algorithmiques 2. sinus (ch. 2) : pb. algorithmiques 3. sinus natif : résultats corrects ◦ Coûts de développement de l’application 1. sinus brutal : faible 2. sinus (ch. 2) : important 3. sinus natif : faible Girardot/Roelens Septembre 2007 Girardot/Roelens Septembre 2007 Cours C-5 9 Cours C-5 10 Procédure "bissextile" ◦ Utilisation de l’opérateur « modulo », % Décomposition du problème int bissextile(int an) { if (an % 400 == 0) return 1 ; if (an % 100 == 0) return 0 ; if (an % 4 == 0) return 1 ; return 0 ; } ◦ Notation : T(J,M,A,h,m,s) = durée écoulée entre le 1/1/1970 à 0h et l’instant courant. ! 1 ≤ J ≤ 31, 1 ≤ M ≤ 12, 1970 ≤ A ! 0 ≤ h ≤ 23, 0 ≤ m ≤ 59, 0 ≤ s ≤ 59 ◦ Besoins du calcul : ! procédure bissextile(A) ! une année est bissextile si elle est divisible par 4 ou 400, mais pas par 100. ! elle a, selon les cas, 365 ou 366 jours ! durée des années pleines : dépend de A seul ! durée des journées pleines de l’année courante : dépend de J, M et A ! nombre de secondes écoulées du jour courant : dépend de h, m, s. Girardot/Roelens ◦ Autre écriture (subtilement plus courte) : int bissextile(int an) { if (an % 4) return 0 ; if (an % 100) return 1 ; if (an % 400) return 0 ; return 1 ; } Septembre 2007 Cours C-5 11 Girardot/Roelens Cours C-5 Sous-problèmes (1) 12 Sous-problèmes (2) ◦ Durée des jours pleins : Durée des années pleines : int tddm[12] = { 31*24*60*60, 28*24*60*60, 30*24*60*60, 31*24*60*60, 31*24*60*60, 31*24*60*60, 31*24*60*60, 30*24*60*60, int dsmp(int an, int mois) { int m, s=0 ; for (m=1 ; m<mois ; m++) s = s + tddm[m-1] ; if (mois>2 && bissextile(an)) s = s + 24*60*60 ; return s ; } int dsap(int an) { int a, s=0 ; for (a=1970 ; a<an ; a++) { s = s + 365*24*60*60 ; if (bissextile(a)) s = s + 24*60*60 ; } return s ; } La durée des années pleines est directement calculée en secondes, en tenant compte des années bissextiles. Girardot/Roelens Septembre 2007 Septembre 2007 31*24*60*60, 30*24*60*60, 30*24*60*60, 31*24*60*60 } ; ◦ Le programme utilise une table des durées en secondes de chaque mois de l’année. ◦ Autre solution : c.f. cours, pp 81-82. Girardot/Roelens Septembre 2007 Cours C-5 13 Cours C-5 Lire sur le terminal Décoder une réponse d’un utilisateur ◦ lecture : procédure getchar() ◦ rend le premier caractère disponible sur le terminal (code ASCII), ou rend « EOF » en fin de fichier ◦ lecture : procédure getchar() ! fournit 1 caractère (son code ASCII), ou EOF (fin de fichier) ! les caractères sont disponibles quand l’utilisateur a fait « enter » ! le caractère « enter » est la fin de ligne « \n » ◦ notre procédure prend en entrée une question, attend une réponse (« o » ou « n »). ! « nettoyer » la fin d’entrée ◦ exemple d’utilisation : #include <stdio.h> #include <stdlib.h> int main(int argc, char * argv []) { int ch ; while ((ch = getchar()) != EOF) { printf("%d ",ch) ; if (ch == ’\n’) printf("\n") ; } exit(0) ; } abcd 97 98 99 100 10 012 48 49 50 10 int a=-1, b=-1, c=-1 ; a = ques("voulez-vous une autre question") ; if (a) { b = ques("une autre question") ; if (b) c = ques("une derniere question") ; } printf("vos reponses : %d %d %d\n", a, b, c) ; Girardot/Roelens Cours C-5 14 Septembre 2007 15 Girardot/Roelens Septembre 2007 Cours C-5 16 Calculer un nombre lu au terminal int ques(char msg[]) { int qes, ans, ch ; qes = 1 ; /* qes == faut-il reposer la question ? */ Problème : une valeur entière doit être introduite par l’utilisateur. Technique : on va lire l’entrée caractère par caractère, tant que ce sont des chiffres, et calculer ce nombre while (1) { if (qes) { printf("%s (O/N) : ", msg) ; qes = 0 ; } ch = getchar() ; if ((ch == ’o’) || (ch == ’O’)) { ans = 1 ; break ; } if ((ch == ’n’) || (ch == ’N’)) { ans = 0 ; break ; } if (ch == EOF) { ans = 0 ; break ; } if (ch == ’\n’) qes = 1 ; } #include <stdio.h> #include <stdlib.h> int main(int argc, char * argv []) { int ch, n=0 ; while (((ch = getchar()) >= ’0’) && (ch <= ’9’)) { n = (n*10)+(ch-’0’) ; } printf("%d\n", n) ; exit(0) ; } 2845a234 2845 while (((ch = getchar()) != ’\n’) && (ch != EOF)) ; return ans ; } Girardot/Roelens Septembre 2007 Girardot/Roelens Septembre 2007 Cours C-5 17 Cours C-5 18 Lecture d’un nombre flottant Remarque ◦ Le programme précédent a lu un caractère de « trop » : ’a’ dans l’exemple ◦ il peut être important de pouvoir utiliser ultérieurement ce caractère ◦ l’opération ungetc() permet de « rejeter » ce caractère, qui pourra ensuite être lu à nouveau ◦ prototype : ! int ungetc(int c, FILE *stream) ; ◦ formule magique : ! ungetc(ch, stdin) ; ◦ le résultat est « EOF » en cas d’échec Girardot/Roelens Septembre 2007 Cours C-5 19 int ch ; double n=0.0, f=1.0 ; while (((ch = getchar()) >= ’0’) && (ch <= ’9’)) { n = (n*10.0)+(ch-’0’) ; } if (ch != ’.’) { printf("Il manque le point decimal\n") ; exit(1) ; } while (((ch = getchar()) >= ’0’) && (ch <= ’9’)) { f = f/10.0 ; n = n + (ch-’0’)*f ; } ungetc(ch, stdin) ; printf("%g\n", n) ; exit(0) ; Girardot/Roelens Septembre 2007 Cours C-5 20 Décoder une durée ? Exemples de syntaxe ◦ 2h30 ◦ 12’30 ◦ 10h22’31 Signaler une erreur ? heure : facultative, marquée par la présence de la lettre h ; séparateur entre minutes et secondes : le caractère ’ Algorithme : 1. un entier est attendu, sinon c’est une erreur 2. si le caractère suivant est un h, aller en 3 ; si le caractère suivant est une quote aller en 5 ; sinon c’est une erreur 3. un entier est attendu, sinon c’est une erreur 4. si le caractère suivant est une quote, aller en 5 ; sinon aller en 6 5. un entier est attendu, sinon c’est une erreur 6. fin de l’algorithme Girardot/Roelens Septembre 2007 Une procédure spéciale, qui va écrire le message qui lui est transmis, et arrêter le programme ; par exemple : void erreur(char message[]) { printf("Erreur : %s\n", message) ; exit(1) ; } Girardot/Roelens Septembre 2007 Cours C-5 21 Cours C-5 { if (etape == 3) { m = lire entier() ; etape = 4 ; continue ; } if (etape == 4) { ch = getchar() ; if (ch == ’\”) etape = 5 ; else { ungetc(ch,stdin) ; continue ; } if (etape == 5) { s = lire entier() ; etape = 6 ; continue ; } if (etape == 6) { break ; } int etape = 1 ; int h, m, s ; int ch ; h = m = s = 0; for ( ; ;) { if (etape == 1) { h = lire entier() ; etape = 2 ; continue ; } if (etape == 2) { ch = getchar() ; if (ch == ’h’) etape = 3 ; else if (ch == ’\”) { m = h ; h = 0 ; etape = 5 ; } else erreur("separateur manquant") ; continue ; } Girardot/Roelens etape = 6 ; } } Septembre 2007 Cours C-5 22 23 Girardot/Roelens Septembre 2007 Cours C-5 24 Premier algorithme Autre exemple : Reines en prise sur un échiquier ◦ Représenter l’échiquier en numérotant les cases de 1 à 64 (de gauche à droite, puis de haut en bas) Placer 8 reines non en prise mutuelle sur un échiquier de taille 8 × 8. ◦ Approche du problème en force brute ! essayer toutes les combinaisons possibles ! 64 cases, 8 reines, soit 648 = 281474976710656, plus de 2 × 1014 combinaisons • note : 2 reines ne peuvent occuper la même case. . . ! définir une procédure qui indique si les reines sont en prise ◦ Question : ! comment représenter le problème ? ! comment s’assurer que l’on essaye toutes les combinaisons possibles, une fois et une seule ? Girardot/Roelens Septembre 2007 1 9 17 25 33 41 49 57 2 10 18 26 34 42 50 58 3 11 19 27 35 43 51 59 4 12 20 28 36 44 52 60 5 13 21 29 37 45 53 61 6 14 22 30 38 46 54 62 7 15 23 31 39 47 55 63 8 16 24 32 40 48 56 64 ◦ Essayer toutes les positions possibles avec une première reine sur la case 1, puis sur la case 2, etc, en plaçant toujours les reines suivantes sur une case postérieure à celle de la dernière reine placée. ◦ On peut se convaincre que toutes les positions possibles sont essayées une fois et une seule. Girardot/Roelens Septembre 2007 Cours C-5 25 Cours C-5 26 Calcul des prises Programmation : boucles imbriquées Plusieurs méthodes : ◦ représenter tous les couples de cases sur lesquelles deux reines sont en prise ; avec notre convention : #define FIRST 1 #define LAST 64 int r1, r2, r3, r4, r5, r6, r7, r8 ; ... for (r1=FIRST ; r1<=LAST-7 ; r1++) for (r2=r1+1 ; r2<=LAST-6 ; r2++) for (r3=r2+1 ; r3<=LAST-5 ; r3++) for (r4=r3+1 ; r4<=LAST-4 ; r4++) for (r5=r4+1 ; r5<=LAST-3 ; r5++) for (r6=r5+1 ; r6<=LAST-2 ; r6++) for (r7=r6+1 ; r7<=LAST-1 ; r7++) for (r8=r7+1 ; r8<=LAST ; r8++) ...corps de la boucle... {1,2} {1,3} {1,4} {1,5} {1,6} {1,7} {1,8} {1,9} {1,10} {1,17} {1,19} {1,25} {1,28}... ◦ pour chaque case, conserver tous les numéros des autres cases en prise depuis celleci : 1 :{2,3,4,5,6,7,8,9,10,17,19,25,28,33,37,41,46,49,55,57,64} 2 :{1,3,4,5,6,7,8,9,10,11,18,20,26,29,34,38,42,47,50,56,58} ... ◦ définir un tableau P de taille 64 × 64, où P [i][j] indique si des reines placées dans les cases i et j sont en prise ◦ recalculer systématiquement ces numéros (par exemple, à partir des numéros de ligne, de colonne, etc...) Note : il y a moins de 28 positions en prise par case, donc moins de 1792 positions à conserver Girardot/Roelens Septembre 2007 Cours C-5 27 Girardot/Roelens Cours C-5 28 Programmation Critique de l’approche ◦ Il y a un nombre considérable de positions à explorer, très précisément : 64 × 63 × 62 × 61 × 60 × 59 × 58 × 57 64! 8 C64 = = 8!56! 8×7×6×5×4×3×2×1 soit 4426165368, plus de 4 × 109 positions. ◦ on peut remarquer que l’on ne peut avoir plus d’une reine par ligne ; ceci réduit le nombre de positions à explorer à : 88 soit 16777216, moins de 2 × 107 positions ◦ nouvelle représentation : tableau de 8 entrées, position de chaque reine dans la ligne correspondante (par ex. de 0 à 7) ◦ énumérer les positions revient à énumérer toutes les configurations de : 0 0 0 0 0 0 0 0 à 7 7 7 7 7 7 7 7 (similitude avec un compteur kilométrique travaillant en octal. . .) Girardot/Roelens Septembre 2007 Septembre 2007 #define CS 8 #define CMIN 0 #define CMAX 7 int cincr(int ctr[]) { int i ; for (i=CS-1 ; i>=0 ; ) { if (++ctr[i] <= CMAX) return 1 ; ctr[i] = CMIN ; i-- ; } return 0 ; } Girardot/Roelens Septembre 2007 Cours C-5 29 Cours C-5 Test des positions Améliorations ? int reinestst(int pos[8]) { int i, j, k, d ; // vérifier le tableau des positions for (i=0 ; i<7 ; i++) { k = pos[i] ; for (j=i+1 ; j<8 ; j++) { // prise verticale ? if (k==pos[j]) return 0 ; d = j-i ; // prise en diagonale ? if (k+d== pos[j]) return 0 ; if (k== pos[j]+d) return 0 ; } } return 1 ; } Girardot/Roelens Le programme précédent fournit les solutions (au nombre de 92) en moins de 2 secondes ; on peut le juger satisfaisant. Il est parfois possible d’imaginer de nouveaux algorithmes. Ainsi : ◦ il ne peut y avoir 2 reines sur une même colonne ; ! les numéros de notre tableau sont donc tous différents ; ! les solutions sont donc des permutations des 8 premiers entiers. ◦ il n’y a donc que 8! = 40320, environ 4 × 104 configurations à explorer. . . ◦ . . .si l’on sait générer simplement l’ensemble des permutations ! une solution : 7 boucles imbriquées génèrent des indices de permutations que l’on applique au tableau [1, 2, 3, 4, 5, 6, 7, 8]. ! durée d’exécution : 0.013 secondes, contre 1.712 secondes Septembre 2007 Cours C-5 31 Girardot/Roelens Septembre 2007 Cours C-5 Le programme 32 Le MasterMind But : implanter un jeu de MasterMind ; le joueur doit deviner une configuration choisie (au sort) par l’ordinateur de m couleurs prises dans p disponibles int r0, r1, r2, r3, r4, r5, r6, i, k, pos[8] ; for (r0=0 ; r0<8 ; r0++) for (r1=1 ; r1<8 ; r1++) for (r2=2 ; r2<8 ; r2++) for (r3=3 ; r3<8 ; r3++) for (r4=4 ; r4<8 ; r4++) for (r5=5 ; r5<8 ; r5++) for (r6=6 ; r6<8 ; r6++) { for (i=0 ; i<8 ; i++) pos[i]=i+1 ; // permuter les positions 0 et r0, etc... k=pos[0] ;pos[0]=pos[r0] ;pos[r0]=k ; k=pos[1] ;pos[1]=pos[r1] ;pos[r1]=k ; k=pos[2] ;pos[2]=pos[r2] ;pos[r2]=k ; k=pos[3] ;pos[3]=pos[r3] ;pos[r3]=k ; k=pos[4] ;pos[4]=pos[r4] ;pos[r4]=k ; k=pos[5] ;pos[5]=pos[r5] ;pos[r5]=k ; k=pos[6] ;pos[6]=pos[r6] ;pos[r6]=k ; // pos contient la position à tester ... } Girardot/Roelens 30 ◦ Points spécifiques ! préciser le problème ! nombre limité de pions de chaque couleur ? • tirage avec/sans remise ! quelle interface avec l’utilisateur ? ! représentation (jeu, couleurs, réponses. . .) ! tirage au sort d’une configuration ? ! évaluation de la configuration proposée par l’utilisateur ? ◦ Pistes ! procédure rand() ! interface : aspect « lecture de données » non abordé ; on « lira » sur la ligne de commande Septembre 2007 Girardot/Roelens Septembre 2007 Cours C-5 33 Cours C-5 34 Programme C void tirage(int res[], int nr, int org[], int no) { int i, k ; for (i=0 ; i<nr ; i++) { k=rand()%no ; no-- ; res[i]=org[k] ; org[k]=org[no] ; org[no]=res[i] ; } } int main(int argc, char * argv[]) { int nbs[49], res[6], i ; for(i=0 ; i<49 ; i++) nbs[i]=i+1 ; tirage(res,6,nbs,49) ; } Exemple de tirage exhaustif Tirer n nombres parmi p. ◦ Algorithme : ! un tableau contient les p nombres ! on en tire 1 au sort rand()%p ! on le retire du tableau ? ! on l’échange avec le dernier ; ! il reste p − 1 nombres ; ! on réitère avec p − 1, etc. Girardot/Roelens Septembre 2007 Cours C-5 35 Girardot/Roelens Septembre 2007 Cours C-5 36 Exemples Avec le pid (numéro de processus) Remarques ◦ Chaque exécution du programme fournit le même résultat ! le générateur de nombre aléatoires (algorithmique) est toujours initialisé de la même façon ! but : permettre le test répétitif de programme ! la procédure srand() permet de modifier le point de départ de l’algorithme, donc la séquence de nombres obtenus ! utiliser srand() avec une valeur différente à chaque lancement de programme : date en seconde, numéro de processus, etc. ◦ La procédure tirage perturbe le contenu du tableau d’entrée (plusieurs solutions) ◦ La procédure peut être utilisée pour obtenir une permutation aléatoire #include <sys/types.h> #include <unistd.h> ... srand((unsigned int)getpid()) ; Avec l’heure #include <time.h> ... srand((unsigned int)time((time t *)0)) ; ◦ attention à la « formule magique » ◦ deux programmes lancés en même temps auront la même initialisation ◦ on peut « combiner » : srand((unsigned int)getpid()+(unsigned int)time((time t *)0)) ; Girardot/Roelens Septembre 2007 Girardot/Roelens Septembre 2007 Cours C-5 37 Cours C-5 38 Communications avec un programme Meilleurs nombres aléatoires Consulter les manuels de rand() et srand() ◦ Le processus utilisé génère une séquence (prévisible) de nombres répartis entre 0 et RAND MAX (valeur fournie par la machine) ◦ On utilise souvent rand()%N pour obtenir un nombre aléatoire compris entre 0 et N-1 ; ce n’est pas la meilleure approche, utiliser : Septembre 2007 Cours C-5 int main(int argc, char * argv[]) ! convention UNIX/Linux : • argc : nombre de chaînes transmises • argv : tableau de pointeurs sur chaînes • la première chaîne est le nom du programme (int)(((float)N*rand())/(RAND MAX+1.0)) Girardot/Roelens ◦ Les entrées/sorties permettent à un programme de lire et d’écrire sur divers périphériques (fichiers, écran, réseau) et donc de communiquer avec l’extérieur ! seront abordées dans le cours « algorithmique » ◦ Un programme peut recevoir des données depuis la ligne de commande ; ! ces données sont des chaînes de caractères ! elles sont accessibles au moyen des fameux et traditionnels argc et argv, premier et second paramètres d’un main : 39 Girardot/Roelens Septembre 2007 Cours C-5 40 Exemple #include <stdio.h> int main(int argc, char * argv[]) { int i ; for (i=0 ; i<argc ; i++) printf("argv[%d]=\"%s\"\n",i,argv[i]) ; return 0 ; } Autres types d’interactions Les systèmes Unix (Linux. . .) permettent d’autres types d’interaction que les E/S en mode ligne ou caractère. ◦ curses : est une bibliothèque qui permet de travailler en mode plein écran ! ex : fichier curs ex.c ! compilation : ajouter l’option -lcurses pour référencer la bibliothèque ◦ vogle : est une bibliothèque graphique complète ! ex : fichier vogle ex2.c ! compilation : ajouter les options : appel : [P]$ ch5main1 hello world ? ? ?.c argv[0]="ch5main1" argv[1]="hello" argv[2]="world" argv[3]="w01.c" [P]$ Girardot/Roelens -lvogle -L/usr/X11R6/lib -lX11 -lm Septembre 2007 Girardot/Roelens Septembre 2007 Cours C-5 41 Conclusions ◦ Se poser toujours la question de la finalité d’un développement : ! obtenir très vite un résultat particulier, ou écrire en vue d’une exécution fréquente et répétitive ? ! faire du « quick and dirty » • ex : écriture 1h, exécution 30’, total 90’ ! effectuer une analyse approfondie • ex : écriture 12h, exécution 1’, total 721’ ! plus de 99% des programmes n’ont jamais un coût d’exécution qui dépasse le coût de réalisation ◦ Rendre une application plus performante peut passer par des améliorations du code, ou par des choix de meilleurs algorithmes ◦ Une analyse « poussée » d’un problème peut permettre d’aboutir à un algorithme plus efficace ! un bon algorithme est préférable à une longue optimisation de code ◦ Lorsque cela est possible, utiliser un programme juste, écrit et prouvé par un spécialiste Girardot/Roelens Septembre 2007