web-dev-qa-db-fra.com

Evitez les zéros de fin dans printf ()

Je n'arrête pas de trébucher sur les spécificateurs de format pour la famille de fonctions printf (). Ce que je veux, c'est pouvoir imprimer un double (ou un float) avec un nombre maximum de chiffres après le point décimal. Si j'utilise:

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

Je reçois

359.013
359.010

Au lieu du désiré

359.013
359.01

Quelqu'un peut-il m'aider?

102
Gorpik

Cela ne peut pas être fait avec les spécificateurs de format printf normaux. 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 casse.

Ce que vous pouvez faites, c’est de sprintf("%.20g") le numéro d’un tampon de chaîne, puis manipulez la chaîne pour n’avoir que N caractères après le point décimal.

En supposant que votre nombre soit dans la variable num, la fonction suivante supprimera toutes les décimales sauf les premières décimales N, puis supprimera les zéros de fin (et le point décimal s'il s'agit de 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 troncature (qui transformerait 0.12399 En 0.123 Plutôt que de l'arrondir à 0.124), Vous pouvez utiliser les fonctions d'arrondi déjà fournies par printf. Il vous 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 entier de nDecimals() dans ce cas est de calculer correctement la largeur des champs, puis de formater le nombre à l'aide d'une chaîne de format basée sur celle-ci. Le faisceau de test main() le montre 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 correctement arrondi la valeur, vous pouvez à nouveau le transmettre à morphNumericString() pour supprimer les zéros de fin en modifiant 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 obtiendrez:

  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
80
paxdiablo

Pour supprimer les zéros de fin, vous devez utiliser le format "% g":

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

Après que la question ait été clarifiée un peu, la suppression des zéros n’est pas la seule chose qui a été posée, mais il a également été nécessaire de limiter la sortie à trois décimales. Je pense que cela ne peut pas être fait avec les chaînes de format sprintf seules. Comme Pax Diablo l'a fait remarquer, une manipulation de chaîne serait nécessaire.

52
Tomalak

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 arrondie à 0,5.

EDIT

Ok, cette réponse a été modifiée plusieurs fois et j’ai perdu la trace de ce que je pensais il ya 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 la sortie 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 à la fin sont supprimés
  • ça le fait mathématiquement juste (non?)
  • fonctionne (maintenant) même lorsque la première décimale est zéro

Malheureusement, cette réponse est une double ligne car sprintf ne renvoie pas la chaîne.

18
Juha

Qu'en est-il quelque chose comme ceci (peut avoir des erreurs d'arrondi et des problèmes de valeur négative nécessitant un débogage, laissé comme exercice pour le lecteur):

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

C'est un peu programmatique mais au moins, cela ne vous oblige pas à manipuler des chaînes.

2
R..

Je recherche dans la chaîne (en partant de l'extrême droite) le premier caractère de la plage 1 à 9 _ (Valeur ASCII 49-57) puis null (défini sur 0) chaque caractère à sa droite - 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;
    }
}
2
DaveR

Une solution simple mais qui accomplit le travail, attribue une longueur et une précision connues et évite les risques de passer au format exponentiel (ce qui présente un risque lorsque vous utilisez% g):

// Since we are only interested in 3 decimal places, this function
// can avoid any potential miniscule floating point differences
// which can return false when using "=="
int DoubleEquals(double i, double j)
{
    return (fabs(i - j) < 0.000001);
}

void PrintMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%.2f", d);
    else
        printf("%.3f", d);
}

Ajoutez ou supprimez "elses" si vous voulez un maximum de 2 décimales; 4 décimales; etc.

Par exemple, si vous vouliez 2 décimales:

void PrintMaxTwoDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else
        printf("%.2f", d);
}

Si vous souhaitez spécifier la largeur minimale pour garder les champs alignés, incrémentez-le si nécessaire, par exemple:

void PrintAlignedMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%7.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%9.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%10.2f", d);
    else
        printf("%11.3f", d);
}

Vous pouvez également convertir cela en une fonction dans laquelle vous passez la largeur souhaitée du champ:

void PrintAlignedWidthMaxThreeDecimal(int w, double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%*.0f", w-4, d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%*.1f", w-2, d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%*.2f", w-1, d);
    else
        printf("%*.3f", w, d);
}
1
Iaijutsu

