Spécificateur de largeur Printf pour maintenir la précision de la valeur à virgule flottante

y a-t-il un spécificateur de largeur printf qui peut être appliqué à un spécificateur de virgule flottante qui formaterait automatiquement la sortie au nombre nécessaire de chiffres significatifs de sorte que lors de la numérisation de la chaîne, la valeur initiale de virgule flottante soit acquise?

par exemple, supposons que j'imprime un float avec une précision de 2 décimales:

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

quand je scanne le sortie 0.94 , Je n'ai aucune garantie conforme aux normes que je vais récupérer la valeur originale 0.9375 flottant-point de retour (dans cet exemple, Je ne vais probablement pas).

j'aimerais qu'il y ait une façon de dire printf pour imprimer automatiquement la valeur en virgule flottante au nombre nécessaire de chiffres significatifs pour s'assurer qu'elle puisse être scannée de nouveau à la valeur originale passée à printf .

je pourrais utiliser une partie de la les macros dans float.h à dérivent la largeur maximale pour passer à printf , mais y a-t-il déjà un spécificateur pour imprimer automatiquement au nombre nécessaire de chiffres significatifs -- ou au moins à la largeur maximale?

64
demandé sur Community 2013-05-30 19:04:36

6 réponses

je recommande @Jens Gustedt solution hexadécimale: utiliser %A.

OP veut "impression avec le maximum de précision (ou au moins les plus importantes décimal)".

Un exemple simple serait d'imprimer une septième comme dans:

#include <float.h>
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 sommes finies précision flottante les nombres à virgule. Supposons IEEE 754 double-précision binaire . Ainsi, le OneSeventh = 1.0/7.0 donne la valeur ci-dessous. Sont également indiqués les nombres précédents et suivants de points flottants double .

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

L'impression du exacte représentation décimale d'un double a des utilisations limitées.

C A 2 familles de macros dans <float.h> pour nous aider.

Le premier ensemble est le nombre de significatif chiffres à imprimer dans une chaîne en décimal ainsi lors de la numérisation de la chaîne de retour, on a la pointe flottante d'origine. Y sont présentés avec le C spec minimum "1519350920 valeur" et un échantillon C11 compilateur.

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 une chaîne peut être scannée dans un point flottant et puis le FP imprimé, tout en conservant la même présentation de chaîne de caractères. Y sont présentés avec le C spec minimum "1519350920 valeur" et un échantillon C11 compilateur. Je crois que c'est disponible avant C99.

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

la première série de macros semble répondre à L'objectif de L'OP de chiffres significatifs . Mais ce 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 l'essentiel de ma réponse précédente. Son centré sur si connaissant la chaîne de conversion aller-retour-FP-string (Ensemble #2 macros disponibles C89), comment déterminerait-on les chiffres pour FP-string-FP (ensemble #1 macros disponibles post C89)? En général, ajouter 3 a été le résultat.

maintenant combien important chiffres à imprimer est connu et conduit via <float.h> .

pour imprimer N significatif décimal chiffres Un mai utilisez différents formats.

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

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

Avec "%f" , le précision "1519350920 champ" est le nombre de chiffres après le point décimal. Pour un certain 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" . Qui affiche 6 chiffres après le point décimal; 6 est l'affichage par défaut, pas la précision du nombre.

61
répondu chux 2013-11-11 13:49:38

la réponse courte pour imprimer des nombres à virgule flottante sans perte (de sorte qu'ils peuvent être lus retour 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 tronquera 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 .

46
répondu ccxvii 2014-01-16 12:43:09

si vous êtes seulement intéressé par le bit (resp hex pattern), vous pouvez utiliser le format %a . Cela vous garantit:

le 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 est par ailleurs suffisamment grande pour distinguer les valeurs de type double.

je dois ajouter que ce n'est disponible que depuis C99.

22
répondu Jens Gustedt 2013-05-30 15:32:27

Non, il n'y a pas un tel Printf largeur spécifique à imprimer flottant 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 double .

Rappel float et double sont stockés dans signer.exposant.mantisse format. Cela signifie que il y a beaucoup plus de bits utilisés pour la composante fractionnaire pour les petits nombres que pour les grands nombres.

enter image description here

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

c'est parce que toute la précision (qui est limitée par le nombre de bits de mantissa) est utilisée pour la grande partie du nombre, à gauche de la décimale.

le modificateur %.f indique juste combien de valeurs décimales vous voulez imprimer à partir du nombre de flotteurs aussi loin que formatage va. Le fait que la précision disponible dépend de la taille du nombre est jusqu'à vous comme le programmeur à manipuler. printf ne peut pas/ne peut pas gérer cela pour vous.

11
répondu bobobobo 2013-11-18 13:50:45

utilisez simplement les macros de <float.h> et le spécificateur de conversion de largeur variable ( ".*" ):

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
9
répondu bobobobo 2013-11-10 20:37:13

dans l'un de mes commentaires à une réponse, j'ai déploré que j'ai longtemps voulu une certaine façon d'imprimer tous les chiffres significatifs dans une valeur de virgule flottante sous forme décimale, à peu près de la même manière que la question pose. Je me suis finalement assis et je l'ai écrit. Ce n'est pas tout à fait parfait, et c'est du code démo qui imprime des informations supplémentaires, mais ça marche surtout pour mes tests. Veuillez me faire savoir si vous (c.-à-d. n'importe qui) souhaitez une copie de l'ensemble du programme de 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)
         *
         *
         * N.B.:  If we want the printed result to again survive a round-trip
         * 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)))));
                        /* i.e. 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",
                         * i.e. non-significant digits is to do a string
                         * 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]);
}
0
répondu Greg A. Woods 2016-07-18 03:12:55