128 votes

Éviter les zéros de queue dans printf()

Je continue à buter sur les spécificateurs de format pour la famille de fonctions printf(). Ce que je veux, c'est être capable d'imprimer un double (ou un flottant) avec un nombre maximum donné de chiffres après la virgule. Si j'utilise :

printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);

Je reçois

359.013
359.010

Au lieu de la

359.013
359.01

Quelqu'un peut-il m'aider ?

1 votes

L'inexactitude de la virgule flottante signifie vraiment que vous devez faire l'arrondi vous-même. Prenez une variante de la réponse de R et Juha (qui ne gère pas bien les zéros de fin de ligne) et corrigez-la.

99voto

paxdiablo Points 341644

Cela ne peut pas être fait avec la méthode normale printf les spécificateurs de format. Le plus proche que vous pourriez obtenir serait :

printf("%.6g", 359.013); // 359.013
printf("%.6g", 359.01);  // 359.01

mais le ".6" est le total largeur numérique donc

printf("%.6g", 3.01357); // 3.01357

le brise.

Ce que vous peut faire est de sprintf("%.20g") le nombre dans un tampon de chaîne de caractères, puis manipuler la chaîne pour qu'elle ne comporte que N caractères après la virgule.

En supposant que votre nombre est dans la variable num, la fonction suivante supprimera tous les chiffres sauf le premier N les décimales, puis supprimez les zéros de fin (et le point décimal s'il n'y avait que des zéros).

char str[50];
sprintf (str,"%.20g",num);  // Make the number.
morphNumericString (str, 3);
:    :
void morphNumericString (char *s, int n) {
    char *p;
    int count;

    p = strchr (s,'.');         // Find decimal point, if any.
    if (p != NULL) {
        count = n;              // Adjust for more or less decimals.
        while (count >= 0) {    // Maximum decimals allowed.
             count--;
             if (*p == '\0')    // If there's less than desired.
                 break;
             p++;               // Next character.
        }

        *p-- = '\0';            // Truncate string.
        while (*p == '0')       // Remove trailing zeros.
            *p-- = '\0';

        if (*p == '.') {        // If all decimals were zeros, remove ".".
            *p = '\0';
        }
    }
}

Si vous n'êtes pas satisfait de l'aspect de la troncature (ce qui rendrait 0.12399 en 0.123 plutôt que de l'arrondir à 0.124 ), vous pouvez en fait utiliser les possibilités d'arrondis déjà fournies par l'option printf . Il suffit d'analyser le nombre au préalable pour créer dynamiquement les largeurs, puis de les utiliser pour transformer le nombre en chaîne :

#include <stdio.h>

void nDecimals (char *s, double d, int n) {
    int sz; double d2;

    // Allow for negative.

    d2 = (d >= 0) ? d : -d;
    sz = (d >= 0) ? 0 : 1;

    // Add one for each whole digit (0.xx special case).

    if (d2 < 1) sz++;
    while (d2 >= 1) { d2 /= 10.0; sz++; }

    // Adjust for decimal point and fractionals.

    sz += 1 + n;

    // Create format string then use it.

    sprintf (s, "%*.*f", sz, n, d);
}

int main (void) {
    char str[50];
    double num[] = { 40, 359.01335, -359.00999,
        359.01, 3.01357, 0.111111111, 1.1223344 };
    for (int i = 0; i < sizeof(num)/sizeof(*num); i++) {
        nDecimals (str, num[i], 3);
        printf ("%30.20f -> %s\n", num[i], str);
    }
    return 0;
}

Le but de nDecimals() Dans ce cas, la solution consiste à déterminer correctement la largeur des champs, puis à formater le nombre à l'aide d'une chaîne de format basée sur cette largeur. Le harnais de test main() montre cela en action :

  40.00000000000000000000 -> 40.000
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.010
 359.00999999999999090505 -> 359.010
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122

Une fois que vous avez la valeur correctement arrondie, vous pouvez à nouveau la transmettre à morphNumericString() pour supprimer les zéros de fin de ligne en changeant simplement :

nDecimals (str, num[i], 3);

dans :

nDecimals (str, num[i], 3);
morphNumericString (str, 3);

(ou en appelant morphNumericString à la fin de nDecimals mais, dans ce cas, je combinerais probablement les deux en une seule fonction), et vous obtenez :

  40.00000000000000000000 -> 40
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.01
 359.00999999999999090505 -> 359.01
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122

10 votes

Remarque, cette réponse suppose que la décimale est . Dans certaines localités, la décimale est en fait un , virgule.

1 votes

Il y a en fait une petite coquille ici - p = strchr (str,'.') ; devrait en fait être p = strchr (s,'.') ; pour utiliser le paramètre de la fonction plutôt que la var globale.

0 votes

L'utilisation d'un trop grand nombre de chiffres peut entraîner des résultats inattendus... par exemple "%.20g" avec 0.1 n'affichera rien. Ce que vous devez faire si vous ne voulez pas surprendre les utilisateurs, c'est de commencer par 15 chiffres et de continuer à incrémenter jusqu'à ce que atof rend la même valeur.

68voto

Tomalak Points 150423

Pour se débarrasser des zéros de fin, il faut utiliser le format "%g" :

float num = 1.33;
printf("%g", num); //output: 1.33

Après avoir clarifié un peu la question, on a constaté que la suppression des zéros n'était pas la seule chose demandée, mais que la limitation de la sortie à trois décimales était également requise. Je pense que cela ne peut pas être fait avec les seules chaînes de format sprintf. Comme Pax Diablo a souligné, la manipulation de chaînes de caractères serait nécessaire.

0 votes

0 votes

@Tomalak : Cela ne fait pas ce que je veux. Je veux pouvoir spécifier un nombre maximum de chiffres après le point décimal. C'est-à-dire : Je veux que 1,3347 soit imprimé "1,335" et que 1,3397 soit imprimé "1,34" @xtofl : J'avais déjà vérifié cela, mais je ne vois toujours pas la réponse à mon problème

3 votes

L'auteur veut le style 'f' et non le style 'e'. 'g' peut utiliser le style 'e' : Extrait de la documentation : Le style e est utilisé si l'exposant de sa conversion est inférieur à -4 ou supérieur ou égal à la précision.

20voto

Juha Points 745

J'aime la réponse de R. légèrement modifiée :

float f = 1234.56789;
printf("%d.%.0f", f, 1000*(f-(int)f));

1000' détermine la précision.

Puissance à l'arrondi de 0,5.

EDIT

Ok, cette réponse a été modifiée plusieurs fois et j'ai perdu le fil de ce que je pensais il y a quelques années (et à l'origine, elle ne remplissait pas tous les critères). Voici donc une nouvelle version (qui remplit tous les critères et gère correctement les nombres négatifs) :

