C printf double dynamicment, pas de perte de précision ni de zéros

Je suis nouveau en C et j’apprends à partir d’un livre / d’Internet. J’essaie d’écrire une fonction à laquelle je peux transmettre n’importe quel double et obtenir un int qui sera utilisé dans un printf("%.*lf" ... déclaration telle que l’ int retourné ne diminuera pas la précision ni ne produira de zéros à la fin. .

J’ai une fonction de travail mais elle est assez grande car elle est écrite pour la lisibilité et commentée.

Pour résumer la fonction, je compte le nombre de divisions par 10 nécessaires pour obtenir le double dans la plage 10 > d >= 0 , ne prends que la partie fractionnaire et la place dans une ssortingng à n décimales où n = 15 - number_of_digits_left_of_decimal ( Je lis que le type double ne peut garder que 15 chiffres), vérifie la ssortingng de droite à gauche pour les zéros de fin et garde le compte, et finalement renvoie un int qui représente le nombre de chiffres non nuls situés à droite de la décimale.

Y a-t-il un moyen plus facile? Merci.

 int get_number_of_digits_after_decimal(double d) { int i = 0; /* sometimes you need an int */ int pl = 0; /* precision left = 15 - sigfigs */ int sigfigs = 1; /* the number of digits in d */ char line[20]; /* used to find last non-zero digit right of the decimal place */ double temp; /* a copy of d used for destructive calculations */ /* find digits to right of decimal */ temp = d; while(sigfigs < 15) { if(temp < 0) temp *= -1; if(temp  temp >= 0 * decrement temp unitl 1 > temp >=0 */ while(temp > 1) { --temp; } if(temp == 0) return(0); pl = 15 - sigfigs; /* if n digits left of decimal, 15-n to right */ switch(pl) { case 14: sprintf(line, "%.14lf", d); break; case 13: sprintf(line, "%.13lf", d); break; case 12: sprintf(line, "%.12lf", d); break; case 11: sprintf(line, "%.11lf", d); break; case 10: sprintf(line, "%.10lf", d); break; case 9: sprintf(line, "%.9f", d); break; case 8: sprintf(line, "%.8lf", d); break; case 7: sprintf(line, "%.7lf", d); break; case 6: sprintf(line, "%.6lf", d); break; case 5: sprintf(line, "%.5lf", d); break; case 4: sprintf(line, "%.4lf", d); break; case 3: sprintf(line, "%.3lf", d); break; case 2: sprintf(line, "%.2lf", d); break; case 1: sprintf(line, "%.1lf", d); break; case 0: return(0); break; } i = (strlen(line) - 1); /* last meaningful digit char */ while(1) /* start at end of ssortingng, move left checking for first non-zero */ { if(line[i] == '0') /* if 0 at end */ { --i; --pl; } else { break; } } return(pl); } 

Il n’y a probablement pas de moyen plus facile. C’est un problème assez complexe.

Votre code ne le résout pas correctement pour plusieurs raisons:

  • La plupart des implémentations pratiques de l’arithmétique en virgule flottante ne sont pas décimales, elles sont binarys. Ainsi, lorsque vous multipliez un nombre à virgule flottante par 10 ou que vous le divisez par 10, vous risquez de perdre de la précision (cela dépend du nombre).
  • Même si le format 64-bit IEEE-754 virgule flottante 64-bit IEEE-754 réserve 53 bits pour la mantisse, ce qui équivaut à floor(log10(2 ^ 53)) = 15 chiffres décimaux, un nombre valide dans ce format peut nécessiter 1080 chiffres décimaux dans la partie décimale lorsqu’ils sont imprimés exactement, c’est ce que vous semblez demander.

Une façon de résoudre ce problème consiste à utiliser le spécificateur de type de format %a dans snprintf() , qui imprimera la valeur à virgule flottante en utilisant des chiffres hexadécimaux pour la mantisse et le standard C de 1999 garantit que tous les chiffres significatifs seront imprimés si le format à virgule flottante est la base 2 (AKA base 2 ou simplement binary). Vous pouvez ainsi obtenir tous les chiffres binarys de la mantisse du nombre. Et à partir de là, vous pourrez déterminer le nombre de chiffres décimaux dans la partie décimale.

Maintenant, observez que:

1,00000 = 2 +0 = 1,00000 (binary)
0.50000 = 2 -1 = 0.10000
0,25000 = 2 -2 = 0,01000
0,12500 = 2 -3 = 0,00100
0,06250 = 2 -4 = 0,00010
0,03125 = 2 -5 = 0,00001

etc.

Vous pouvez clairement voir ici qu’un chiffre binary situé à la i ème position à droite du sharepoint la représentation binary produit le dernier chiffre décimal différent de zéro également à la i ème position située à la droite du sharepoint la représentation décimale.

Ainsi, si vous savez où se trouve le bit non nul le moins significatif dans un nombre à virgule flottante binary, vous pouvez déterminer le nombre de chiffres décimaux nécessaires pour imprimer exactement la partie décimale du nombre.

Et voici ce que fait mon programme.

Code:

 // file: PrintFullFraction.c // // comstack with gcc 4.6.2 or better: // gcc -Wall -Wextra -std=c99 -O2 PrintFullFraction.c -o PrintFullFraction.exe #include  #include  #include  #include  #include  #include  #include  #if FLT_RADIX != 2 #error currently supported only FLT_RADIX = 2 #endif int FractionalDigits(double d) { char buf[ 1 + // sign, '-' or '+' (sizeof(d) * CHAR_BIT + 3) / 4 + // mantissa hex digits max 1 + // decimal point, '.' 1 + // mantissa-exponent separator, 'p' 1 + // mantissa sign, '-' or '+' (sizeof(d) * CHAR_BIT + 2) / 3 + // exponent decimal digits max 1 // ssortingng terminator, '\0' ]; int n; char *pp, *p; int e, lsbFound, lsbPos; // convert d into "+/- 0x h.hhhh p +/- ddd" representation and check for errors if ((n = snprintf(buf, sizeof(buf), "%+a", d)) < 0 || (unsigned)n >= sizeof(buf)) return -1; //printf("{%s}", buf); // make sure the conversion didn't produce something like "nan" or "inf" // instead of "+/- 0x h.hhhh p +/- ddd" if (strstr(buf, "0x") != buf + 1 || (pp = strchr(buf, 'p')) == NULL) return 0; // extract the base-2 exponent manually, checking for overflows e = 0; p = pp + 1 + (pp[1] == '-' || pp[1] == '+'); // skip the exponent sign at first for (; *p != '\0'; p++) { if (e > INT_MAX / 10) return -2; e *= 10; if (e > INT_MAX - (*p - '0')) return -2; e += *p - '0'; } if (pp[1] == '-') // apply the sign to the exponent e = -e; //printf("[%s|%d]", buf, e); // find the position of the least significant non-zero bit lsbFound = lsbPos = 0; for (p = pp - 1; *p != 'x'; p--) { if (*p == '.') continue; if (!lsbFound) { int hdigit = (*p >= 'a') ? (*p - 'a' + 10) : (*p - '0'); // assuming ASCII chars if (hdigit) { static const int lsbPosInNibble[16] = { 0,4,3,4, 2,4,3,4, 1,4,3,4, 2,4,3,4 }; lsbFound = 1; lsbPos = -lsbPosInNibble[hdigit]; } } else { lsbPos -= 4; } } lsbPos += 4; if (!lsbFound) return 0; // d is 0 (integer) // adjust the least significant non-zero bit position // by the base-2 exponent (just add them), checking // for overflows if (lsbPos >= 0 && e >= 0) return 0; // lsbPos + e >= 0, d is integer if (lsbPos < 0 && e < 0) if (lsbPos < INT_MIN - e) return -2; // d isn't integer and needs too many fractional digits if ((lsbPos += e) >= 0) return 0; // d is integer if (lsbPos == INT_MIN && -INT_MAX != INT_MIN) return -2; // d isn't integer and needs too many fractional digits return -lsbPos; } const double testData[] = { 0, 1, // 2 ^ 0 0.5, // 2 ^ -1 0.25, // 2 ^ -2 0.125, 0.0625, // ... 0.03125, 0.015625, 0.0078125, // 2 ^ -7 1.0/256, // 2 ^ -8 1.0/256/256, // 2 ^ -16 1.0/256/256/256, // 2 ^ -24 1.0/256/256/256/256, // 2 ^ -32 1.0/256/256/256/256/256/256/256/256, // 2 ^ -64 3.14159265358979323846264338327950288419716939937510582097494459, 0.1, INFINITY, #ifdef NAN NAN, #endif DBL_MIN }; int main(void) { unsigned i; for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++) { int digits = FractionalDigits(testData[i]); assert(digits >= 0); printf("%f %e %.*f\n", testData[i], testData[i], digits, testData[i]); } return 0; } 

Sortie ( ideone ):

 0.000000 0.000000e+00 0 1.000000 1.000000e+00 1 0.500000 5.000000e-01 0.5 0.250000 2.500000e-01 0.25 0.125000 1.250000e-01 0.125 0.062500 6.250000e-02 0.0625 0.031250 3.125000e-02 0.03125 0.015625 1.562500e-02 0.015625 0.007812 7.812500e-03 0.0078125 0.003906 3.906250e-03 0.00390625 0.000015 1.525879e-05 0.0000152587890625 0.000000 5.960464e-08 0.000000059604644775390625 0.000000 2.328306e-10 0.00000000023283064365386962890625 0.000000 5.421011e-20 0.0000000000000000000542101086242752217003726400434970855712890625 3.141593 3.141593e+00 3.141592653589793115997963468544185161590576171875 0.100000 1.000000e-01 0.1000000000000000055511151231257827021181583404541015625 inf inf inf nan nan nan 0.000000 2.225074e-308 0. 

Vous pouvez voir que π et 0.1 ne sont vrais que jusqu’à 15 chiffres décimaux et le rest des chiffres indique à quoi les nombres sont réellement arrondis, car ils ne peuvent pas être représentés exactement dans un format à virgule flottante binary.

Vous pouvez également voir que DBL_MIN , la plus petite valeur double normalisée positive, a 1022 chiffres dans la partie DBL_MIN et parmi ceux-ci, il y a 715 chiffres significatifs.

Problèmes possibles avec cette solution:

  • Les fonctions printf() votre compilateur ne supportent pas %a ou n’impriment pas correctement tous les chiffres demandés par la précision (c’est tout à fait possible).
  • Votre ordinateur utilise des formats à virgule flottante non binarys (ce qui est extrêmement rare).

La première chose que je remarque est que vous divisez le temp par 10 et que cela cause une perte de précision.

Ne vous fermez pas et ne vous découragez pas d’essayer à nouveau, mais une mise en œuvre correcte est beaucoup plus complexe que ce que vous avez montré.

Guy L. Steele et Jon L. White ont écrit un article intitulé ” Comment imprimer des nombres à virgule flottante avec précision ” qui détaille certaines des embûches et présente un algorithme de travail pour l’impression de nombres à virgule flottante. C’est une bonne lecture.