|
Par
Maëlan
Mise à jour : 13/08/2011
193 visites depuis 7 jours,
classé 428/786
|
1 2 | printf("taille d'un float : %u\n", sizeof(float)); printf("taille d'un double : %u\n", sizeof(double)); |
1 2 3 4 | 0.0e0 ; 0.0 ; 0.e0 ; 0. ; .0e0 ; .0 ; 0e0 ; |
Pour cela :
).| Formateurs | Utilisation |
|---|---|
| "%f", "%F" | Affiche le double "simplement", comme vous avez sans doute l'habitude de l'écrire : l'affichage est du type [-]XXXX.XXX, où les X sont des chiffres de 0 à 9 et [-] symbolise le "moins" éventuel. |
| "%e", "%E" | Affiche le double en écriture scientifique, c'est-à-dire avec un seul chiffre avant la virgule et un exposant introduit par la lettre 'e' (ou 'E' pour "%E") ; l'affichage est donc du type [-]X.XXXXXXeYY (ou [-]X.XXXXXXEYY). |
| "%g", "%G" | Une sorte de "combinaison" des formateurs précédents : utilise le premier style si le nombre n'est pas trop grand ou trop petit, le deuxième style sinon. |
| Remarquez que tous ces formateurs attendent des flottants de type double. Il n'existe pas de formateurs pour float, ce qui n'est pas dramatique car vous pouvez convertir vos nombres de float vers double pour les afficher. Le C99 vous permet d'ajouter la lettre 'L' majuscule entre le '%' et le formateur afin de correspondre à un long double. |
|
| Formateurs | Utilisation |
|---|---|
| "%e", "%E", "%f", "%F", "%g", "%G" |
Lit un nombre à virgule flottante et l'écrit dans la variable de type float indiquée. Le nombre lu doit être écrit de la même manière que vous écrivez une constante dans votre code source, c'est-à-dire (vous avez déjà oublié ?) :
|
| Pour lire un double, il faut ajouter la lettre 'l' minuscule entre le '%' et le formateur ; pour un long double, il faut ajouter 'L' (ou 'll'). | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | #include <math.h> /** fonctions courantes **/ double pow(double x, double n); // x à la puissance n double sqrt(double); // racine carrée double cbrt(double); // [C99] racine cubique double fabs(double); // valeur absolue double fdim(double, double); // [C99] différence positive double fmin(double, double); // [C99] minimum double fmax(double, double); // [C99] maximum /** fonctions trigonométriques (les angles sont en radians) **/ double cos(double); // cosinus double sin(double); // sinus double tan(double); // tangente double acos(double); // arc-cosinus double asin(double); // arc-sinus double atan(double); // arc-tangente double atan2(double y, double x); /* arc-tangente avec 2 arguments : angle entre l'axe des abscisses et le point de coordonnées (x ; y) ; attention, y est le 1er argument ! */ double cosh(double); // cosinus hyperbolique double sinh(double); // sinus hyperbolique double tanh(double); // tangente hyperbolique double acosh(double); // [C99] arc-cosinus hyperbolique double asinh(double); // [C99] arc-sinus hyperbolique double atanh(double); // [C99] arc-tangente hyperbolique /** arrondis, parties entière et fractionnaire **/ double round(double); // [C99] arrondi à l'entier, tel que round(.5)==1 long int lround(double); // [C99] idem long long int llround(double); // [C99] idem double rint(double); // [C99] arrondi à l'entier suivant le mode d'arrondi [1] long int lrint(double); // [C99] idem long long int llrint(double); // [C99] idem double nearbyint(double) /* [C99] arrondi à l'entier suivant le mode d'arrondi, sans lever d'exception "inexact" [1] */ double ceil(double); // arrondi à l'entier supérieur double floor(double); // arrondi à l'entier inférieur double trunc(double); // [C99] partie entière (troncature) double modf(double, double*); /* retourne la partie fractionnaire et écrit la partie entière dans le nombre pointé */ /** exponentielles, logarithmes & co. **/ double exp(double); // fonction exponentielle double expm1(double x); // [C99] équivalent à exp(x)-1, mais plus précis double exp2(double x); // [C99] 2 à la puissance x double log(double); // logarithme naturel (ou "népérien") double log1p(double x); // [C99] équivalent à log(1+x), mais plus précis double log10(double); // logarithme en base 10 double log2(double); // [C99] logarithme en base 2 double logb(double x); /* [C99] logarithme entier en base 2 (équivalent à floor(log2(x)), mais plus rapide) [2] */ int ilogb(double); // [C99] idem double scalbn(double x, int n); // [C99] x * 2^n [2] double scalbln(double, long int); // [C99] idem double ldexp(double x, int n); // x * 2^n double frexp(double x, int*); /* fraction normalisée : écrit un exposant EXP dans l'entier pointé et renvoie une valeur A, tels que x == A / (2^EXP) ; A est compris entre 0.5 inclus et 1 exclus */ /** plus de fonctions **/ double fmod(double, double); // reste de la division double remainder(double, double); // [C99] idem (avec une subtile nuance) double hypot(double, double); /* [C99] hypoténuse du triangle rectangle dont les deux autres côtés sont donnés */ double erf(double); // [C99] fonction d'erreur de Gauss double erfc(double); // [C99] fonction d'erreur complémentaire double tgamma(double); // [C99] fonction gamma double lgamma(double); // [C99] logarithme naturel de la valeur absolue de la fonction gamma double copysign(double x, double y); // [C99] valeur de x avec le signe de y double fma(double x, double y, double z) // [C99] x*y + z double nextafter(double x, double y); /* [C99] prochaine valeur flottante représentable après x (vers y) */ double nexttoward(double, long double); // [C99] idem /** NOTES : [1] Modes d'arrondis, exceptions : on verra ça plus tard. [2] En fait, j'ai mis 2 pour simplifier, mais en réalité il s'agit de FLT_RADIX (une constante définie dans le header <float.h> et dont vous n'avez pas encore les connaissances requises pour en comprendre le sens) ; FLT_RADIX vaut probablement 2. **/ |
Un point sur la norme C sera fait à la fin de ce chapitre (ainsi qu'une manière de déterminer si IEEE 754 est bien employé sur votre implémentation).| Nom dans la norme IEE 754 |
Type en C | Taille (en bits) | Chiffres significatifs (en base 10) |
Valeurs absolues possibles | ||||
|---|---|---|---|---|---|---|---|---|
| Total | s | e | m | minimum | maximum | |||
| simple précision | float | 32 bits | 1 | 8 | 23 | 7 | 1,2 ×10-38 | 3,4 ×10+38 |
| double précision | double | 64 bits | 1 | 11 | 52 | 16 | 2,2 ×10-308 | 1,8 ×10+308 |

| Type | Représentation mémoire | Valeur | |
|---|---|---|---|
| binaire | hexadécimal | ||
| Not a Number | [0/1] — 11111111 — 11111111111111111111111 … [0/1] — 11111111 — 00000000000000000000001 |
[7/F]F FF FF FF … [7/F]F 80 00 01 |
NaN … NaN |
| Infini | [0/1] — 11111111 — 00000000000000000000000 | [7/F]F 80 00 00 | ± |
| Nombre normalisé | [0/1] — 11111110 — 11111111111111111111111 … [0/1] — 00000001 — 00000000000000000000000 |
[7/F]F 7F FF FF … [0/8]0 80 00 00 |
± 3,4028235 ×10+38 … ± 1,1754944 ×10-38 |
| Nombre dénormalisé | [0/1] — 00000000 — 11111111111111111111111 … [0/1] — 00000000 — 00000000000000000000001 |
[0/8]0 7F FF FF … [0/8]0 00 00 01 |
± 1,1754942 ×10-38 … ± 1,4012985 ×10-45 |
| Zéro | [0/1] — 00000000 — 00000000000000000000000 | [0/8]0 00 00 00 | ± 0 |
| Type | Représentation mémoire | Valeur |
|---|---|---|
| Not a Number | [7/F]F FF FF FF FF FF FF FF … [7/F]F F0 00 00 00 00 00 01 |
NaN … NaN |
| Infini | [7/F]F F0 00 00 00 00 00 00 | ± |
| Nombre normalisé | [7/F]F EF FF FF FF FF FF FF … [0/8]0 10 00 00 00 00 00 00 |
± 1,7976931348623157 ×10+308 … ± 2,2250738585072014 ×10-308 |
| Nombre dénormalisé | [0/8]0 0F FF FF FF FF FF FF … [0/8]0 00 00 00 00 00 00 01 |
± 2,2250738585072010 ×10-308 … ± 5,0000000000000000 ×10-324 |
| Zéro | [0/8]0 00 00 00 00 00 00 00 | ± 0 |

Il aurait été plus simple de décider qu'à l'exposant décalé nul correspondaient également des nombres normalisés "banals" (dont l'exposant réel serait bien -127 et non -126).



Mais si, vous pouvez tout à fait répondre, réfléchissez un peu.

1 2 3 4 5 6 | #include <stdio.h> int main(void) { printf("%g\n%g\n", (1.0 - .2 - .2 - .2 - .2 - .2), (1.0 - (.2 + .2 + .2 + .2 + .2)) ); return 0; } |
5.55112e-017 0 |
L'exemple précédent l'illustre bien. D'autres cas parlants sont imaginables.
En effet, l'opérateur de comparaison == renvoie vrai si ses deux opérandes sont EXACTEMENT égales, ou s'il compare un zéro positif et un zéro négatif. Or, avec les problèmes d'arrondis, ce n'est pas forcément vrai, car une différence minuscule peut s'être glissée entre les deux nombres testés. Ainsi, vous risquez de vous retrouver avec des égalités qui devraient être vraies mais qui sont fausses, ou le contraire !1 2 3 4 5 6 7 8 9 10 11 12 | int main(void) { float f=0; int i; for(i=0; i<100; ++i) f+= .01; if(f==1) printf("f==1\n"); else printf("f!=1, f==%f", f); return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 | #include <math.h> // pour la fonction fabs, renvoyant la valeur absolue d'un flottant #define EPSILON 1e-8 /* ici pour des doubles, mais on fait la même chose pour des floats */ int doublesAreEqual(double a, double b) { return fabs(a-b) <= EPSILON; /* La fonction fabs renvoie la valeur absolue (c'est-à-dire positive) du nombre de type double passé en argument ; ses équivalents (C99) sont fabsf pour les float, et fabsl pour les long double (pensez donc à adapter votre code en conséquence pour comparer les autres types flottants). */ } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <math.h> #define EPSILON 1e-8 int doublesAreEqual(double a, double b) { if(a==b) return 1; // si a et b valent zéro (explications plus bas) double absError= fabs(a-b); // écart absolu a= fabs(a); // on ne garde que les valeurs absolues pour la suite … b= fabs(b); // … pour pouvoir calculer le nombre le plus grand en valeur absolue return ( absError / (a>b? a:b) ) <= EPSILON; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <math.h> #define EPSILON 1e-8 int doublesAreEqual(double a, double b, double maxAbs, double maxRel) { if(a==b) return 1; double absError= fabs(a-b); a= fabs(a); b= fabs(b); return absError <= maxAbs || ( absError / (a>b? a:b) ) <= maxRel; } |

Valeur du float représentation en mémoire binaire hexadécimal en base 10 +1.9999998 0 0111111 1 1111111 11111111 11111110 3F FF FF FE 1073741822 +1.9999999 0 0111111 1 1111111 11111111 11111111 3F FF FF FF 1073741823 |
Ce n'est pas vraiment compliqué, quand on y pense. Quelques explications si vraiment vous bloquez :Secret (cliquez pour afficher)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdlib.h> // pour la fonction abs, renvoyant la valeur absolue d'un entier #include <stdint.h> /* header du C99, qui fournit des types entiers de taille fixe : — int32_t, int64_t : entier signé de 32 bits ou de 64 bits ; — uint32_t, uint64_t : entier non-signé de 32 bits ou de 64 bits. */ #define INTREPOFFLOAT(f) ( *(int32_t*)&(f) ) // représentation entière d'un float de 32 bits #define INTREPOFDOUBLE(d) ( *(int64_t*)&(d) ) // représentation entière d'un double de 64 bits #define MAXULPS 5 /* ici pour des floats, mais on fait exactement pareil pour des doubles */ int floatsAreEqual(float a, float b) { if(a==b) return 1; return abs( INTREPOFFLOAT(a) - INTREPOFFLOAT(b) ) <= MAXULPS; /* attention à la fonction abs, qui prend un int en argument ; un int fait 16 ou 32 bits : il peut donc être trop petit pour contenir les 32 bits de la représentation entière d'un float, et sera de toutes façons insuffisant pour les 64 bits de celle d'un double. Voyez la fonction labs qui prend un long int, ou llabs (C99) qui prend un long long int, ou mieux, écrivez vos propres fonctions de valeur absolue, pour 32 et 64 bits (avec les types de <stdint.h>) ; ainsi, vous n'aurez plus de problèmes de taille des types pouvant varier. Ici, je garde les fonctions standards par souci de clarté. */ } |

Ben oui, le signe ! Les flottants, selon la norme IEE 754, sont signés selon le principe du bit de signe et non du complément à 2. Or, les entiers (du moins sur la grande majorité des ordinateurs aujourd'hui) sont stockés… selon la règle du complément à 2.Valeur du float représentation en mémoire hexadécimal en base 10 selon le complément à 2 +4.2038954 e-45 00 00 00 03 3 +2.8025969 e-45 00 00 00 02 2 +1.4012985 e-45 00 00 00 01 1 +0.0000000 00 00 00 00 0 -0.0000000 80 00 00 00 -2147483648 -1.4012985 e-45 80 00 00 01 -2147483647 -2.8025969 e-45 80 00 00 02 -2147483646 -4.2038954 e-45 80 00 00 03 -2147483645 |
Valeur du float représentation "transformée" hexadécimal en base 10 selon le complément à 2 +4.2038954 e-45 00 00 00 03 3 +2.8025969 e-45 00 00 00 02 2 +1.4012985 e-45 00 00 00 01 1 +0.0000000 00 00 00 00 0 -0.0000000 00 00 00 00 0 * (* = a été transformé) -1.4012985 e-45 FF FF FF FF -1 * -2.8025969 e-45 FF FF FF FE -2 * -4.2038954 e-45 FF FF FF FD -3 * |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdlib.h> #include <stdint.h> #define INTREPOFFLOAT(f) ( *(int32_t*)&(f) ) #define INTREPOFDOUBLE(d) ( *(int64_t*)&(d) ) #define MAXULPS 5 int floatsAreEqual(float a, float b) { int32_t aInt= INTREPOFFLOAT(a); // représentations entières int32_t bInt= INTREPOFFLOAT(b); if(aInt<0) aInt= 0x80000000 - aInt; // ou 0x8000000000000000 pour des doubles if(bInt<0) bInt= 0x80000000 - bInt; /* NOTE: on teste (aInt<0) et non (a<0). En effet, si a==-0.0 (zéro négatif), alors le test (a<0) renverrait faux, et on garderait la représentation de -0.0, à savoir 0x80000000. La règle du complément à 2 garde l'avantage du bit de signe : si le bit de poids fort est à 1, alors le nombre entier est négatif, et réciproquement ; on peut donc utiliser le test sur l'entier et non sur le flottant pour savoir s'il faut "transformer" la représentation. */ return abs( aInt - bInt ) <= MAXULPS; } |
) ;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #ifndef INCLUDE_CMPFLOATS_H #define INCLUDE_CMPFLOATS_H #include <stdint.h> #include <math.h> /* pour les macros de test des nombres flottants ( isnan() et isinf() ) */ /* représentation entière du nombre en virgule flottante */ #define INTREPOFFLOAT(f) ( *(int32_t*)&(f) ) #define INTREPOFDOUBLE(d) ( *(int64_t*)&(d) ) /* marge maximale séparant deux nombres flottants considérés comme égaux, en termes d'ULPs ("Unit of Least Precision") */ #define MAXULPSFLOAT 5 #define MAXULPSDOUBLE 5 /* renvoie vrai (1) si les 2 nombres sont égaux, faux (0) sinon */ int floatsAreEqual(float a, float b); int doublesAreEqual(double a, double b); /* renvoie -1 si a>b, 0 si a==b, 1 si a<b, ou -2 si a ou b est NaN */ int cmpFloats(float a, float b); int cmpDoubles(double a, double b); /* macros booléennes (à utiliser dans des tests simples) */ #define CMPFLOATS_EQUAL(a,b) (cmpFloats((a),(b))==0) // => a==b #define CMPFLOATS_UNEQUAL(a,b) (cmpFloats((a),(b))!=0) // => a!=b #define CMPFLOATS_GT(a,b) (cmpFloats((a),(b))==-1) // => a>b #define CMPFLOATS_LT(a,b) (cmpFloats((a),(b))==1) // => a<b //#define CMPFLOATS_GTEQUAL(a,b) (cmpFloats((a),(b))!=1) // => a>=b //#define CMPFLOATS_LTEQUAL(a,b) (cmpFloats((a),(b))!=-1) // => a<=b #define CMPFLOATS_GTEQUAL(a,b) ((cmpFloats((a),(b))-1)&2) // => a>=b #define CMPFLOATS_LTEQUAL(a,b) (cmpFloats((a),(b))>=0) // => a<=b #define CMPFLOATS_NAN(a,b) (cmpFloats((a),(b))==-2) // => a==NaN || b==NaN #define CMPDOUBLES_EQUAL(a,b) (cmpDoubles((a),(b))==0) // => a==b #define CMPDOUBLES_UNEQUAL(a,b) (cmpDoubles((a),(b))!=0) // => a!=b #define CMPDOUBLES_GT(a,b) (cmpDoubles((a),(b))==-1) // => a>b #define CMPDOUBLES_LT(a,b) (cmpDoubles((a),(b))==1) // => a<b //#define CMPDOUBLES_GTEQUAL(a,b) (cmpDoubles((a),(b))!=1) // => a>=b //#define CMPDOUBLES_LTEQUAL(a,b) (cmpDoubles((a),(b))!=-1) // => a<=b #define CMPDOUBLES_GTEQUAL(a,b) ((cmpDoubles((a),(b))-1)&2) // => a>=b #define CMPDOUBLES_LTEQUAL(a,b) (cmpDoubles((a),(b))>=0) // => a<=b #define CMPDOUBLES_NAN(a,b) (cmpDoubles((a),(b))==-2) // => a==NaN || b==NaN #endif //INCLUDE_CMPFLOATS_H |
1 2 3 4 5 | instructions1; if(CMPFLOATS_GT(a,b)) { // a>b instructions2; } instructions3; |
1 2 3 4 5 6 7 8 9 10 11 12 | instructions1; if(CMPFLOATS_GT(a,b)) { // a>b instructions2; } else if(CMPFLOATS_LT(a,b)) { // a<b /* /!\ on appelle la fonction cmpFloats 2 fois */ instructions2b; } else // a==b instructions2t; /* /!\ ce code est aussi exécuté dans le cas de NaN ! */ } instructions3; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | instructions1; switch(cmpFloats(a,b)) { case -1: // a>b instructions2; break; case 0: // a==b instructions2t; break; case 1: // a<b instructions2b; break; default: break; // NaN } instructions3; |
1 2 3 4 5 6 7 8 9 10 11 12 | instructions1; switch(cmpFloats(a,b)) { case -1: // a>b case 0: // a==b instructions2; // ce code est donc exécuté si a>=b break; case 1: // a<b instructions2b; break; default: break; // NaN } instructions3; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #include "cmpfloats.h" /* valeur absolue d'un entier de 32 ou 64 bits */ uint32_t abs32(int32_t x) { return x<0? -x : x; } uint64_t abs64(int64_t x) { return x<0? -x : x; } int floatsAreEqual(float a, float b) { /* vérification pour NaN : si l'un des deux nombres est NaN, alors on retourne toujours faux */ if(isnan(a) || isnan(b)) return 0; /* vérification pour les infinis : si l'un des deux nombres est infini, alors on ne retourne vrai que si les deux nombres sont strictement égaux (tous les deux +inf ou -inf) */ if(isinf(a) || isinf(b)) return a==b; int32_t aInt= INTREPOFFLOAT(a); int32_t bInt= INTREPOFFLOAT(b); if(aInt<0) aInt= 0x80000000 - aInt; if(bInt<0) bInt= 0x80000000 - bInt; return abs32( aInt - bInt ) <= MAXULPSFLOAT; } int doublesAreEqual(double a, double b) { if(isnan(a) || isnan(b)) return 0; if(isinf(a) || isinf(b)) return a==b; int64_t aInt= INTREPOFDOUBLE(a); int64_t bInt= INTREPOFDOUBLE(b); if(aInt<0) aInt= 0x8000000000000000LL - aInt; if(bInt<0) bInt= 0x8000000000000000LL - bInt; return abs64( aInt - bInt ) <= MAXULPSDOUBLE; } int cmpFloats(float a, float b) { if(isnan(a) || isnan(b)) return -2; // -2 si on a au moins un NaN if(isinf(a) || isinf(b)) return (a<b)? -1 : (a>b)? 1 : 0; // gestion des infinis similaire à ci-dessous int32_t aInt= INTREPOFFLOAT(a); int32_t bInt= INTREPOFFLOAT(b); if(aInt<0) aInt= 0x80000000 - aInt; if(bInt<0) bInt= 0x80000000 - bInt; return (abs32(aInt-bInt) <= MAXULPSFLOAT)? 0 // 0 si les nombres sont égaux : (aInt<bInt)? 1 // 1 si a<b : -1; // -1 si a>b } int cmpDoubles(double a, double b) { if(isnan(a) || isnan(b)) return -2; if(isinf(a) || isinf(b)) return (a<b)? -1 : (a>b)? 1 : 0; int64_t aInt= INTREPOFDOUBLE(a); int64_t bInt= INTREPOFDOUBLE(b); if(aInt<0) aInt= 0x8000000000000000LL - aInt; if(bInt<0) bInt= 0x8000000000000000LL - bInt; return (abs64(aInt-bInt) <= MAXULPSDOUBLE)? 0 : (aInt<bInt)? 1 : -1; } |
Cela vous simplifiera la vie (toutefois, vous risquerez alors, à la longue, d'oublier que vous avez fait quelque chose pour pouvoir comparer convenablement des flottants, et un jour ça ne marchera plus car vous n'aurez plus inclus votre petit header magique.)

)
|
Chaînes de caractères à taille variable en C |
|
|
Utiliser la mémoire |
|
|
[C++] Déboguer avec Code::Blocks |
|
|
Exemples avec quelques instructions |