précédent  index  suivant

11. Nombres en virgule flottante


11.1 J'ai un problème quand j'imprime un nombre réel.

Sur la plupart des architectures, les nombres réels (dits flottants) sont représentés en base 2, comme pour les entiers. Ainsi, le nombre 3.1 ne peut s'écrire exactement en base 2. La représentation binaire est un arrondi qui dépend de la précision du codage des flottants, et des choix du compilateur. De plus, avec une fonction comme printf(), le nombre à imprimer est converti en base 2 puis reconverti en base 10, ce qui augmente encore les imprécisions.

Il est préférable d'utiliser les double, qui ont une précision supérieure aux float, sauf si l'économie de mémoire est vraiment critique. Voir à ce sujet la question 11.10.

11.2 Pourquoi mes extractions de racines carrées sont erronées ?

Assurez-vous d'avoir inclus math.h, d'avoir correctement déclaré les autres fonctions renvoyant des double. Une autre fonction de la bibliothèque standard avec laquelle il faut faire attention est atof(), dans stdlib.h.

11.3 J'ai des erreurs de compilation avec des fonctions mathématiques

Il faut s'assurer d'avoir linké (lié) son code avec la bibliothèque mathématique. Par exemple, sous Unix, vous devez généralement passer l'option -lm à la fin de la ligne de commande.

11.4 Mes calculs flottants me donnent des résultats étranges et/ou différents selon les plateformes

Pour commencer, relisez 11.1. Si le problème est plus complexe, il convient de se rappeler que les ordinateurs utilisent des formats de représentation des flottants qui ne permettent pas des calculs exacts. Pertes de précision, accumulation d'erreurs et autres anomalies sont le lot commun du numéricien.

Rappelez-vous qu'aucun calcul sur des flottants n'a de chance d'être exact, en particulier, n'utilisez jamais == entre deux flottants. Ces problèmes ne sont pas spécifiques au C.

Dans certains problèmes, une solution peut être d'introduire un petit paramètre de relaxation, par exemple #define EPS 1e-10, puis de multiplier l'un des termes (judicieusement choisi) de vos calculs par (1 + EPS).

Pour plus de renseignements, on se reportera par exemple aux Numerical Recipes ou à Numerical Algorithms with C (cf. 3.9).

11.5 Comment simuler == entre des flottants ?

Étant donné qu'il y a perte de précision très vite, pour comparer deux valeurs flottantes, on teste si elles sont assez proches. Plutôt que d'écrire une horreur du genre :

	double a, b;
	/* ... */
	if (a == b) /* HORREUR ! */
		/* ... */
		

on écrira :

	#include <math.h>
	/* ... */
	double a, b;
	/* ... */
	if (fabs(a - b) <= epsilon * fabs(a))
		/* ... */
		

où l'on aura judicieusement choisi epsilon (non-nul !).

11.6 Comment arrondir des flottants ?

La méthode la plus simple et la plus expéditive est (int)(x + 0.5). Cette technique ne fonctionne pas correctement pour les nombres négatifs aussi vaut-il mieux utiliser

	(int)(x < 0 ? x - 0.5 : x + 0.5)
		

11.7 Pourquoi le C ne dispose-t-il pas d'un opérateur d'exponentiation ?

Parce que certains processeurs ne disposent pas d'une telle instruction. Il existe une fonction pow() déclarée dans math.h bien que la multiplication soit préférable pour de petits exposants.

11.8 Comment obtenir π ?

Parfois une constante prédéfinie M_PI est déclarée dans math.h mais ce n'est pas standard aussi vaut-il mieux calculer π soi-même via 4 * atan (1.0).

11.9 Qu'est-ce qu'un NaN ?

« NaN is Not a Number », ce qui signifie « Ce n'est pas un nombre ». Un NaN est un nombre flottant qui est le résultat d'une opération non conforme, par exemple 0/0. Lorsqu'un NaN est produit, la plupart des architectures produisent une interruption (ou un signal) qui termine le programme, au moment de l'utilisation de celui-ci. Il est parfois possible de vérifier si un nombre est NaN. Un bon test est celui-ci :

	#define isNaN(x) ((x) != (x))
		

Certains compilateurs fournissent des facilités quant à la gestion des NaN. GCC fournit dans la bibliothèque mathématique (math.h) les fonctions isnan(), isinf() et finite().

11.10 Faut-il préférer les double aux float ?

La vitesse de traitement d'un double n'est pas forcément plus longue qu'un float, cela dépend du compilateur (de ses options) et du processeur. Ainsi avec l'exemple suivant, en remplaçant le typedef par float ou double, on s'aperçoit que sur un Pentium ou un PowerPC, le double est plus rapide à calculer que le float tout en ayant une précision plus grande.

	#include <stdio.h>
	#include <math.h>

	typedef float reel;     /* float ou double */

	int main(void)
	{
		long i;
		reel d = 3.0;

		for (i = 0; i < 100000000; i++) {
			d = cos(d);
		}

		(void)printf("%f\n", d);
		return 0;
	}
		

Le C comprend des instructions mathématiques pour traiter les float directement au lieu de toujours passer par des double depuis la dernière norme (C99). Par exemple il existe cosf() en plus de cos(). En faisant des essais on s'aperçoit que dans notre exemple, le cosf() appliqué à un float devient aussi rapide que le cos() appliqué à un double.

En conclusion, nous pouvons dire qu'il est préférable d'utiliser des double à la place des float, sauf lorsque la place mémoire devient critique.


précédent  index  suivant