Comment cacher le zéro non significatif dans printf

Les sorties suivantes 0.23 . Comment puis-je l’obtenir simplement en sortie .23 ?

 printf( "%8.2f" , .23 ); 

La norme C stipule que, pour les spécificateurs de format à virgule flottante f et F :

Si un caractère séparateur décimal apparaît, au moins un chiffre apparaît devant celui-ci.

Je pense que si vous ne voulez pas qu’un zéro apparaisse avant le point décimal, vous devrez probablement faire quelque chose comme utiliser snprintf() pour formater le nombre en une chaîne et supprimer le 0 si la chaîne formatée commence par ” 0. ” (et pareillement pour “-0.”). Puis passez cette chaîne formatée à notre sortie réelle. Ou quelque chose comme ça.

Il n’est pas possible de le faire uniquement en utilisant printf . La documentation pour printf dit:

 f - "double" argument is output in conventional form, ie [-]mmmm.nnnnnn The default number of digits after the decimal point is six, but this can be changed with a precision field. If a decimal point appears, at least one digit appears before it. The "double" value is rounded to the correct number of decimal places. 

Notez que si un point décimal apparaît, il y a au moins un chiffre devant celui-ci .

Par conséquent, il semble que vous deviez coder manuellement votre propre formateur.

 double f = 0.23; assert(f < 0.995 && f >= 0); printf(".%02u\n" , (unsigned)((f + 0.005) * 100)); 
 #include  static void printNoLeadingZeros(double theValue) { char buffer[255] = { '\0' }; sprintf(buffer, "%.2f", theValue); printf("%s\n", buffer + (buffer[0] == '0')); } int main() { double values[] = { 0.23, .23, 1.23, 01.23, 001.23, 101.23 }; int n = sizeof(values) / sizeof(values[0]); int i = 0; while(i < n) printNoLeadingZeros(values[i++]); return(0); } 

Il suffit de le convertir en un entier avec la précision requirejse

 double value = .12345678901; // input int accuracy = 1000; // 3 digit after dot printf(".%d\n", (int)(value * accuracy) ); 

Sortie:

 .123 

exemple de source sur pastebin

La bibliothèque Standard C ne fournit pas cela, vous devez donc l’écrire vous-même. Ce n’est pas une exigence rare et unique. Vous aurez besoin d’écrire des fonctions similaires tôt ou tard pour supprimer les zéros et les séparateurs de milliers. Il est donc avantageux non seulement d’obtenir les octets de sortie que vous recherchez, mais aussi d’illustrer plus généralement comment écrire une bibliothèque solide. Ce faisant, gardez à l’esprit:

  1. comprendre comment vous voulez l’appeler. Quelque chose comme cela, vous écrivez une fois mais appelez des millions de fois, alors appelez-le aussi facilement que possible.

  2. puis faites la suite de tests exerçant toutes les alternatives que vous pouvez penser

  3. pendant que vous y êtes, résolvez simplement le problème pour ne plus avoir à y revenir (par exemple, ne codez pas en largeur, précision, allez-y et créez des versions pour lead-plus, format électronique, etc. )

  4. sécurisez les threads même si vous n’utilisez pas de threads (un cas spécifique du point 3, en fait)

Donc, travailler en arrière: la sécurité des threads nécessite d’allouer de la mémoire sur la stack, ce qui doit être fait à partir de l’appelant. Ce n’est pas joli ou amusant, mais juste s’y habituer. C’est la voie C. Les formats peuvent avoir une largeur, une précision, des drapeaux et un type de conversion (f, e, g). Faisons donc des parameters de largeur et de précision. Plutôt que de paramétrer entièrement l’API publique, j’aurais simplement plusieurs points d’entrée indiquant dans le nom de la fonction les indicateurs et le type de conversion utilisés.

Une bête noire, c’est que lorsqu’elle passe dans les tampons aux fonctions, celle-ci doit connaître la taille. Mais si vous faites de ce paramètre un paramètre distinct, vous aurez du mal à: 1) l’appelant doit l’écrire et 2) l’appelant peut se tromper. Mon style personnel consiste donc à créer une macro de masquage qui suppose que la mémoire tampon est un tableau de caractères, pas un pointeur, et qui utilise sizeof () pour passer la taille à une version plus détaillée de la fonction prenant la taille.

Voici la maquette de la manière la plus simple à laquelle je puisse penser, avec des cas de test.