J'ai trouvé des problèmes dans certaines des solutions affichées. J'ai mis cela ensemble sur la base des réponses ci-dessus. Il semble fonctionner pour moi.

int doubleEquals(double i, double j) {
    return (fabs(i - j) < 0.000001);
}

void printTruncatedDouble(double dd, int max_len) {
    char str[50];
    int match = 0;
    for ( int ii = 0; ii < max_len; ii++ ) {
        if (doubleEquals(dd * pow(10,ii), floor(dd * pow(10,ii)))) {
            sprintf (str,"%f", round(dd*pow(10,ii))/pow(10,ii));
            match = 1;
            break;
        }
    }
    if ( match != 1 ) {
        sprintf (str,"%f", round(dd*pow(10,max_len))/pow(10,max_len));
    }
    char *pp;
    int count;
    pp = strchr (str,'.');
    if (pp != NULL) {
        count = max_len;
        while (count >= 0) {
             count--;
             if (*pp == '\0')
                 break;
             pp++;
        }
        *pp-- = '\0';
        while (*pp == '0')
            *pp-- = '\0';
        if (*pp == '.') {
            *pp = '\0';
        }
    }
    printf ("%s\n", str);
}

int main(int argc, char **argv)
{
    printTruncatedDouble( -1.999, 2 ); // prints -2
    printTruncatedDouble( -1.006, 2 ); // prints -1.01
    printTruncatedDouble( -1.005, 2 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.005, 2 ); // prints 1 (should be 1.01?)
    printTruncatedDouble( 1.006, 2 ); // prints 1.01
    printTruncatedDouble( 1.999, 2 ); // prints 2
    printf("\n");
    printTruncatedDouble( -1.999, 3 ); // prints -1.999
    printTruncatedDouble( -1.001, 3 ); // prints -1.001
    printTruncatedDouble( -1.0005, 3 ); // prints -1.001 (shound be -1?)
    printTruncatedDouble( -1.0004, 3 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.0004, 3 ); // prints 1
    printTruncatedDouble( 1.0005, 3 ); // prints 1.001
    printTruncatedDouble( 1.001, 3 ); // prints 1.001
    printTruncatedDouble( 1.999, 3 ); // prints 1.999
    printf("\n");
    exit(0);
}
1
magnusviri

Certaines des solutions très votées suggèrent le %g spécificateur de conversion de printf. C'est faux car il y a des cas où %g produira une notation scientifique. D'autres solutions utilisent les mathématiques pour imprimer le nombre souhaité de chiffres décimaux.

Je pense que la solution la plus simple est d’utiliser sprintf avec le %f spécificateur de conversion et pour supprimer manuellement les zéros de fin et éventuellement un point décimal du résultat. Voici une solution C99:

#include <stdio.h>
#include <stdlib.h>

char*
format_double(double d) {
    int size = snprintf(NULL, 0, "%.3f", d);
    char *str = malloc(size + 1);
    snprintf(str, size + 1, "%.3f", d);

    for (int i = size - 1, end = size; i >= 0; i--) {
        if (str[i] == '0') {
            if (end == i + 1) {
                end = i;
            }
        }
        else if (str[i] == '.') {
            if (end == i + 1) {
                end = i;
            }
            str[end] = '\0';
            break;
        }
    }

    return str;
}

Notez que les caractères utilisés pour les chiffres et le séparateur décimal dépendent des paramètres régionaux en cours. Le code ci-dessus suppose une locale C ou US English.

1
nwellnhof

Légère variation ci-dessus:

  1. Élimine la période pour le cas (10000.0).
  2. Les pauses après la première période sont traitées.

Code ici:

void EliminateTrailingFloatZeros(char *iValue)
{
  char *p = 0;
  for(p=iValue; *p; ++p) {
    if('.' == *p) {
      while(*++p);
      while('0'==*--p) *p = '\0';
      if(*p == '.') *p = '\0';
      break;
    }
  }
}

Il y a toujours un potentiel de débordement, alors soyez prudent; P

0
David Thornley

Pourquoi ne pas simplement faire ça?

double f = 359.01335;
printf("%g", round(f * 1000.0) / 1000.0);
0
Jim Hunziker