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

103

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

Par exemple, supposons que j'imprime a floatavec une précision de 2décimales:

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

Lorsque je scanne la sortie 0.94, je n'ai aucune garantie conforme aux normes que j'obtiendrai l'original0.9375 virgule flottante d' (dans cet exemple, je ne le ferai probablement pas).

Je voudrais un moyen printfd'imprimer automatiquement la valeur à virgule flottante sur le nombre nécessaire de chiffres significatifs pour s'assurer qu'elle peut être numérisée à la valeur d'origine transmise àprintf .

Je pourrais utiliser certaines des macros float.hpour dériver la largeur maximale à laquelle passer printf, mais existe-t-il déjà un spécificateur pour imprimer automatiquement le nombre nécessaire de chiffres significatifs - ou du moins jusqu'à la largeur maximale?

Vilhelm Gris
la source
4
@bobobobo Vous recommandez donc
1
@ H2CO3 Non, je ne recommanderais pas d'utiliser "une hypothèse hors de l'air", je suggérerais d'utiliser printf( "%f", val );ce qui est déjà portable, efficace et par défaut.
bobobobo
2
@bobobobo Pour que je puisse l'ajouter aux réponses, seriez-vous en mesure de citer la clause de la norme C99 qui stipule que l'instruction printf affichera le type float avec une précision maximale par défaut si aucune précision n'est spécifiée?
Vilhelm Gray du
1
@VilhelmGray Eh bien, comme @chux entre en jeu, il y a des calculs assez compliqués quant à la précision réelle de votre particulier double. Comme votre doubledevient extrêmement grand (très loin de 1.0), il devient en fait moins précis dans la partie décimale (partie de valeur inférieure à 1.0). Donc, vous ne pouvez pas vraiment avoir une réponse satisfaisante ici, car votre question contient une fausse hypothèse (à savoir que tous les floats / doubles sont créés égaux)
bobobobo
2
@Vilhelm Gray C11dr 5.2.4.2.2 "... nombre de chiffres décimaux, n, tel que tout nombre à virgule flottante avec p radix b chiffres peut être arrondi à un nombre à virgule flottante avec n chiffres décimaux et inversement sans changement à la valeur, p log10 bb est une puissance de 10 ⎡1 + p log10 b⎤ sinon FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ... "Les 6,10,10 sont les valeurs minimales .
chux

Réponses:

92

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

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

Un exemple simple serait d'imprimer un 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 utilisons des nombres à virgule flottante de précision finie. Supposons que le binaire double précision IEEE 754 . Donc, les OneSeventh = 1.0/7.0résultats dans la valeur ci-dessous. Les doublenombres à virgule flottante représentables précédents et suivants sont également représentés .

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

L'impression de la représentation décimale exacte de a doublea des utilisations limitées.

C a 2 familles de macros <float.h>pour nous aider.
Le premier ensemble est le nombre de chiffres significatifs à imprimer dans une chaîne en décimal, donc lors de la numérisation de la chaîne, nous obtenons la virgule flottante 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 être balayée en virgule flottante, 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 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 atteindre l'objectif de OP significatif chiffres . 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 au cœur de ma réponse précédente. Son centré sur la connaissance de la conversion aller-retour string-FP-string (set # 2 macros disponibles C89), comment déterminer les chiffres pour FP-string-FP (set # 1 macros disponibles après C89)? En général, ajouter 3 était le résultat.

Maintenant combien chiffres significatifs à imprimer est connu et piloté <float.h>.

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

Avec "%e", le champ de précision correspond au nombre de chiffres après le chiffre principal et la virgule décimale. Il en - 1va de même. Remarque: ce -1n'est pas dans leint Digs = DECIMAL_DIG;

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

Avec "%f", le champ de précision correspond au nombre de chiffres après la virgule décimale. Pour un nombre comme OneSeventh/1000000.0, il faudrait OP_DBL_Digs + 6voir 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

