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;