double f = 1234.05678900;
char s[100]; 
int decimals = 10;

sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
printf("10 decimals: %d%s\n", (int)f, s+1);

Et les cas de test :

#import <stdio.h>
#import <stdlib.h>
#import <math.h>

int main(void){

    double f = 1234.05678900;
    char s[100];
    int decimals;

    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf("10 decimals: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" 3 decimals: %d%s\n", (int)f, s+1);

    f = -f;
    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative 10: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative  3: %d%s\n", (int)f, s+1);

    decimals = 2;
    f = 1.012;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" additional : %d%s\n", (int)f, s+1);

    return 0;
}

Et le résultat des tests :

 10 decimals: 1234.056789
  3 decimals: 1234.057
 negative 10: -1234.056789
 negative  3: -1234.057
 additional : 1.01

Maintenant, tous les critères sont remplis :

  • le nombre maximum de décimales derrière le zéro est fixé
  • les zéros de fin de ligne sont supprimés
  • il le fait mathématiquement bien (n'est-ce pas ?)
  • fonctionne (maintenant) aussi quand la première décimale est zéro

Malheureusement, cette réponse est à double sens car sprintf ne renvoie pas la chaîne de caractères.

1 votes

Cela ne semble pas vraiment couper les zéros de fin de ligne, cependant ? Il peut volontiers sortir quelque chose comme 1.1000.

0 votes

La question et ma réponse ne sont plus les originaux... Je vérifierai plus tard.

1 votes

Cas de test échoués : 1.012 avec le formateur "%.2g" donnera 1.012 au lieu de 1.01.

2voto

R.. Points 93718

Que diriez-vous de quelque chose comme ceci (il peut y avoir des erreurs d'arrondi et des problèmes de valeurs négatives qui nécessitent un débogage, laissé comme un exercice pour le lecteur) :

printf("%.0d%.4g\n", (int)f/10, f-((int)f-(int)f%10));

Il est légèrement programmatique, mais au moins il ne vous fait pas faire de manipulation de chaîne.

2voto

DaveR Points 21

Je recherche dans la chaîne (en commençant par la plus à droite) le premier caractère de l'intervalle 1 a 9 (valeur ASCII 49 - 57 ) alors null (réglé sur 0 ) chaque char à droite de celui-ci - voir ci-dessous :

void stripTrailingZeros(void) { 
    //This finds the index of the rightmost ASCII char[1-9] in array
    //All elements to the left of this are nulled (=0)
    int i = 20;
    unsigned char char1 = 0; //initialised to ensure entry to condition below

    while ((char1 > 57) || (char1 < 49)) {
        i--;
        char1 = sprintfBuffer[i];
    }

    //null chars left of i
    for (int j = i; j < 20; j++) {
        sprintfBuffer[i] = 0;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X