Remarque: Beaucoup sont habitués à "%f". Cela affiche 6 chiffres après la virgule décimale; 6 est la valeur d'affichage par défaut, pas la précision du nombre.

chux - Réintégrer Monica
la source
pourquoi 1.428571428571428492127e-01 et non 1.428571428571428492127e-0 0 1, le nombre de chiffres après 'e' devrait être 3?
user1024
12.12.5 Conversions à virgule flottante indique que la précision par défaut %fest de 6.
Jingguo Yao
1
@Jingguo Yao D'accord que la référence dit "La précision spécifie combien de chiffres suivent le caractère décimal pour le '% f'". Le mot «précision» n'est pas utilisé dans un sens mathématique, mais simplement pour définir le nombre de chiffres après la virgule décimale. 1234567890.123, comporte mathématiquement 13 chiffres de précision ou chiffres significatifs. 0.000000000123 a 3 chiffres de précision mathématique, et non 13. Les nombres à virgule flottante sont distribués logarithmiquement. Cette réponse utilise des chiffres significatifs et le sens mathématique de la précision .
chux - Réintégrer Monica
1
@Slipp D. Thompson "Ils sont affichés avec la valeur minimale de la spécification C et un exemple de compilateur C11."
chux - Réintégrer Monica le
1
En effet, vous avez raison - mon astuce n'est valable que pour les valeurs d'une magnitude comprise entre 1.0 et 1.0eDBL_DIG, qui est sans doute la seule plage vraiment appropriée pour l'impression avec "%f"en premier lieu. Utiliser "%e"comme vous l'avez montré est bien sûr une meilleure approche globale et effectivement une réponse décente (bien que ce ne soit peut-être pas aussi bon que l'utilisation "%a"pourrait l'être s'il est disponible, et bien sûr "%a"devrait être disponible si `DBL_DECIMAL_DIG l'est). J'ai toujours souhaité un spécificateur de format qui arrondirait toujours exactement à la précision maximale (au lieu des 6 décimales codées en dur).
Greg A. Woods
66

La réponse courte pour imprimer les 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).

NE PAS utiliser %f, car cela spécifie uniquement 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.hlequel définit FLT_DECIMAL_DIGet DBL_DECIMAL_DIG.

ccxvii
la source
6
Seriez-vous en mesure d'expliquer le %gprescripteur?
Vilhelm Gray
14
% g imprime le nombre avec autant de chiffres que nécessaire pour la précision, préférant la syntaxe exponentielle lorsque les nombres sont petits ou énormes (1e-5 au lieu de .00005) et sautant les zéros de fin (1 plutôt que 1.00000).
ccxvii
4
@truthseeker Pour représenter un code binaire64 IEEE 754, il faut en effet imprimer au moins 15 décimales significatives. Mais la non-ambiguïté a besoin de 17 car la précision change dans un nombre binaire (à 2,4,8, etc.) et un nombre décimal (à 10,100,1000, etc.) ne sont jamais au même nombre (sauf 1.0). Exemple: les 2 doublevaleurs juste au- dessus 0.1: 1.000_0000_0000_0000_2e-01, 1.000_0000_0000_0000_3e-01besoin de 17 chiffres pour distinguer.
chux - Réintégrer Monica le
3
@chux - Vous vous trompez sur le comportement de% .16g; cela ne convient pas pour votre exemple de distinction entre 1.000_0000_0000_0000_2e-01 et 1.000_0000_0000_0000_3e-01. % .17g est nécessaire.
Don Hatch le
1
@Don Hatch Je suis d' accord "%.16g"est insuffisant et "%.17g"et "%.16e"sont suffisantes. Les détails de %g, ont été mal rappelés par moi.
chux - Réintégrer Monica le
23

Si vous n'êtes intéressé que par le bit (motif hexadécimal resp), vous pouvez utiliser le %aformat. Cela vous garantit:

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

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

Jens Gustedt
la source
16

Non, il n'existe pas de spécificateur de largeur printf pour imprimer en virgule flottante avec une précision maximale . Laissez-moi vous expliquer pourquoi.

