J'ai lu sur la différence entre la double précision et la simple précision. Cependant, dans la plupart des cas, float
et double
semblent être interchangeables, c'est-à-dire que l'utilisation de l'un ou de l'autre ne semble pas affecter les résultats. Est-ce vraiment le cas? Quand les flottants et les doubles sont-ils interchangeables? Quelles sont les différences entre eux?
Énorme différence.
Comme son nom l'indique, une double
a 2x la précision de float
[1]. En général, une double
a 15 chiffres décimaux de précision, alors que float
en a 7.
Voici comment le nombre de chiffres est calculé:
double
a 52 bits de mantisse + 1 bit caché: log (253) ÷ log (10) = 15,95 chiffres
float
a 23 bits de mantisse + 1 bit caché: log (224) ÷ log (10) = 7,22 chiffres
Cette perte de précision pourrait entraîner des erreurs de troncature beaucoup plus faciles à remonter, par exemple.
float a = 1.f / 81;
float b = 0;
for (int i = 0; i < 729; ++ i)
b += a;
printf("%.7g\n", b); // prints 9.000023
tandis que
double a = 1.0 / 81;
double b = 0;
for (int i = 0; i < 729; ++ i)
b += a;
printf("%.15g\n", b); // prints 8.99999999999996
En outre, la valeur maximale de float correspond à environ 3e38
, alors que double correspond à 1.7e308
. Par conséquent, utiliser float
peut frapper "infini" (c'est-à-dire un nombre spécial à virgule flottante) beaucoup plus facilement que double
pour quelque chose de simple, par exemple. calculer la factorielle de 60.
Lors des tests, quelques cas de test contiennent peut-être ces nombres énormes, ce qui peut entraîner l'échec de vos programmes si vous utilisez des flottants.
Bien sûr, parfois, même double
n'est pas assez précis, nous avons donc parfois long double
[1] (l'exemple ci-dessus donne 9.000000000000000066 sur Mac), mais tous les types à virgule flottante souffrent d'erreurs round-off. Par conséquent, si la précision est très importante (par exemple, le traitement de l'argent), vous devez utiliser int
ou une classe de fraction.
De plus, n'utilisez pas +=
pour additionner de nombreux nombres en virgule flottante, car les erreurs s'accumulent rapidement. Si vous utilisez Python, utilisez fsum
. Sinon, essayez d'implémenter l'algorithme de sommation Kahan .
[1]: Les normes C et C++ ne spécifient pas la représentation de float
, double
et long double
. Il est possible que les trois soient implémentés en double précision IEEE. Néanmoins, pour la plupart des architectures (gcc, MSVC; x86, x64, ARM) float
est un nombre à virgule flottante simple précision IEEE (binary32) et double
est un nombre à virgule flottante double précision IEEE (binary64).
Voici ce que disent les normes C99 (ISO-IEC 9899 6.2.5 § 10) ou C++ 2003 (ISO-IEC 14882-2003 3.1.9 §8):
Il existe trois types de nombres à virgule flottante:
float
,double
etlong double
. Le typedouble
fournit au moins autant de précision quefloat
et le typelong double
fournit au moins autant de précision quedouble
. L'ensemble de valeurs du typefloat
est un sous-ensemble de l'ensemble des valeurs du typedouble
; l'ensemble de valeurs de typedouble
est un sous-ensemble de l'ensemble de valeurs de typelong double
.
La norme C++ ajoute:
La représentation des valeurs des types à virgule flottante est définie par l'implémentation.
Je suggérerais de jeter un coup d'oeil à l'excellent Ce que tout informaticien devrait savoir sur l'arithmétique en virgule flottante qui couvre en profondeur le standard IEEE en virgule flottante. Vous en apprendrez plus sur les détails de la représentation et vous vous rendrez compte qu'il existe un compromis entre magnitude et précision. La précision de la représentation en virgule flottante augmente à mesure que la magnitude décroît. Par conséquent, les nombres en virgule flottante compris entre -1 et 1 sont ceux avec la plus grande précision.
Étant donné une équation quadratique: x2- 4.0000000 x + 3.9999999 = 0, les racines exactes à 10 chiffres significatifs sont, r1= 2.000316228 et r2= 1,999683772.
En utilisant float
et double
, nous pouvons écrire un programme de test:
#include <stdio.h>
#include <math.h>
void dbl_solve(double a, double b, double c)
{
double d = b*b - 4.0*a*c;
double sd = sqrt(d);
double r1 = (-b + sd) / (2.0*a);
double r2 = (-b - sd) / (2.0*a);
printf("%.5f\t%.5f\n", r1, r2);
}
void flt_solve(float a, float b, float c)
{
float d = b*b - 4.0f*a*c;
float sd = sqrtf(d);
float r1 = (-b + sd) / (2.0f*a);
float r2 = (-b - sd) / (2.0f*a);
printf("%.5f\t%.5f\n", r1, r2);
}
int main(void)
{
float fa = 1.0f;
float fb = -4.0000000f;
float fc = 3.9999999f;
double da = 1.0;
double db = -4.0000000;
double dc = 3.9999999;
flt_solve(fa, fb, fc);
dbl_solve(da, db, dc);
return 0;
}
Lancer le programme me donne:
2.00000 2.00000
2.00032 1.99968
Notez que les nombres ne sont pas importants, mais vous obtenez toujours des effets d'annulation utilisant float
.
(En fait, ce qui précède n’est pas la meilleure façon de résoudre des équations quadratiques utilisant des nombres à virgule flottante à simple ou double précision, mais la réponse reste inchangée même si l’on utilise la méthode { méthode la plus stable .)
La taille des nombres impliqués dans les calculs en virgule flottante n'est pas la chose la plus pertinente. C'est le calcul qui est effectué qui est pertinent.
En substance, si vous effectuez un calcul et que le résultat est un nombre irrationnel ou une décimale récurrente, il y aura des erreurs d'arrondi lorsque ce nombre sera réduit à la structure de données de taille finie que vous utilisez. Étant donné que double est deux fois plus grand que float, l'erreur d'arrondi sera beaucoup plus petite.
Les tests peuvent spécifiquement utiliser des nombres qui pourraient causer ce type d'erreur et donc vérifier que vous avez utilisé le type approprié dans votre code.
Le type float, long de 32 bits, a une précision de 7 chiffres. Bien qu'il puisse stocker des valeurs avec une plage très grande ou très petite (+/- 3.4 * 10 ^ 38 ou * 10 ^ -38), il ne comporte que 7 chiffres significatifs.
Le type double, long de 64 bits, a une plage plus étendue (* 10 ^ +/- 308) et une précision de 15 chiffres.
Le type long double est nominalement 80 bits, bien qu'un couple compilateur/système d'exploitation donné puisse le stocker sous 12-16 octets à des fins d'alignement. Le long double a un exposant qui est ridiculement énorme et qui devrait avoir une précision de 19 chiffres. Microsoft, dans son infinie sagesse, limite long double à 8 octets, la même chose que simple double.
De manière générale, utilisez simplement type double lorsque vous avez besoin d’une valeur/variable à virgule flottante. Les valeurs littérales à virgule flottante utilisées dans les expressions seront traitées par défaut comme des doublons, et la plupart des fonctions mathématiques renvoyant des valeurs à virgule flottante renvoient des doublons. Vous éviterez bien des maux de tête et des transcriptions de caractères si vous utilisez simplement double.
Les flotteurs ont moins de précision que les doubles. Bien que vous le sachiez déjà, lisez Ce que WE devrait savoir sur l’arithmétique en virgule flottante pour une meilleure compréhension.
Je viens de rencontrer une erreur qui m'a pris une éternité à comprendre et qui peut potentiellement vous donner un bon exemple de précision de flottement.
#include <iostream>
#include <iomanip>
int main(){
for(float t=0;t<1;t+=0.01){
std::cout << std::fixed << std::setprecision(6) << t << std::endl;
}
}
La sortie est
0.000000
0.010000
0.020000
0.030000
0.040000
0.050000
0.060000
0.070000
0.080000
0.090000
0.100000
0.110000
0.120000
0.130000
0.140000
0.150000
0.160000
0.170000
0.180000
0.190000
0.200000
0.210000
0.220000
0.230000
0.240000
0.250000
0.260000
0.270000
0.280000
0.290000
0.300000
0.310000
0.320000
0.330000
0.340000
0.350000
0.360000
0.370000
0.380000
0.390000
0.400000
0.410000
0.420000
0.430000
0.440000
0.450000
0.460000
0.470000
0.480000
0.490000
0.500000
0.510000
0.520000
0.530000
0.540000
0.550000
0.560000
0.570000
0.580000
0.590000
0.600000
0.610000
0.620000
0.630000
0.640000
0.650000
0.660000
0.670000
0.680000
0.690000
0.700000
0.710000
0.720000
0.730000
0.740000
0.750000
0.760000
0.770000
0.780000
0.790000
0.800000
0.810000
0.820000
0.830000
0.839999
0.849999
0.859999
0.869999
0.879999
0.889999
0.899999
0.909999
0.919999
0.929999
0.939999
0.949999
0.959999
0.969999
0.979999
0.989999
0.999999
Comme vous pouvez le constater après 0.83, la précision diminue considérablement.
Cependant, si j’ai configuré t
en tant que double, un tel problème ne se produira pas.
Il m'a fallu cinq heures pour réaliser cette erreur mineure, qui a ruiné mon programme.
Lorsque vous utilisez des nombres à virgule flottante, vous ne pouvez pas vous assurer que vos tests locaux seront exactement les mêmes que ceux effectués côté serveur. L'environnement et le compilateur sont probablement différents sur votre système local et où les tests finaux sont exécutés. J'ai déjà rencontré ce problème à plusieurs reprises dans certaines compétitions TopCoder, en particulier si vous essayez de comparer deux nombres à virgule flottante.
Les opérations de comparaison intégrées diffèrent car lorsque vous comparez 2 nombres avec une virgule flottante, la différence de type de données (c'est-à-dire float ou double) peut entraîner des résultats différents.
La différence entre float et double est que double a une valeur de précision supérieure à celle des variables float . Lorsque vous déclarez une variable comme étant float, elle autorise uniquement la saisie de 6 décimales après le point décimal . une variable flottante float f= 2.3333333;
// 7 décimales après le . est le maximum que vous puissiez stocker
Même si vous stockez une valeur qui est supérieure à six chiffres après le signe décimal, il ne stockera pas le nombre entier, mais ne le stockera que jusqu'aux six premiers chiffres après le point décimal . Vous obtiendrez également une erreur. si vous essayez de stocker plus de 7 chiffres après le point décimal de cette variable. Dans ce cas, vous devez l’initialiser comme suit -float f= 2.3333334443f;
// alors cela sera interprété comme 2.3333334 par le compilateur.
En cas de double, il stockera jusqu'à 15 chiffres après le point décimal . Ex: double d=1.222222345675423;
// 15 chiffres après le point décimal