Spécificateur de largeur d’impression pour maintenir la précision de la valeur à virgule flottante

Existe-t-il un printf largeur printf qui peut être appliqué à un spécificateur à virgule flottante qui formaterait automatiquement la sortie au nombre nécessaire de chiffres significatifs de sorte que lors du balayage de la chaîne, la valeur à virgule flottante d’origine soit acquise?

Par exemple, supposons que j’imprime un float à une précision de 2 décimales:

 float foobar = 0.9375; printf("%.2f", foobar); // prints out 0.94 

Lorsque je scanne la sortie 0.94 , je n’ai aucune garantie de conformité à la norme que je récupérerai la valeur à virgule flottante originale de 0.9375 (dans cet exemple, je ne le ferai probablement pas).

Je voudrais un moyen de dire à printf d’imprimer automatiquement la valeur en virgule flottante au nombre nécessaire de chiffres significatifs pour s’assurer qu’elle peut être scannée à la valeur d’origine transmise à printf .

Je pourrais utiliser certaines des macros de float.h pour obtenir la largeur maximale à transmettre à printf , mais existe-t-il déjà un spécificateur pour imprimer automatiquement le nombre nécessaire de chiffres significatifs – ou au moins la largeur maximale?

Je recommande la solution hexadécimale @Jens Gustedt: utilisez% a.

OP veut «imprimer avec une précision maximale (ou au moins à la décimale la plus significative)».

Un exemple simple serait d’imprimer un septième comme dans:

 #include  int Digs = DECIMAL_DIG; double OneSeventh = 1.0/7.0; printf("%.*e\n", Digs, OneSeventh); // 1.428571428571428492127e-01 

Mais creusons plus profondément …

Mathématiquement, la réponse est “0.142857 142857 142857 …”, mais nous utilisons des nombres à virgule flottante de précision finie. Supposons que le binary à double précision IEEE 754 . Donc, le OneSeventh = 1.0/7.0 donne la valeur ci-dessous. Les nombres en double virgule représentables précédents et suivants sont également indiqués.

 OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125 OneSeventh = 0.1428571428571428 49212692681248881854116916656494140625 OneSeventh after = 0.1428571428571428 769682682968777953647077083587646484375 

L’impression de la représentation décimale exacte d’un double a des utilisations limitées.

C a 2 familles de macros dans pour nous aider.
Le premier ensemble est le nombre de chiffres significatifs à imprimer dans une chaîne en décimal. Ainsi, lors de la numérisation de la chaîne, nous obtenons le point flottant d’origine. Ils sont affichés avec la valeur minimale de la spécification C et un exemple de compilateur C11.

 FLT_DECIMAL_DIG 6, 9 (float) (C11) DBL_DECIMAL_DIG 10, 17 (double) (C11) LDBL_DECIMAL_DIG 10, 21 (long double) (C11) DECIMAL_DIG 10, 21 (widest supported floating type) (C99) 

Le deuxième ensemble est le nombre de chiffres significatifs qu’une chaîne peut parsingr en un point flottant, puis le FP imprimé, en conservant toujours la même présentation de chaîne. Ils sont affichés avec la valeur minimale de la spécification C et un exemple de compilateur C11. Je crois disponible pré-C99.

 FLT_DIG 6, 6 (float) DBL_DIG 10, 15 (double) LDBL_DIG 10, 18 (long double) 

Le premier ensemble de macros semble répondre à l’objective de chiffres significatifs d’OP. Mais cette macro n’est pas toujours disponible.

 #ifdef DBL_DECIMAL_DIG #define OP_DBL_Digs (DBL_DECIMAL_DIG) #else #ifdef DECIMAL_DIG #define OP_DBL_Digs (DECIMAL_DIG) #else #define OP_DBL_Digs (DBL_DIG + 3) #endif #endif 

Le “+ 3” était le noeud de ma réponse précédente. Si on connaissait la chaîne de conversion aller-retour-chaîne-FP (ensemble n ° 2 des macros disponibles en C89), comment déterminerait-on les chiffres pour FP-ssortingng-FP (ensemble de macros n ° 1 disponibles après C89)? En général, append 3 était le résultat.

Maintenant, combien de chiffres significatifs à imprimer sont connus et pilotés via .

Pour imprimer N chiffres décimaux significatifs, on peut utiliser différents formats.

