Avoid trailing zeroes in printf()

前端 未结 14 2180
猫巷女王i
猫巷女王i 2020-11-22 07:18

I keep stumbling on the format specifiers for the printf() family of functions. What I want is to be able to print a double (or float) with a maximum given number of digits

相关标签:
14条回答
  • 2020-11-22 07:43

    I found problems in some of the solutions posted. I put this together based on answers above. It seems to work for me.

    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);
    }
    
    0 讨论(0)
  • 2020-11-22 07:45

    A simple solution but it gets the job done, assigns a known length and precision and avoids the chance of going exponential format (which is a risk when you use %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);
    }
    

    Add or remove "elses" if you want a max of 2 decimals; 4 decimals; etc.

    For example if you wanted 2 decimals:

    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);
    }
    

    If you want to specify the minimum width to keep fields aligned, increment as necessary, for example:

    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);
    }
    

    You could also convert that to a function where you pass the desired width of the field:

    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);
    }
    
    0 讨论(0)
  • 2020-11-22 07:49

    Hit the same issue, double precision is 15 decimal, and float precision is 6 decimal, so I wrote to 2 functions for them separately

    #include <stdio.h>
    #include <math.h>
    #include <string>
    #include <string.h>
    
    std::string doublecompactstring(double d)
    {
        char buf[128] = {0};
        if (isnan(d))
            return "NAN";
        sprintf(buf, "%.15f", d);
        // try to remove the trailing zeros
        size_t ccLen = strlen(buf);
        for(int i=(int)(ccLen -1);i>=0;i--)
        {
            if (buf[i] == '0')
                buf[i] = '\0';
            else
                break;
        }
    
        return buf;
    }
    
    std::string floatcompactstring(float d)
    {
        char buf[128] = {0};
        if (isnan(d))
            return "NAN";
        sprintf(buf, "%.6f", d);
        // try to remove the trailing zeros
        size_t ccLen = strlen(buf);
        for(int i=(int)(ccLen -1);i>=0;i--)
        {
            if (buf[i] == '0')
                buf[i] = '\0';
            else
                break;
        }
    
        return buf;
    }
    
    int main(int argc, const char* argv[])
    {
        double a = 0.000000000000001;
        float  b = 0.000001f;
    
        printf("a: %s\n", doublecompactstring(a).c_str());
        printf("b: %s\n", floatcompactstring(b).c_str());
        return 0;
    }
    

    output is

    a: 0.000000000000001
    b: 0.000001
    
    0 讨论(0)
  • 2020-11-22 07:51

    Slight variation on above:

    1. Eliminates period for case (10000.0).
    2. Breaks after first period is processed.

    Code here:

    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;
        }
      }
    }
    

    It still has potential for overflow, so be careful ;P

    0 讨论(0)
  • 2020-11-22 07:52

    I like the answer of R. slightly tweaked:

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

    '1000' determines the precision.

    Power to the 0.5 rounding.

    EDIT

    Ok, this answer was edited a few times and I lost track what I was thinking a few years back (and originally it did not fill all the criteria). So here is a new version (that fills all criteria and handles negative numbers correctly):

    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);
    

    And the test cases:

    #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;
    }
    

    And the output of the tests:

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

    Now, all criteria are met:

    • maximum number of decimals behind the zero is fixed
    • trailing zeros are removed
    • it does it mathematically right (right?)
    • works (now) also when first decimal is zero

    Unfortunately this answer is a two-liner as sprintf does not return the string.

    0 讨论(0)
  • 2020-11-22 07:53

    To get rid of the trailing zeros, you should use the "%g" format:

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

    After the question was clarified a bit, that suppressing zeros is not the only thing that was asked, but limiting the output to three decimal places was required as well. I think that can't be done with sprintf format strings alone. As Pax Diablo pointed out, string manipulation would be required.

    0 讨论(0)
提交回复
热议问题