La précision maximale de floatet doubleest variable et dépend de la valeur réelle de floatou double.

Rappel floatet doublesont stockés au format sign.exponent.mantissa . Cela signifie qu'il y a beaucoup plus de bits utilisés pour la composante fractionnaire pour les petits nombres que pour les grands nombres.

entrez la description de l'image ici

Par exemple, floatpeut 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 floatn'a aucune idée de la différence entre 1e27et 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 (qui est limitée par le nombre de bits de mantisse) est utilisée pour la grande partie du nombre, à gauche de la décimale.

Le %.fmodificateur indique simplement le nombre de valeurs décimales que vous souhaitez imprimer à partir du nombre flottant en ce qui concerne le formatage . Le fait que la précision disponible dépende de la taille du nombre est à vous en tant que programmeur à gérer. printfne peut pas / ne gère pas cela pour vous.

bobobobo
la source
2
C'est une excellente explication des limites de l'impression précise des valeurs en virgule flottante à des décimales spécifiques. Cependant, je pense que j'étais trop ambigu avec mon choix initial de mots, j'ai donc mis à jour ma question pour éviter le terme «précision maximale» dans l'espoir qu'il puisse dissiper la confusion.
Vilhelm Gray
Cela dépend toujours de la valeur du numéro que vous imprimez.
bobobobo
3
c'est en partie vrai, mais cela ne répond pas à la question et vous ne savez pas ce que OP demande. Il demande si l'on peut interroger le nombre de chiffres [décimaux] significatifs floatfournis par un , et vous affirmez qu'il n'y a rien de tel (c'est-à-dire qu'il n'y en a pas FLT_DIG), ce qui est faux.
@ H2CO3 Vous devriez peut-être éditer mon message et voter contre (j / k). Cette réponse affirme FLT_DIGne veut rien dire. Cette réponse affirme que le nombre de décimales disponibles dépend de la valeur à l'intérieur du flottant .
bobobobo
1
Pensez-vous que la lettre de format doit être "f"? Je ne pense pas que ce soit nécessaire. Ma lecture de la question est que l'OP recherche un spécificateur de format printf qui produit un aller-retour sans perte, donc la réponse de @ccxvii ("% .9g" pour float, "% .17g" pour double) est un bon. La question serait probablement mieux formulée en supprimant le mot «largeur».
Don Hatch
11

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

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
bobobobo
la source
2
@OliCharlesworth Voulez-vous dire comme ça:printf("%." FLT_DIG "f\n", f);
Vilhelm Gray
3
+1 mais cela fonctionne mieux pour %e, pas si bien pour %f: seulement s'il sait que la valeur à imprimer est proche 1.0.
Pascal Cuoq
3
%eimprime les chiffres significatifs pour les très petits nombres et %fne le fait pas. par exemple x = 1e-100. %.5fempreintes 0.00000(une perte totale de précession). %.5eimpressions 1.00000e-100.
chux
1
@bobobobo De plus, vous vous trompez en ce sens que cela "donne des raisons plus précises". FLT_DIGest défini à la valeur à laquelle il est défini pour une raison. Si c'est 6, c'est parce qu'il floatne peut pas contenir plus de 6 chiffres de précision. Si vous l'imprimez avec %.7f, le dernier chiffre n'aura aucune signification. Réfléchissez avant de voter contre.
5
@bobobobo Non, %.6fn'est pas équivalent, parce que ce FLT_DIGn'est pas toujours 6. Et qui se soucie de l'efficacité? Les E / S sont déjà chères, un chiffre plus ou moins précis ne fera pas de goulot d'étranglement.
5

Je lance une petite expérience pour vérifier que l'impression avec DBL_DECIMAL_DIGconserve en effet exactement la représentation binaire du nombre. Il s'est avéré que pour les compilateurs et les bibliothèques C que j'ai essayés, DBL_DECIMAL_DIGc'est bien le nombre de chiffres requis, et l'impression avec même un chiffre de moins crée un problème important.

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

union {
    short s[4];
    double d;
} u;

void
test(int digits)
{
    int i, j;
    char buff[40];
    double d2;
    int n, num_equal, bin_equal;

    srand(17);
    n = num_equal = bin_equal = 0;
    for (i = 0; i < 1000000; i++) {
        for (j = 0; j < 4; j++)
            u.s[j] = (rand() << 8) ^ rand();
        if (isnan(u.d))
            continue;
        n++;
        sprintf(buff, "%.*g", digits, u.d);
        sscanf(buff, "%lg", &d2);
        if (u.d == d2)
            num_equal++;
        if (memcmp(&u.d, &d2, sizeof(double)) == 0)
            bin_equal++;
    }
    printf("Tested %d values with %d digits: %d found numericaly equal, %d found binary equal\n", n, digits, num_equal, bin_equal);
}

int
main()
{
    test(DBL_DECIMAL_DIG);
    test(DBL_DECIMAL_DIG - 1);
    return 0;
}

Je lance cela avec le compilateur C de Microsoft 19.00.24215.1 et la version gcc 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1). L'utilisation d'un chiffre décimal en moins réduit de moitié le nombre de nombres qui se comparent exactement égaux. (J'ai également vérifié que rand()tel qu'utilisé produit en effet environ un million de nombres différents.) Voici les résultats détaillés.

