Série 7 - Moodle

Transcription

Série 7 - Moodle
Série 7 : types numériques à virgule flottante
Buts
Le langage C possède quelques types de base prédéfinis. Ici on se concentre sur les
types numériques flottants (ou à virgule flottante). Vous aurez conscience des
limitations du domaine couvert (dépassement de capacité) et de la précision
numérique offerte par ces types.
Exercice 0 (document séparé / niveau 0): écriture d'un petit programme de
calcul avec lecture en double précision
On veut écrire le programme pythagoras.c qui calcule l'hypoténuse d'un triangle à
partir de la donnée des longueurs des 2 autres cotés.
Exercice 1 (document séparé / niveau 0): Résolution de polynômes de dégrée 2
et imprécisions numériques
Exercice 2 (niveau 1): Comment comparer des nombres de type flottant
Ce programme affiche le temps d'une course sportive à intervalle régulier. La course
dure exactement 10 secondes. En plus du temps courant, le programme doit afficher
un message au début de la course, au milieu de la course, et à la fin de la course.
Voici le programme :
#include <stdio.h>
int main (void)
{
float t;
printf ("DEPART DE LA COURSE\n");
for (t=0.; t != 10.; t += 0.1)
{
printf (" Temps t = %f secondes\n", t);
if (t == 5.)
printf ("MILIEU DE LA COURSE\n");
}
printf ("FIN DE LA COURSE\n");
return 0;
}
Est-ce que ce programme vous semble correct ? Que va-t-il afficher à l'écran ? Pour
en être sûr, récupérez le fichier course.c, compilez-le et exécutez-le.
Manifestement, ce programme n'est pas correct. Pouvez-vous expliquer pourquoi, et
le corriger tout en conservant une structure de boucle ?
Remarque: pour interrompre un programme qui tourne sans fin, il faut presser les
touches Ctrl et C.
Exercice 3 (niveau 1): Overflow pour les types flottants et "non-nombres" (Not
a Number / NaN)
Vous allez maintenant observer le comportement des types flottants lorsque:


le domaine couvert (par ex., entre -1037 et 1037) est dépassé (on utilise aussi
le mot overflow pour ce cas avec les nombres flottants);
un nombre non réel (par ex. 0/0) est affecté à une variable.
Pour cela, récupérez le programme suivant (float_overflow.c), compilez-le et
exécutez-le. Observez le résultat:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
float v = 1e36; /* Valeur admissible mais proche de la limite */
printf ("v = %f\n", v); /* Affiche la valeur de v */
v = v * 1000.;
/* Multiplie v par 1000 -> Overflow */
printf ("v = %f\n", v); /* Affiche la nouvelle valeur de v */
v = 0. / 0.;
/* Affectation d'un non-nombre */
printf ("v = %f\n", v); /* Affiche la nouvelle valeur de v */
return EXIT_SUCCESS;
}
Questions:



comparez l’overflow des nombres entiers avec l’overflow des nombres
flottants;
donnez le nombre flottant maximum qui peut être représenté avec un float, en
consultant la macro HUGE ou HUGE_VAL se trouvant dans le fichier
/usr/include/math.h
dans le cas d’un non-nombre (par ex. 0/0), comment se comporte le système
?
Remarques:



