Avoid trailing zeroes in printf()

2018-12-31 14:41发布

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 after the decimal point. If I use:

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

I get

359.013
359.010

Instead of the desired

359.013
359.01

Can anybody help me?

标签: c printf
12条回答
梦该遗忘
2楼-- · 2018-12-31 14:47

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.

查看更多
与风俱净
3楼-- · 2018-12-31 14:49

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.

查看更多
裙下三千臣
4楼-- · 2018-12-31 14:51

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);
}
查看更多
回忆,回不去的记忆
5楼-- · 2018-12-31 14:54

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.

查看更多
闭嘴吧你
6楼-- · 2018-12-31 14:57

Here is my first try at an answer:

void
xprintfloat(char *format, float f)
{
  char s[50];
  char *p;

  sprintf(s, format, f);
  for(p=s; *p; ++p)
    if('.' == *p) {
      while(*++p);
      while('0'==*--p) *p = '\0';
    }
  printf("%s", s);
}

Known bugs: Possible buffer overflow depending on format. If "." is present for other reason than %f wrong result might happen.

查看更多
荒废的爱情
7楼-- · 2018-12-31 14:59

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;

}
查看更多
登录 后发表回答