Microsoft C

999507 valeurs testées avec 17 chiffres: 999507 trouvée numériquement égale, 999507 trouvée binaire égale
Testé 999507 valeurs avec 16 chiffres: 545389 trouvé numériquement égal, 545389 trouvé binaire égal

GCC

999485 valeurs testées avec 17 chiffres: 999485 trouvée numériquement égale, 999485 trouvée binaire égale
999485 valeurs testées avec 16 chiffres: 545402 trouvé égal en nombre, 545402 trouvé égal en binaire
Diomidis Spinellis
la source
1
"Exécutez ceci avec le compilateur C de Microsoft" -> Ce compilateur peut avoir RAND_MAX == 32767. Considérez u.s[j] = (rand() << 8) ^ rand();ou similaire pour vous assurer que tous les bits ont une chance d'être 0 ou 1.
chux - Réintègre Monica
En effet, son RAND_MAX est 32767, donc votre proposition est correcte.
Diomidis Spinellis
1
J'ai mis à jour le message pour gérer RAND_MAX comme suggéré par @ chux-ReinstateMonica. Les résultats sont similaires à ceux obtenus auparavant.
Diomidis Spinellis
3

Dans l'un de mes commentaires à une réponse, j'ai déploré le fait que je souhaitais depuis longtemps un moyen d'imprimer tous les chiffres significatifs sous forme de virgule flottante sous forme décimale, à peu près de la même manière que la question le demande. Eh bien, je me suis finalement assis et je 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 principalement pour mes tests. S'il vous plaît laissez-moi savoir si vous (c'est-à-dire quelqu'un) souhaitez 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)
         *
         *
         * 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]);
}
Greg A. Woods
la source
Je me fiche de savoir si cela répond à la question ou non - c'est vraiment impressionnant à faire. Il a fallu réfléchir et doit être reconnu et félicité. Peut-être que ce serait bien si vous incluiez d'une manière ou d'une autre (que ce soit ici ou ailleurs) le code complet pour les tests, mais même sans cela, c'est vraiment un bon travail. Ayez un +1 pour ça!
Pryftan
0

À ma connaissance, il existe un algorithme bien diffusé permettant de sortir le nombre nécessaire de chiffres significatifs de telle sorte que lors du balayage de la chaîne, la valeur en virgule flottante d'origine est acquise en dtoa.cécriture par Daniel Gay, qui est disponible ici sur Netlib (voir également le papier associé ). Ce code est utilisé par exemple en Python, MySQL, Scilab et bien d'autres.

Stéphane Mottelet
la source