Avoid trailing zeroes in printf()

前端 未结 14 2178
猫巷女王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:32

    This can't be done with the normal printf format specifiers. The closest you could get would be:

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

    but the ".6" is the total numeric width so

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

    breaks it.

    What you can do is to sprintf("%.20g") the number to a string buffer then manipulate the string to only have N characters past the decimal point.

    Assuming your number is in the variable num, the following function will remove all but the first N decimals, then strip off the trailing zeros (and decimal point if they were all zeros).

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

    If you're not happy with the truncation aspect (which would turn 0.12399 into 0.123 rather than rounding it to 0.124), you can actually use the rounding facilities already provided by printf. You just need to analyse the number before-hand to dynamically create the widths, then use those to turn the number into a string:

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

    The whole point of nDecimals() in this case is to correctly work out the field widths, then format the number using a format string based on that. The test harness main() shows this in 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
    

    Once you have the correctly rounded value, you can once again pass that to morphNumericString() to remove trailing zeros by simply changing:

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

    into:

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

    (or calling morphNumericString at the end of nDecimals but, in that case, I'd probably just combine the two into one function), and you end up with:

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

    Your code rounds to three decimal places due to the ".3" before the f

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

    Thus if you the second line rounded to two decimal places, you should change it to this:

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

    That code will output your desired results:

    359.013
    359.01
    

    *Note this is assuming you already have it printing on separate lines, if not then the following will prevent it from printing on the same line:

    printf("%1.3f\n", 359.01335);
    printf("%1.2f\n", 359.00999);
    

    The Following program source code was my test for this answer

    #include <cstdio>
    
    int main()
    {
    
        printf("%1.3f\n", 359.01335);
        printf("%1.2f\n", 359.00999);
    
        while (true){}
    
        return 0;
    
    }
    
    0 讨论(0)
  • 2020-11-22 07:33

    I search the string (starting rightmost) for the first character in the range 1 to 9 (ASCII value 49-57) then null (set to 0) each char right of it - see below:

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

    Some of the highly voted solutions suggest the %g conversion specifier of printf. This is wrong because there are cases where %g will produce scientific notation. Other solutions use math to print the desired number of decimal digits.

    I think the easiest solution is to use sprintf with the %f conversion specifier and to manually remove trailing zeros and possibly a decimal point from the result. Here's a C99 solution:

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

    Note that the characters used for digits and the decimal separator depend on the current locale. The code above assumes a C or US English locale.

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

    What about something like this (might have rounding errors and negative-value issues that need debugging, left as an exercise for the reader):

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

    It's slightly programmatic but at least it doesn't make you do any string manipulation.

    0 讨论(0)
  • Why not just do this?

    double f = 359.01335;
    printf("%g", round(f * 1000.0) / 1000.0);
    
    0 讨论(0)
提交回复
热议问题