I worked on extending a C++ tool. Making it produce results with higher precision, in particular. I discovered that it's surprisingly hard to output the custom precision number in C++. Here are two ways I found:
cout.precision(18); cout << setprecision(18) << "rest of the output";
Both of these mean modifying the global state of the stream.
Which, in case of
The solution my friend (an experienced C++ dev) suggested was to just use
Anyway, printing floats is a mess.
Unlike printing anything else.
So this post goes into why you don't need
We (programmers) all use formatted output daily. But most formatting primitives, especially in low-level languages, are for numbers. So formatted output
So what I'm trying to say here is not that format strings are useless. I'm rather of the opinion they can be shortened, optimized, and simplified. Except for float printing cases where printing parameters actually matter. The rest is easily accomplished with string interpolation or even separate printing expressions. No meter-thick historic crust.
Let's start with something simple. There were several attempts to standardize format strings in Scheme. One is SRFI 28, Basic Format Strings, then SRFI 48, Intermediate Format Strings, and SRFI 54 (cat) for an alternative take, and others.
SRFI 28 is quite basic with only three directives:
It's more or less clear why one would want to extend this—while
human-readable vs. machine-readable is quite enough for many cases, one might need finer output.
That's where SRFI 48 comes in:
This SRFI extends SRFI 28 in being more generally useful but is less general than advanced format strings in that it does not allow, aside from ~F, for controlled positioning of text within fields.
In other words: SRFI 48 exists for the sole purpose of printing floats prettily.
(I mean, there are lots of other directives, mimicking for C
C
And there's a moderate number of directives to choose from.
Here's my categorization:
It's useless, because you have an assortment of number-printing directives.
Some of which you're unlikely to ever use.
And the useful stuff like string printing is so stripped down that it's almost useless too.
How often does C programmer implements conditional printing with
Too often.
String interpolation akin to JavaScript would make more sense.
It would both put the data into the output.
And won't bother with the obvious (from the type system point of view) details of printing directives.
While this example would require type inference to be ported to C, it's a good things to aim for.
(And you'll see that it's achievable in the end of this post.)
Common Lisp's
I'll refer you to
my earlier post
for a full feature listing (and horrible format string from my own experience!)
But the gist is that
I'll handle the hardest case out of these three: C data formatting.
Thanks to C11 generics, it's possible to implement generic printing
(and that's exactly what I'm doing in my
Pretty.C)
that covers 99% of the printing cases:
If there is
Here's how the example from earlier looks with this new macro:
It's the same length as printf (or even less if we don't consider 8 spaces of indentation!)
But the structure and syntax are easier on both human and machine readers.
The solution in Scheme and CL
was already proposed:
just use language macro facilities to implement printing that actually reflect the output shape.
Something like string interpolation, but even more structured and enforceable!
So yes, you likely don't need
printf In C: Exists For Floats
-
% and n
printf("%s%s", (boolean ? "!" : ""), "string");
console.log(`${boolean ? "!" : ""}string`)
Counter
Solution: Generic Printing and Interpolation
#if __STDC_VERSION__ >= 201112L
#define print(...) \
_Generic((__VA_ARGS__), \
_Bool: fputs(stdout, (_Bool)(__VA_ARGS__) ? "true" : "false"), \
default: printf( \
_Generic((__VA_ARGS__), \
char*: "%s", \
char: "%c", \
signed char: "%hhi", \
short: "%hi", \
int: "%i", \
long: "%li", \
long long: "%lli", \
unsigned char: "%hhu", \
unsigned short: "%hi", \
unsigned int: "%u", \
unsigned long: "%lu", \
unsigned long long: "%llu", \
float: "%g", \
double: "%g", \
long double: "%Lg", \
default: "%p"), \
(__VA_ARGS__)))
#endif
if (boolean)
print("!");
print("string");