pour les types flottants, une transition est faite quand on descend en-dessous
de la plus petite puissance du domaine couvert (typiquement 10 -37 ) ; dans ce
cas uniquement la partie entière de la mantisse passe de 1 à 0 pour arriver
progressivement jusqu'à 0 avec la partie fractionnaire.
NaN signifie "not a number" en anglais.
les comportements observés dans cette série pour les types flottants mais
aussi pour les types entiers ne sont pas standards (dépendent du système).
Exercice 4 (niveau 1): Précision des nombres à virgule flottante
En plus du problème de l'overflow, les nombres à virgule flottante sont aussi sujets
au problème de précision numérique. En effet, entre deux nombres réels
quelconques, il existe une infinité d’autres nombres réels: il est clairement impossible
de tous les représenter avec un nombre fini de bits.
La représentation des nombres flottants est conçue pour garantir le même nombre
de chiffres significatifs sur tout le domaine couvert (peu importe que l'amplitude du
nombre soit très petite ou très grande) ; cela veut dire qu'on aura le même nombre
de chiffres exactement représentés, en commençant à les compter depuis le
premier chiffre de poids fort différent de 0.
Le nombre de chiffres significatifs est proportionnel au nombre de bits réservés à
la mantisse .
Pour observer ce phénomène, copiez le fichier precision.c, exécutez-le. Ce
programme affiche une bonne approximation du nombre transcendental pi
=3.1415926535897932384..., et ensuite deux moins bonnes approximations, en
utilisant les deux types flottants float et double.
#include <stdio.h>
#include <stdlib.h>
#define M_PI 3.1415926535897932384
int main (void)
{
float
pi_f = M_PI;
double
pi_d = M_PI;
printf ("Une bonne approximation de PI : 3.1415926535897932384...\n");
printf ("En float (%lu bytes)
printf ("En double (%lu bytes)
return EXIT_SUCCESS;
}
: %.20f\n", sizeof (pi_f),
: %.20g\n", sizeof (pi_d),
pi_f);
pi_d);
Question: Combien de chiffres significatifs observez-vous pour chacun des deux
types flottants ?
L'explication technique est la suivante: soit X un nombre représenté dans le type float
qui possède une mantisse de 23 bits.
Avec seulement 23 bits de mantisse, le nombre représentable en float qui vient juste
après X est: X + X.2-23 car:
X + X.2-23 = exposant_de_X.( 1,mantisse_de_X + 2-23)
Utilisez cette explication pour l'exercice suivant.
Exercice 5 (niveau 1): Le compteur float détraqué
Copiez-Collez le petit programme suivant (float_compteur.c):
#include <stdlib.h>
#include <stdio.h>
int main (void)
{
float f = 16600000.;
while (1) /* boucle infinie */
{
printf ("f = %f\n", f);
f = f + 1.;
}
return EXIT_SUCCESS;
}
Ce programme incrémente indéfiniment une variable de type float, et affiche la
nouvelle valeur à chaque fois. La valeur initiale est 16 600 000. Compilez ce
programme et exécutez-le. Vous remarquez qu’au début le comportement du
programme est correct, mais que par contre à partir d’une certaine valeur (laquelle
?), rien ne va plus... Expliquez pourquoi à l'aide de l'exercice précédent.
Enfin, remplacez float par double et essayez à nouveau. Que constatez-vous et
comment l'expliquez vous ?
Vous pouvez quitter le programme en appuyant simultanément sur les touches Ctrl
et C.
Exercice 6 (niveau 2): Petite calculatrice
Ecrire un programme qui simule une petite calculatrice capable d'effectuer les
opérations arithmétiques élémentaires. Ce programme doit évaluer une expression
(entrée au clavier après une demande explicite du programme) de la forme:
nombre_1 opérateur nombre_2
où nombre_1 et nombre_2 sont des nombres réels, et opérateur est un caractère
identifiant l'opération à effectuer (addition (+), soustraction (-), multiplication (x) ou
division (/)).
NOTE: pour éviter tout conflit (vous verrez pourquoi dans une série ultérieure),
utilisez bien 'x' pour la multiplication et non pas '*'
Le programme doit aussi tourner indéfiniment: pour cela, utilisez une boucle sans fin,
par exemple: for (;;)
Attention: si l'utilisateur tente une division par zéro, le programme doit afficher le
message "ERREUR: division par zero". En cas d'opération invalide, un message
d'erreur doit aussi être affiché.
Exemples:
Entrez l'expression: 4 x 2.5
Résultat: 10.000000
Entrez l'expression: 2. ^ 2.5
ERREUR: opération invalide.
Essayez maintenant de rentrer au clavier un opérande faux. Que se passe-t-il?
Trouvez le problème et corrige-le (voir livre de cours).
Exercice 7: (niveau 2): Développement de la fonction sinus en série de
puissances
En analyse, un développement en série de Taylor permet d'écrire une approximation
en 0 de la fonction sin(x):d
Ecrivez un programme qui 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 qui affiche ensuite la valeur de sin(x):

une fois en utilisant la fonction sin() fournie dans la bibliothèque mathématique
standard

une fois avec l'approximation donnée ci-dessus
Remarques:


La fonction sin(x) de la bibliothèque mathématique standard utilise des radians.
Affichez vos résultats avec 15 décimales avec le code de format %15f
Testez votre programme avec des valeurs de x comprises entre
et . Constatez
aussi qu'en dehors de cet intervalle l'approximation n'est plus satisfaisante. Testez
votre programme pour x = 0.523598776 radians (~30 degrés). A partir de combien de
termes la précision n'augmente plus (à 15 décimales)?
Rappel: n'oubliez pas de signaler l'utilisation de la bibliothèque mathématique en
incluant le fichier math.h dans votre fichier source et en utilisant l'option -lm avec la
commande gcc.
Exemple d'exécution (avec machine virtuelle sur PC personnel) :
Donnez la valeur de x (en radians) : 1.57079633
Donnez le nombre de termes : 4
sin(1.57079633) = 1.000000000000000 (avec la bibliotheque
mathematique standard)
sin(1.57079633) = 0.999843101396632 (approximation avec 4 termes)
L’approximation peut donner un résultat différent selon le nombre d’opérations
utilisées pour calculer la formule de Taylor.
Une bonne approche est de chercher à réduire au maximum le nombre d’opérations
pour minimiser l’accumulation des imprécisions. Cela est possible en tirant parti du
fait qu’on peut s’aider du Terme (n-1) pour calculer le Terme n de la série de Taylor
avec la formule :
Termen = -1/(2*n(2*n +1))*x*x* Termen-1
Exercice 8 (niveau 1): opérateurs bit à bit
Ecrire un programme qui lit un nombre entier unsigned n avec le code de format
%u. Puis lire un autre entier p, compris entre 0 et 31, et qui affiche un message
indiquant si le bit p du nombre n est à 1 ou pas.
Complétez le programme pour qu'il affiche le nombre total de bits à 1 et à 0 du
nombre n.