(Remarque: COUNT () est une macro que j’utilise toutes les semaines depuis des décennies pour obtenir le nombre d’éléments d’un tableau. Standard C aurait dû ressembler à ceci.)

(Notez que j’utilise un dialecte de “Notation hongroise” ici. “D” est un double. “A” est un “tableau de”. “Sz” est un tampon de chaîne terminé par NUL, tandis que “psz” est un pointeur sur un. La différence entre les deux est que “sz” peut être utilisé avec COUNT () ou sizeof () pour obtenir la taille du tableau, tandis que “psz” ne le peut pas. “I” est un entier et la variable spécifique “i” est utilisée pour la mise en boucle. .

 double ad[] = { 0.0, 1.0, 2.2, 0.3, 0.45, 0.666, 888.99, -1.0, -2.2, -0.3, -0.45, -0.666, -888.99 }; char szBuf[20]; for ( int i = 0; i < COUNT( ad ); i++ ) printf( "%s\n", NoLeadingZeroF( 4, 2, ad[i], szBuf ) ); for ( int i = 0; i < COUNT( ad ); i++ ) printf( "%s\n", NoLeadingZeroPlusF( 4, 2, ad[i], szBuf ) ); 

Maintenant, les versions "f" et "+ f" semblent très similaires, alors appelons-les à une fonction interne. Voici les fonctions qui prennent la taille du tampon et les macros qui le déterminent elles-mêmes. (Les fonctions parallèles sont également écrites pour les formats e et g.)

 char* NoLeadingZeroFN( int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) { return NoLeadingZeroFmtN( "%*.*f", iWidth, iPrecision, d, szBuf, iBufLen ); } char* NoLeadingZeroPlusFN( int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) { return NoLeadingZeroFmtN( "%+*.*f", iWidth, iPrecision, d, szBuf, iBufLen ); } #define NoLeadingZeroF( width, precision, number, buf ) \ NoLeadingZeroFN( ( width ), (precision ), ( number ), ( buf ), sizeof( buf ) ) #define NoLeadingZeroPlusF( width, precision, number, buf ) \ NoLeadingZeroPlusFN( ( width ), (precision ), ( number ), ( buf ), sizeof( buf ) ) 

Enfin la fonction (interne) qui fait le travail. Notez que snprintf () nécessite un trait de soulignement ajouté sous Windows, mais pas sous Unix.

 char* NoLeadingZeroFmtN( char* szFmt, int iWidth, int iPrecision, double d, char* szBuf, int iBufLen ) { #ifdef WIN32 _snprintf( szBuf, iBufLen - 1, szFmt, iWidth, iPrecision, d ); #else snprintf( szBuf, iBufLen - 1, szFmt, iWidth, iPrecision, d ); #endif // Some snprintf()'s do not promise to NUL-terminate the ssortingng, so do it ourselves. szBuf[ iBufLen - 1 ] = '\0'; // _snprintf() returns the length actually produced, IF the buffer is big enough. // But we don't know it was, so measure what we actually got. char* pcTerminator = strchr( szBuf, '\0' ); for ( char* pcBuf = szBuf; *pcBuf && *pcBuf != '.'; pcBuf++ ) if ( *pcBuf == '0' ) { memmove( pcBuf, pcBuf + 1, pcTerminator - pcBuf ); break; } return szBuf; } 

La sortie est:

 .00 1.00 2.20 .30 .45 .67 888.99 -1.00 -2.20 -.30 -.45 -.67 -888.99 +.00 +1.00 +2.20 +.30 +.45 +.67 +888.99 -1.00 -2.20 -.30 -.45 -.67 -888.99 

Des tests supplémentaires doivent vérifier que les fonctions fonctionnent avec des tampons trop petits.

Il semble qu’il n’y ait pas de solution facile. J’utiliserais probablement quelque chose comme du code ci-dessous. Ce n’est pas la méthode la plus rapide, mais cela devrait fonctionner avec de nombreux formats différents. Il conserve également le nombre de caractères et la position du point.

 #include  void fixprint(char *s) { size_t i; i = 1; while (s[i]=='0' || s[i]==' ' || s[i]=='+' || s[i]=='-') { if (s[i]=='0') s[i]=' '; i++; } } int main() { float x = .23; char s[14]; sprintf(s,"% 8.2f",x); fixprint(s); printf("%s\n",s); }