Marco Bonelli
Marco Bonelli

Reputation: 69367

Multiple dynamic float format specifier in C

It happened to me several times to work with float variables having to printf them out properly formatted with a certain number of decimal positions. I know that in C you can easily format float variables by using the following notation to specify both decimal and total digits:

// Where x and y are natural numbers
printf("%<x>.<y>f", myFloat);

And I also know that you can use the * symbol to dynamically format a float using two variables, like this:

// Where x and y are natural numbers
printf("%*.*f", x, y, myFloat);

But I recently got stuck in a really redundant situation: when I have to printf out multiple float numbers with the same dynamic format I have to repeat these two x and y variables thousands of times. I always like to #define my constants so that I can easily change them in the future, so my programs look something like:

#define FLOAT_FORMAT_SIZE 10
#define FLOAT_FORMAT_PRECISION 3

int main(int argc, char *argv[]) {
    // Anything I have to do...

    // Output
    printf("%*.*f, %*.*f, %*.*f, %*.*f",
        FLOAT_FORMAT_SIZE, FLOAT_FORMAT_PRECISION, myFloat1,
        FLOAT_FORMAT_SIZE, FLOAT_FORMAT_PRECISION, myFloat2,
        FLOAT_FORMAT_SIZE, FLOAT_FORMAT_PRECISION, myFloat3,
        FLOAT_FORMAT_SIZE, FLOAT_FORMAT_PRECISION, myFloat4
    );

    return 0;
}

Now you can see that this is a bit too redundant, and I was wondering: isn't there any special system constant or format technique I can use to avoid repeating the same values over and over again for every float I printf out?

I don't want to create my own function to print out these numbers, I can obviously do it, but I'd like to solve it on a lower level, if it's possible.

Upvotes: 0

Views: 1510

Answers (2)

M Oehm
M Oehm

Reputation: 29126

You can write a macro that defines your default way of printing floats:

#define FLOAT_FORMAT_SIZE 10
#define FLOAT_FORMAT_PRECISION 3

#define M_STR(x) M_STR_(x)
#define M_STR_(x) #x

#define FLOAT_FMT                       \
    "%" M_STR(FLOAT_FORMAT_SIZE)        \
    "." M_STR(FLOAT_FORMAT_PRECISION)   \
    "f"

Then your printf statement will look like this:

printf(FLOAT_FMT ", " FLOAT_FMT ", " FLOAT_FMT ", " FLOAT_FMT,
    myFloat1, myFloat2, myFloat3, myFloat4);

Both the macro and how it is used use string literal concatenation. The format string is created at compile time, so that warnings about format and argument type mismatch will be triggered.

It is, of course, debatable whether is format string is very readable.

Edit: How does this work? The macro M_STR "stringifies" its argument; it turns it into a C string literal. The operator #, which is available in macro definitions, achieves this.

The indirection to the auxiliary macro M_STR_ must be used, so that the macro argument will be expanded first. Without this auxiliary macro, M_STR(FLOAT_FORMAT_SIZE) would be expanded to "FLOAT_FORMAT_SIZE". With the detour via M_STR_, the macro FLOAT_FORMAT_SIZE is expanded before it is turned into a string and the resulting string is "10".

Hence, the macro FLOAT_FMT expands to

"%"   "10"   "."   "3"   "f"

The C compiler concatenates adjacent string literals, so the final result of the macro is equivalent to "%10.3f".

The same is done when constructing the format string:

printf(FLOAT_FMT ", " FLOAT_FMT ", " FLOAT_FMT ", " FLOAT_FMT,
    myFloat1, myFloat2, myFloat3, myFloat4);

is effectively:

printf("%10.3f, %10.3f, %10.3f, %10.3f",
    myFloat1, myFloat2, myFloat3, myFloat4);

A drawback of this solution is that juxtapositions of strings and macros are somewhat hard to read.

Upvotes: 2

R Sahu
R Sahu

Reputation: 206697

You can create the format string at run time and use it as many times as you need.

// Create the format string.
char formatString[100]; // Make it large enough.
sprintf(formatString, "%%%d.%df", FLOAT_FORMAT_SIZE, FLOAT_FORMAT_PRECISION);

// Use the format string to print a float
printf(formatString, myFloat1);
printf(formatString, myFloat2);
printf(formatString, myFloat3);
printf(formatString, myFloat4);

Upvotes: 2

Related Questions