views:

374

answers:

6

I want to output my floats without the ending zeros.

Example: float 3.570000 should be outputted as 3.57

and float 3.00000 should be outputted as 3.0 (so here would be the exception!)

+2  A: 

This is not possible with standard printf semantics. In, the past, I've had to do this by outputting to a string (with something like "%.20f") then post-processing the string.

Something like this is probably what you're looking for:

#include <stdio.h>

void morphNumericString (char *s) {
    char *p;
    int count;

    // Find decimal point, if any.
    p = strchr (s,'.');

    if (p == NULL) {
        // No decimal, just add one fractional position.

        strcat (s, ".0");
    } else {
        // Decimal, start stripping off trailing zeros.

        while (s[strlen(s)-1] == '0') {
            s[strlen(s)-1] = '\0';
        }

        // If all fractional positions were zero, add one.

        if (s[strlen(s)-1] == '.') {
            strcat (s, "0");
        }
    }
}

 

int main (int argc, char *argv[]) {
    char str[100];
    int i;

    for (i = 1; i < argc; i++) {
        strcpy (str, argv[i]);
        morphNumericString (str);
        printf ("[%s] -> [%s]\n", argv[i], str);
    }

    return 0;
}

The code runs through each of it's arguments, morphing each one in turn. The following transcript shows how it works:

pax> ./qq 3.750000 12 12.507 47.90 56.0000000 76.0 0

[3.750000] -> [3.75]
[12] -> [12.0]
[12.507] -> [12.507]
[47.90] -> [47.9]
[56.0000000] -> [56.0]
[76.0] -> [76.0]
[0] -> [0.0]

You should be aware however that, if using floats or doubles, you'll have to watch out for the normal floating point inaccuracies. On my system 3.57 is actually 3.5699999999999998401, which won't be truncated at all.

Of course, you can get around that problem by using a less-specific number of output digits in the sprintf, something less than the actual accuracy of the floating point point. For example, "%.10f" on my system outputs 3.5700000000 which will be truncated. Adding the following lines to main:

sprintf (str, "%.10f", 3.57);
morphNumericString (str);
printf ("[%.10f] -> [%s]\n", 3.57, str);

sprintf (str, "%.10f", 3.0);
morphNumericString (str);
printf ("[%.10f] -> [%s]\n", 3.0, str);

will result in the following added output:

[3.5700000000] -> [3.57]
[3.0000000000] -> [3.0]

as per your test data.

One other possibility (if your input range and precision can be controlled) is to use the g format specifier. This outputs in either f format where the precision is the maximum number of digits (rather than fixed number like f) or exponential format (e).

Basically, it prefers the non-exponential output format as long as all the information is shown. It will switch to exponential only if that will deliver more information. A simple example is the format string "%.4g" with 3.7 and .0000000004. The former would be printed as 3.7, the latter as 4e-10.


Update: for those more concerned about performance than robustness or readability, you could try the following (unnecessary in my opinion but to each their own):

void morphNumericString (char *s) {
    char *p = strchr (s,'.');
    if (p == NULL) {
        strcat (s, ".0");
        return;
    }

    p = &(p[strlen(p)-1]);
    while ((p != s) && (*p == '0') && (*(p-1) != '.'))
        *p-- = '\0';
}

It may be faster but, given the extreme optimisations I've seen modern compilers do, you can never be too sure. I tend to code for readability first and only worry about speed when it becomes an issue (YAGNI can apply equally to performance as well as functionality).

