Série 7 - Moodle
Transcription
Série 7 - Moodle
Série 7 Corrigé Exercice 2 (niveau 1): Comment comparer des nombres de type flottant Le programme n'est pas correct, car il effectue des tests d'égalité "stricte" entre deux nombres de type flottant, et ceci à deux reprises. Premièrement, la condition de fin de boucle t != 10., et deuxièmement, dans la condition de milieu de course (t == 5.). A cause des erreurs numériques, ces conditions sont toujours fausses. L'affichage de la variable t montre en effet que t ne vaut jamais exactement 5. ou 10. La solution est d'utiliser une tolérance ε. On a alors t < 10 + ε pour le test d’inégalité de la boucle qui doit être vrai tant que t est inférieur ou égal à 10, et fabs(t-5.)< ε pour le test d’égalité de milieu de course. Il reste à choisir un ε , ce qui n'est pas immédiat. Dans le cas présent, il faut le choisir petit par rapport au pas de temps qui est 0.1. On peut donc choisir 0.0001, par exemple. Exercice 2 (niveau 1): Overflow pour types flottants et non-nombres (NaN) Voici l'exécution du programme: v = 999999961690316245365415600208216064.000000 v = Inf v = NaN En cas d'overflow, les flottants ne se comportent pas de la même façon que les nombres entiers: la variable flottante adopte une valeur, notée inf, qui indique que les limites ont été atteintes, alors qu'une variable entière qui dépasse sa capacité se retrouve avec une valeur sans signification (rien n'indique si cette valeur est correcte ou pas). Avec les nombres flottants, la plus grande valeur qui peut être représentée est MAXFLOAT = 3.402823466E+38 Cette macro bien que très largement utiisée n'est pas requise par la norme ANSI-C. Donc sous certaines distributions Linux, la macro peut être definie soit dans le fichier limit.h, math.h ou float.h. Si cette macro n'est pas definie vous pouvez la définir vous même à l'aide de ces deux directives: #undef MAXFLOAT #define MAXLOAT 3.402823466e+38F Dans le cas d'un nombre non-réel, par ex. 0/0, la variable prend la valeur "NaN", pour signifier que la valeur de cette variable n'est pas un nombre. Sur d'autres systèmes, le programme aurait pu planter. Exercice 4 (niveau 1): Précision des nombres à virgule flottante Le nombre de chiffres significatifs dépend du nombre de bits attribués à la mantisse. Type float : 23 bits de mantisse en base 2 permettent d’obtenir 6 chiffres significatifs en base 10 dans le pire des cas. Type double : 52 bits de mantisse en base 2 permettent d’obtenir 15 chiffres significatifs en base 10 dans le pire des cas. On compte les chiffres significatifs en commençant du coté des puissances de la base la plus importante (premier chiffre différent de 0) car ces chiffres sont plus représentatif de la quantité codée dans ce nombre. On dit qu’il s’agit des chiffres de poids fort. Voici le résultat de l'exécution du programme : Une bonne approximation de PI : 3.1415926535897932384... affichage de l'approximation faite avec un float (4 bytes) : 3.14159274101257324219 affichage de l'approximation faite avec un double (8 bytes) : 3.141592653589793116 Pour la représentation de Pi, on observe 7 chiffres significatifs corrects pour le type float, et 16 chiffres significatifs corrects pour le type double. La valeur des chiffres « non significatifs » peut très bien s’expliquer ; il s’agit du résultat d’une somme de puissances de 2 qui fait une approximation du nombre que l’on cherche à représenter. Exercice 5 (niveau 1): Le compteur float détraqué Le programme incrémente bien la variable, jusqu'à ce que celle-ci atteigne la valeur 16777216, qui est équivalent à 224 . Or, dans une variable de type float, la mantisse est codée sur 23 bits. Ce qui veut dire qu'entre deux puissances de deux successives seuls 223nombres distincts peuvent être représentés. C'est ce qui arrive entre 223 et 224 ; dans cet intervalle cela correspond exactement aux nombres entiers, et pas plus. Ensuite 224et 225 il existe deux fois plus d'entiers mais seuls la moitié d'entre eux sont représentés, de deux en deux à partir de 224. C'est ce qui explique que 224.est le dernier nombre entier consécutif représentable. On a atteint la limite de précision du type float. pour représenter exactement les entiers consécutifs. Du point de vue de l'addition qui est faite dans ce programme, le rapport entre les ordres de grandeur des deux valeurs est trop différent: l'incrément (1) est "trop petit" par rapport à la valeur de f, lorsque celle-ci vaut 16777216 ou plus. En d'autre termes, cela demanderait trop de chiffres significatifs pour représenter exactement la valeur 16777217 avec un float (cela demande 8 chiffres significatifs). Donc, on se limite à une approximation, qui est 16777216, et on reste bloqués sur cette valeur. Ce problème peut être repoussé en utilisant le type double, qui permet une plus grande précision (mantisse codée sur 52 bits). La boucle va donc tourner très très longtemps avant de rencontrer le même problème qu’avec un float mai sle problème se produira aussi. Exercice 6 (niveau 2): Petite calculatrice Lorsque l'on entre une expression incorrecte, celle-ci est stockée dans le buffer d'entrée (tampon). La fonction scanf tente de lire la valeur mais n'y parvient pas. Elle sort donc sur une erreur sans avoir touché au tampon. Lors de l'itération suivante, le tampon n'étant pas vide, scanf tente de lire la valeur mais n'y parvient toujours pas........ Pour régler ce problème, il faut vider le tampon avant de refaire une nouvelle lecture , pour cela on peut utiliser la petite boucle while getchar(). // inclure les fichiers en-tête stdio.h et stdlib.h int main (void) { for (;;) /* boucle infinie */ { double nb1, nb2; char op; int c; do { printf ("Entrez l'expression: "); c=scanf ("%lf %c %lf", &nb1, &op, &nb2); // on peut vider le buffer d'entrée dans tous les cas // car il y a au moins un caractère '\n' à enlever while(getchar() !='\n'); } while(c!=3); switch (op) { case '+' : printf ("Résultat: %f\n\n", nb1 + nb2); break; case '-' : printf ("Résultat: %lf\n\n", nb1 - nb2); break; case 'x' : printf ("Résultat: %lf\n\n", nb1 * nb2); break; case '/' : if (nb2 != 0.) printf ("Résultat: %lf\n\n", nb1 / nb2); else printf ("ERREUR: division par zéro.\n\n"); break; default: printf ("ERREUR: opération invalide.\n\n"); } } } return EXIT_SUCCESS; _________________________________________________________________________ Exercice 7 (niveau 2): Développement de la fonction sinus en série de puissance Le programme ci-dessous lit une valeur de x au clavier (x étant une variable de type double exprimant un angle en radians) ainsi que le nombre de termes de la série (dans l'équation, n = nb termes - 1) et affiche la valeur de sin(x). #include <stdio.h> #include <math.h> #include <stdlib.h> int main(void) { double x, x_n; int n, nb_termes; double sin_standard, sin_taylor; printf("Donnez la valeur de x (en radians) : "); while( scanf("%lf", &x) != 1) while(getchar() != '\n'); printf("Donnez le nombre de termes : "); while( scanf("%d", &nb_termes) != 1) while(getchar() != '\n'); if (nb_termes < 1) { printf("ERREUR : le nombre de termes doit etre superieur a 0\n"); exit(0); } sin_standard = sin(x); // ATTENTION: n = nb_termes - 1 // initialisation pour n=0 (nb_termes = 1) x_n = x; sin_taylor = x_n; for (n=1;n<nb_termes;++n) { // on utilise le terme précédent de la série pour simplifier le calcul // du terme x_n. Cela permet d’éviter le calcul des factorielles x_n = -1./(2*n*(2*n+1))*x*x*x_n; sin_taylor += x_n; } } // // // // ATTENTION: on serait tente de remplacer les 2 lignes ci-dessus par: sin_taylor += -1./(2*n*(2*n+1))*x*x*x_n; Cependant, elles ne sont pas equivalentes car x_n est remis a jour a chaque tour de boucle printf("sin(%lf) = %.15lf (avec la bibl. math. standard)\n", x, sin_standard); printf("sin(%lf) = %.15lf (approx. par dev. en serie avec %d termes)\n", x, sin_taylor, nb_termes); return EXIT_SUCCESS; Exercice 8 (niveau 1): Opérateurs bit à bit Voici le program, qui affiche un message indiquant si le bit p du nombre n est à 1 ou pas : #include <stdio.h> #include <stdlib.h> int main (void) { unsigned n ; int p; printf ("nombre unsigned: "); scanf("%u",&n); printf ("indice du bit [0, 31]:"); scanf("%d",&p); printf ("Le bit %d est à %d\n", p,((1<<p)&n)?1:0); } return EXIT_SUCCESS; NOTE : l’opérateur conditionnel (?:) évalue une expression retournant une valeur si cette expression est vraie et une autre si l'expression est faux, donc l’expression : printf ("Le bit %d est à %d\n", p,((1<<p)&n)?1:0); est équivalent à: int isEqual; if ((1<<p)&n) isEqual = 1; else isEqual = 0; printf ("Le bit %d est à %d\n", p, isEqual); Pour qu'il affiche le nombre total de bits à 1 et à 0 du nombre n, il faut le modifier comme ci-dessous : #include <stdio.h> #include <stdlib.h> int main (void) { unsigned n, mask; int count_1 =0; printf ("nombre unsigned: "); scanf("%u",&n); mask = 1; do { count_1 += ((mask&n)?1:0); mask = mask << 1 ; }while(mask); } printf ("Le nombre de bits à 1 est à %d\n", count_1); printf ("Le nombre de bits à 0 est à %d\n", 32-count_1); return EXIT_SUCCESS;