Avec "%e" , le champ de précision est le nombre de chiffres après le chiffre principal et le point décimal. So - 1 est en ordre. Note: Ce -1 is not in the initial int int -1 is not in the initial Digs = DECIMAL_DIG; `

 printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh); // 1.4285714285714285e-01 

Avec "%f" , le champ de précision est le nombre de chiffres après le point décimal. Pour un nombre comme OneSeventh/1000000.0 , il faudrait OP_DBL_Digs + 6 pour voir tous les chiffres significatifs .

 printf("%.*f\n", OP_DBL_Digs , OneSeventh); // 0.14285714285714285 printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0); // 0.00000014285714285714285 

Note: Beaucoup sont utilisés pour "%f" . Cela affiche 6 chiffres après le point décimal; 6 est l’affichage par défaut, pas la précision du nombre.

La réponse courte pour imprimer des nombres à virgule flottante sans perte (de sorte qu’ils puissent être lus exactement au même nombre, sauf NaN et Infinity):

  • Si votre type est float: utilisez printf("%.9g", number) .
  • Si votre type est double: utilisez printf("%.17g", number) .

N’utilisez PAS %f , car cela ne spécifie que le nombre de chiffres significatifs après la décimale et tronque les petits nombres. Pour référence, les nombres magiques 9 et 17 peuvent être trouvés dans float.h qui définit FLT_DECIMAL_DIG et DBL_DECIMAL_DIG .

Si vous êtes uniquement intéressé par le bit (resp modèle hexadécimal), vous pouvez utiliser le format %a . Cela vous garantit:

La précision par défaut suffit pour une représentation exacte de la valeur si une représentation exacte dans la base 2 existe et si elle est suffisamment grande pour distinguer les valeurs de type double.

Je dois append que ce n’est disponible que depuis C99.

Non, il n’y a pas de telle spécificité Printf width pour imprimer en virgule flottante avec une précision maximale . Laissez-moi vous expliquer pourquoi.

La précision maximale de float et double est variable et dépend de la valeur réelle du float ou du double .

Rappelez-vous float et double sont stockés au format sign.exponent.mantissa . Cela signifie qu’il y a beaucoup plus de bits utilisés pour la composante fractionnaire que pour les grands nombres.

entrer la description de l'image ici

Par exemple, float peut facilement distinguer entre 0.0 et 0.1.

 float r = 0; printf( "%.6f\n", r ) ; // 0.000000 r+=0.1 ; printf( "%.6f\n", r ) ; // 0.100000 

Mais float n’a aucune idée de la différence entre 1e27 et 1e27 + 0.1 .

 r = 1e27; printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000 r+=0.1 ; printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000 

En effet, toute la précision (limitée par le nombre de bits de la mantisse) est utilisée pour la plus grande partie du nombre, à gauche de la décimale.

Le modificateur %.f indique simplement le nombre de valeurs décimales que vous souhaitez imprimer à partir du nombre à virgule, dans la mesure où le formatage est correct . Le fait que la précision disponible dépend de la taille du numéro dépend de vous, en tant que programmeur . printf ne peut pas / ne gère pas cela pour vous.

Utilisez simplement les macros de et le spécificateur de conversion de largeur variable ( ".*" ):

 float f = 3.14159265358979323846; printf("%.*f\n", FLT_DIG, f); 

Dans l’un de mes commentaires sur une réponse, j’ai regretté d’avoir longtemps voulu imprimer tous les chiffres significatifs dans une valeur à virgule flottante sous forme décimale, de la même manière que le demande la question. Eh bien je me suis finalement assis et l’ai écrit. Ce n’est pas tout à fait parfait, et c’est un code de démonstration qui imprime des informations supplémentaires, mais cela fonctionne surtout pour mes tests. S’il vous plaît laissez-moi savoir si vous (c.-à-d. N’importe qui) voudrait une copie de l’ensemble du programme wrapper qui le pilote pour le test.

 static unsigned int ilog10(uintmax_t v); /* * Note: As presented this demo code prints a whole line including information * about how the form was arrived with, as well as in certain cases a couple of * interesting details about the number, such as the number of decimal places, * and possibley the magnitude of the value and the number of significant * digits. */ void print_decimal(double d) { size_t sigdig; int dplaces; double flintmax; /* * If we really want to see a plain decimal presentation with all of * the possible significant digits of precision for a floating point * number, then we must calculate the correct number of decimal places * to show with "%.*f" as follows. * * This is in lieu of always using either full on scientific notation * with "%e" (where the presentation is always in decimal format so we * can directly print the maximum number of significant digits * supported by the representation, taking into acount the one digit * represented by by the leading digit) * * printf("%1.*e", DBL_DECIMAL_DIG - 1, d) * * or using the built-in human-friendly formatting with "%g" (where a * '*' parameter is used as the number of significant digits to print * and so we can just print exactly the maximum number supported by the * representation) * * printf("%.*g", DBL_DECIMAL_DIG, d) * * * NB: If we want the printed result to again survive a round-sortingp * conversion to binary and back, and to be rounded to a human-friendly * number, then we can only print DBL_DIG significant digits (instead * of the larger DBL_DECIMAL_DIG digits). * * Note: "flintmax" here refers to the largest consecutive integer * that can be safely stored in a floating point variable without * losing precision. */ #ifdef PRINT_ROUND_TRIP_SAFE # ifdef DBL_DIG sigdig = DBL_DIG; # else sigdig = ilog10(uipow(FLT_RADIX, DBL_MANT_DIG - 1)); # endif #else # ifdef DBL_DECIMAL_DIG sigdig = DBL_DECIMAL_DIG; # else sigdig = (size_t) lrint(ceil(DBL_MANT_DIG * log10((double) FLT_RADIX))) + 1; # endif #endif flintmax = pow((double) FLT_RADIX, (double) DBL_MANT_DIG); /* xxx use uipow() */ if (d == 0.0) { printf("z = %.*s\n", (int) sigdig + 1, "0.000000000000000000000"); /* 21 */ } else if (fabs(d) >= 0.1 && fabs(d) <= flintmax) { dplaces = (int) (sigdig - (size_t) lrint(ceil(log10(ceil(fabs(d)))))); if (dplaces < 0) { /* XXX this is likely never less than -1 */ /* * XXX the last digit is not significant!!! XXX * * This should also be printed with sprintf() and edited... */ printf("R = %.0f [%d too many significant digits!!!, zero decimal places]\n", d, abs(dplaces)); } else if (dplaces == 0) { /* * The decimal fraction here is not significant and * should always be zero (XXX I've never seen this) */ printf("R = %.0f [zero decimal places]\n", d); } else { if (fabs(d) == 1.0) { /* * This is a special case where the calculation * is off by one because log10(1.0) is 0, but * we still have the leading '1' whole digit to * count as a significant digit. */ #if 0 printf("ceil(1.0) = %f, log10(ceil(1.0)) = %f, ceil(log10(ceil(1.0))) = %f\n", ceil(fabs(d)), log10(ceil(fabs(d))), ceil(log10(ceil(fabs(d))))); #endif dplaces--; } /* this is really the "useful" range of %f */ printf("r = %.*f [%d decimal places]\n", dplaces, d, dplaces); } } else { if (fabs(d) < 1.0) { int lz; lz = abs((int) lrint(floor(log10(fabs(d))))); /* ie add # of leading zeros to the precision */ dplaces = (int) sigdig - 1 + lz; printf("f = %.*f [%d decimal places]\n", dplaces, d, dplaces); } else { /* d > flintmax */ size_t n; size_t i; char *df; /* * hmmmm... the easy way to suppress the "invalid", * ie non-significant digits is to do a ssortingng * replacement of all dgits after the first * DBL_DECIMAL_DIG to convert them to zeros, and to * round the least significant digit. */ df = malloc((size_t) 1); n = (size_t) snprintf(df, (size_t) 1, "%.1f", d); n++; /* for the NUL */ df = realloc(df, n); (void) snprintf(df, n, "%.1f", d); if ((n - 2) > sigdig) { /* * XXX rounding the integer part here is "hard" * -- we would have to convert the digits up to * this point back into a binary format and * round that value appropriately in order to * do it correctly. */ if (df[sigdig] >= '5' && df[sigdig] <= '9') { if (df[sigdig - 1] == '9') { /* * xxx fixing this is left as * an exercise to the reader! */ printf("F = *** failed to round integer part at the least significant digit!!! ***\n"); free(df); return; } else { df[sigdig - 1]++; } } for (i = sigdig; df[i] != '.'; i++) { df[i] = '0'; } } else { i = n - 1; /* less the NUL */ if (isnan(d) || isinf(d)) { sigdig = 0; /* "nan" or "inf" */ } } printf("F = %.*s. [0 decimal places, %lu digits, %lu digits significant]\n", (int) i, df, (unsigned long int) i, (unsigned long int) sigdig); free(df); } } return; } static unsigned int msb(uintmax_t v) { unsigned int mb = 0; while (v >>= 1) { /* unroll for more speed... (see ilog2()) */ mb++; } return mb; } static unsigned int ilog10(uintmax_t v) { unsigned int r; static unsigned long long int const PowersOf10[] = { 1LLU, 10LLU, 100LLU, 1000LLU, 10000LLU, 100000LLU, 1000000LLU, 10000000LLU, 100000000LLU, 1000000000LLU, 10000000000LLU, 100000000000LLU, 1000000000000LLU, 10000000000000LLU, 100000000000000LLU, 1000000000000000LLU, 10000000000000000LLU, 100000000000000000LLU, 1000000000000000000LLU, 10000000000000000000LLU }; if (!v) { return ~0U; } /* * By the relationship "log10(v) = log2(v) / log2(10)", we need to * multiply "log2(v)" by "1 / log2(10)", which is approximately * 1233/4096, or (1233, followed by a right shift of 12). * * Finally, since the result is only an approximation that may be off * by one, the exact value is found by subtracting "v < PowersOf10[r]" * from the result. */ r = ((msb(v) * 1233) >> 12) + 1; return r - (v < PowersOf10[r]); }