paxdiablo
+1 since I like your concept. But I don't like your code. I know performance is rarely a problem, but still strlen()s inside loops make an old programmer shudder (think O(n^2)). And why do strcat(s,".0") twice for effectively the same condition ?. And why strcat() at all, given it has to find the end of the string, and we are scanning from the end so we know where it is?I can't very well write replacement code in a comment, so I've added it in my own answer
Bill Forster
@Bill, you should probably just think of it as the concept then, I generally only optimise when there's a problem and the chance of that for a 20-character string is close enough to zero not to warrant clogging up the answer. I don't strcat the same thing twice, one is for ".0" for strings without a period, the other is for "0" when you've stripped off all trailing zeros and are left with the period. Otherwise the code would be messier. Anyhow, I'll post an "optimised" version to keep you happy :-)
paxdiablo
dreamlax
Sure, I could also hand-code it in heavily optimised assembler :-) I would suggest to anyone that needs a faster solution to open _another_ question - this one was more of a "How can I ..." rather than a "How can I, at blazingly fast speeds, ...".
paxdiablo
+2  A: 

A more efficient and (in my opinion) clearer form of paxdiablo's morphNumericString(). Sorry not compiled or tested.

void morphNumericString( char *s )
{
    char *p, *end, *decimal, *nonzero;

    // Find the last decimal point and non zero character
    end = p = strchr(s,'\0');
    decimal = nonzero = NULL;
    while( p > s )
    {
        p--;
        if( !nonzero && *p!='0' )
        {
            nonzero = p;
        }
        if( !decimal && *p=='.' )
        {
            decimal = p;
            break;  // nonzero must also be non NULL, so stop early
        }
    }

    // eg "4.3000" -> "4.3"
    if( decimal && nonzero && nonzero>decimal )
        *(nonzero+1) = '\0';

    // eg if(decimal)  "4.0000" -> "4.0"
    //    if(!decimal) "4" -> "4.0"
    else
        strcpy( decimal?decimal:end, ".0" );
}
Bill Forster
That does work by the way, @Bill. We have to agree to disagree about the clarity :-)
paxdiablo
is it somehow possible to make own functions for the sprintf() patterns so i could use this function with own code like %F etc? so %F would use this morphNumericString() function for the strings its outputting in sprintf()
Newbie
@Newbie. Sorry I posted and then left so didn't see your question. Sorry, interesting idea but to the best of my knowledge you can't do that.
Bill Forster
A: 
dreamlax
A: 

My approach is:

  1. implement a function trim(char c) to eliminate the trail 'c', for examaple:

    void trim(std::string &str, char c) { size_t len = str.length();

    const char *s = str.c_str();
    const char *p = s + len - 1;
    while (p != s && *p == c) {
        -- p;
    }
    ++ p;
    size_t end = p - s;
    
    
    str = str.substr(0, end);
    

    }

  2. char buf[32]; printf(buf, "%f", 0.01); std::string s(buf); trim(buf, '0');

doudehou
A: 

Here's another string modification function. I did test it.

It's longer than Bill's and Diablo's, but it handles trailing 9's as well as 0's and should perform well.

Leaves a single trailing zero after the dot. You'll have to truncate using sprintf to get proper trailing 9's.

void morphNumericString( char *s ) {
    char *point = s;
    while ( * point && * point != '.' ) ++ point;
    char *last = strchr( point, 0 );
    if ( point == last ) {
        * point = '.';
        ++ last;
    }
    if ( point == last - 1 ) {
        * last = '0';

    } else {
        -- last;
        if ( * last == '0' ) {
            while ( * last == '0' ) -- last;

        } else if ( * last == '9' ) {
            while ( * last == '9' || * last == '.' ) {
                if ( * last == '9' ) * last = '0';
                -- last;
            }
            ( * last ) ++;
        }
        if ( last < point + 1 ) last = point + 1;
    }
    * ++ last = 0;
}

Edit: Yikes, this fails on input like 999.999. Left as an exercise to the reader ;v)

Potatoswatter
A: 

It might be easier to calculate the fractional part directly:

double value= -3.57;

double int_part;
double frac= modf(value, &int_part);

int64 int_part_as_int= int_part;
int significant_digits= 10;
int64 fractional_scale= pow(10., significant_digits);
int64 fraction_magnitude= fabs(frac)*fractional_scale + 0.5;

fractional_magnitude/fractional_scale will be the fraction rounded to significant_digits sig figs. Even with doubles, this is guaranteed not to overflow.

Formatting the fraction should be straightforward.

MSN