69

It looks like it could have been, there are (at least in C99) length modifiers that can be applied to int: %hhd, %hd, %ld and %lld mean signed char, short, long and long long. There is even a length modifier applicable to double: %Lf means long double.

The question is why did they omit float? Following the pattern, it might have been %hf.

TylerH
  • 20,799
  • 66
  • 75
  • 101
skyking
  • 13,817
  • 1
  • 35
  • 57
  • @Seb I got the format specifiers from the "wrong" place. Some implementation seem to accept `%lf` for `long double`, the standard however specifies `%Lf` for that. I've updated the question. – skyking Sep 11 '15 at 08:50
  • ... and `%Ld` does not mean `long`, either. What that means is [*undefined behaviour*](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html#7.21.6.1p9) (the same section and paragraph as passing an incorrect argument renders your previous `%lf` -> `long double` correspondence undefined). Note the lack of pattern? – autistic Sep 11 '15 at 08:50
  • @skyking Which implementation would that be? – autistic Sep 11 '15 at 09:08
  • @Seb Don't know, it may even be that I misread my source. – skyking Sep 11 '15 at 09:10
  • @skyking Why `%hf` and not `%Hf`, to align with the capital `L` in `%Lf`? Surely you see that the pattern used for integer specifiers doesn't apply for `float` specifiers, now, right? – autistic Sep 11 '15 at 09:14
  • Related to [Why does printf() promote a float to a double?](http://stackoverflow.com/q/28097564/1708801) – Shafik Yaghmour Sep 11 '15 at 09:30
  • 2
    possible duplicate of [Why does scanf() need "%lf" for doubles, when printf() is okay with just "%f"?](http://stackoverflow.com/questions/210590/why-does-scanf-need-lf-for-doubles-when-printf-is-okay-with-just-f) – phuclv Sep 11 '15 at 09:55
  • @Seb: In fact `%lf` stands for `long float`, which was in early version of C a synonym for `double`. Now historical vestige, but it prevented the scheme to be minimally coherent when the committee introduced `long double` in C89. And no, there are no real pattern; which does not prevent asking for a way to format float values. – AntoineL Sep 12 '15 at 20:02
  • @AntoineL Are you reading any particular manual, or is that just a guess? Because the manuals I'm reading ([the POSIX standard (part of the Single Unix Specification)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html) and [the C11 standard, section 7.21.6.1, paragraph 7](http://www.iso-9899.info/n1570.html#7.21.6.1p7)) both state quite clearly that the `l` length modifier "has no effect on a following `a`, `A`, `e`, `E`, `f`, `F`, `g`, or `G` conversion specifier." – autistic Sep 12 '15 at 23:23
  • 1
    @Seb: I was (unobviously?) speaking about `scanf` specifier, so next subclause applies. About `long float`, see for example [C99 Rationale](http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf), page 42. What _would be_ a guess could be to decide whether the ANSI committee added explicit `%lf` to `printf` (it was [undefined behaviour (by omission) in V7](https://www.freebsd.org/cgi/man.cgi?query=printf&manpath=Unix+Seventh+Edition)) to either cater with then-existing usage for `long float`, to align with `scanf`, or if both apply. – AntoineL Sep 14 '15 at 14:46
  • @AntoineL Ah, I see what you were saying, but "`%lf` once stood for `long float`" is quite different to "`%lf` stands for `long float`". At the time when the word "animal", already widely known as a Latin word, was adopted into English, there was a native word "deer" which had the same meaning. After "animal" came in as a general term, the word "deer" developed it's specialised meaning of "a horned beast". It might be a good idea to consider this a similar form of evolution; if you use "deer" in the old context you are speaking *Old English*, which is very different to the English of today... – autistic Sep 15 '15 at 01:29
  • @AntoineL ... and similarly, `long float` is in the context of *Old C* (ancient, in fact, since it's pre-C89), which is very different to the C of today (decidedly not-C, in fact). – autistic Sep 15 '15 at 01:35

8 Answers8

47

Because in C variadic function calls, any float argument is promoted (i.e. converted) to a double, so printf is getting a double and would use va_arg(arglist, double) to get it inside its implementation.

In the past (C89 and K&R C) every float argument was converted to a double. The current standard omits this promotion for fixed arity functions which have an explicit prototype. It is related to (and details are explained in) the ABI & calling conventions of the implementation. Practically speaking, a float value would be often loaded into a double-floating point register when passed as an argument, but details can vary. Read the Linux x86-64 ABI specification as an example.

Also, there is no practical reason to give a specific format control string for float since you can adjust the width of the output (e.g. with %8.5f) as wanted, and %hd is much more useful (nearly necessary) in scanf than in printf

Beyond that, I guess that the reason (to omit %hf specifying float -promoted to double in caller- in printf) is historical: at first, C was a system programming language, not an HPC one (Fortran was preferred in HPC perhaps till the late 1990s) and float was not very important; it was (and still is) thought of like short, a way to lower memory consumption. And today's FPUs are fast enough (on desktop or server computers) to avoid using float except as a mean to use less memory. You basically should believe that every float is somewhere (perhaps inside the FPU or the CPU) converted to double.

Actually, your question might be paraphrased as : why %hd exists for printf (where it is basically useless, since printf is getting an int when you pass it some short; however scanf needs it!). I don't know why, but I imagine than in system programming it might be more useful.

You could spend time lobbying the next ISO C standard to get %hf accepted by printf for float (promoted to double at printf calls, like short-s get promoted to int), with undefined behavior when the double precision value is out of bound for float-s, and symetrically %hf accepted by scanf for float pointers. Good luck on that.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 3
    That doesn't explain everything for `printf`, though. There is also argument promotion of all narrow types to `int`, but still there are special format modifiers for them. – Jens Gustedt Sep 11 '15 at 07:43
  • 1
    But for the small integer type format specifier, the C standard addresses promotion. See for example 7.19.6.1 fprintf: `hh Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing);`. So I don't believe this has anything to do with implicit type promotion. – Lundin Sep 11 '15 at 07:58
  • @Lundin This is an excellent observation! It's enough to remove the last bit of doubt from this answer. – anatolyg Sep 11 '15 at 08:25
  • What you have not seen here, which Lundin mentioned, is the secondary conversion within `printf`. Values outside of the range of `short` will be converted, in an implementation-defined manner, down to `short`. This is actually very well defined for `unsigned` types and their corresponding specifiers. What purpose would such a narrowing conversion from `double` (as passed to `printf`) to `short` have, when the corresponding behaviour is undefined? – autistic Sep 11 '15 at 08:43
  • 1
    Concerning "there is no practical reason to give a specific format control string for `float`". In corner cases, some sort of `"%ha"` could be useful to display `float` aligned in a different fashion `printf("%a %ha\n", FLT_MAX, FLT_MAX) --> 0x1.fffffep+127 0x0.ffffffp+128` as that pesky `e` in the first case only have 3 significant bits. – chux - Reinstate Monica Sep 11 '15 at 11:19
  • 3
    [What is the purpose of the h and hh modifiers for printf?](http://stackoverflow.com/q/4586962/995714) – phuclv Sep 11 '15 at 11:27
  • (continue) This right-justified alignment is some times employed when printing `long double` with `"%La"` and when it has 64 bits of precision, a leading 0 is used. – chux - Reinstate Monica Sep 11 '15 at 11:30
  • I would disagree with "today's FPUs are fast enough (on desktop or server computers) to avoid using `float` except as a mean to use less memory". `float` is still very widely used in high-performance applications where double precision is not needed. While double-precision processing is quite fast nowadays, single precision operations often provide twice the throughput on SIMD architectures (the same register can hold twice the number of elements). Plus, the smaller memory footprint can yield huge benefits when caching is taken into account. – Jason R Sep 11 '15 at 11:31
  • @Jason R The ideas about "single precision operations often provide twice the throughput" and "the smaller memory footprint" bolsters this answer's to "avoid using `float` except as a mean to use less memory" as less memory usage readily equates to higher throughput. – chux - Reinstate Monica Sep 11 '15 at 11:38
  • @chux: I disagree; the answer specifically implies that speed isn't a reason to choose `float` above `double`, which just isn't true. – Jason R Sep 11 '15 at 12:05
  • All C standards including C89 allow prototypes, and convert to the declared type for prototyped parameters, but default-promote vararg arguments and all arguments to an unprototyped (K&R1) function. Only pre-C89 (K&R1 and pre-K&R1) has no prototypes and thus always default-promotes. – dave_thompson_085 Sep 15 '15 at 15:39
  • for your last question on `..however scanf needs it!` **maybe** because `scanf` need to know the size in bytes for what it will be reading from an input stream, where `printf` wouldn't have issues outputting a `short` as an `int` because it won't show trailing unused zeroes beyond `short` range, which won't seem to exist since it's becoming part of an output stream of characters. – Khaled.K Sep 16 '15 at 09:42
15

Because of default argument promotions.

printf() is a variable argument function (... in its signature), all float arguments are promoted to double.

C11 §6.5.2.2 Function calls

6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

7 The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

Community
  • 1
  • 1
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
  • 12
    That doesn't explain everything, though. There is also argument promotion of all narrow types to `int`, but still there are special format modifiers for them. – Jens Gustedt Sep 11 '15 at 07:42
  • 7
    This quote is also irrelevant, it just describes the default argument promotions. People got to stop upvoting just because someone quotes a random part of the standard... actually read what it says before you do. The part that is relevant and actually says that the default argument promotions apply to variadic functions is found further down, in 6.5.2.2/7: "The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments." – Lundin Sep 11 '15 at 08:14
  • 3
    @Lundin I've added 6.5.2.2/7. Section 6 explains how `float` promoted to `double` in default argument promotions, section 7 explains how `...` performs default argument promotions. My previous answer was incomplete, yes, and it still is, but I don't think saying it's irrelevant/random is fair. – Yu Hao Sep 11 '15 at 08:35
  • 5
    "The question is why did they omit float?" to which you answered "because of default argument promotions" followed by a quote of how default argument promotions work in the absence of a function prototype. That's rather random to me. – Lundin Sep 11 '15 at 09:28
  • @Jens Gustedt Strictly, there are no special format modifiers for _narrow_ integers `"%hd"`, `"%hhu"`, etc. take an `int`, `unsigned` argument. It is that the modifiers narrow the `int`, `unsigned` to `shiort int`, `unsigned char` _after_ receiving them at the full width. – chux - Reinstate Monica Sep 11 '15 at 10:54
  • 1
    @chux The question is still why there isn't one that does the same thing to a double argument. – Random832 Sep 11 '15 at 13:32
  • @Random832 The answer is because there's an implementation-defined (or in case of `unsigned` types, well defined) narrowing conversion for the integer types, and the same narrowing is **undefined** for floating point types. – autistic Jun 21 '16 at 00:46
  • @Seb it's still well defined for values that can be represented in the narrower type. – Random832 Jun 21 '16 at 01:55
  • @Random832 my point is there's a pattern you've no doubt spotted which is the `h` suffix reduces values beyond the range modulo whatever`_MAX` for `unsigned` types, and in an implementation-defined manner for the signed integer types. This reduction isn't well defined for floating types as you just clarified... the `h` suffix is meant to do exactly that. If that can't be done without UB then it's inconsistent. Inconsistency in C is the reason we're having this discussion; perhaps we should all just use Java ;) – autistic Jun 21 '16 at 03:38
  • 1
    @Seb yeah but C doesn't in general shy away from having features which would cause undefined behavior if used with the wrong values... just in printf itself (or, rather, in varargs functions as a whole) you're free to use %o/%u/%x with signed values as long as they happen to be positive but it's undefined if they're negative. – Random832 Jun 21 '16 at 05:03
  • @Random832 Those all have unique functionality attached to them, and the UB is due to conflicting representations between signed and unsigned types. What you're proposing already exists as `%f` with the caveat that values outside of range don't invoke UB. – autistic Jun 21 '16 at 08:47
10

Due to the default argument promotions when calling variadic functions, float values are implicitly converted to double before the function call, and there is no way to pass a float value to printf. Since there's no way to pass a float value to printf, there's no need for an explicit format specifier for float values.

Having said that, AntoineL brought up an interesting point in a comment that %lf (currently used in scanf to correspond to an argument type double *) may have once stood for "long float", which was a type synonym in pre-C89 days, according to page 42 of the C99 rationale. By that logic, it might make sense that %f was intended to stand for a float value which has been converted to a double.


Regarding the hh and h length modifiers, %hhu and %hu present a well-defined use-case for these format specifiers: You can print the least significant byte of a large unsigned int or unsigned short without a cast, for example:

printf("%hhu\n", UINT_MAX); // This will print (unsigned char)  UINT_MAX
printf("%hu\n",  UINT_MAX); // This will print (unsigned short) UINT_MAX

It isn't particularly well defined what a narrowing conversion from int to char or short will result in, but it is at least implementation-defined, meaning the implementation is required to actually document this decision.

Following the pattern it should have been %hf.

Following the pattern you've observed, %hf should convert values outside of the range of float back to float. However, that kind of narrowing conversion from double to float results in undefined behaviour, and there's no such thing as an unsigned float. The pattern you see doesn't make sense.


To be formally correct, %lf does not denote a long double argument, and if you were to pass a long double argument you would be invoking undefined behaviour. It is explicit from the documentation that:

l (ell) ... has no effect on a following a, A, e, E, f, F, g, or G conversion specifier.

I'm surprised nobody else has picked up on this? %lf denotes a double argument, just like %f. If you want to print a long double, use %Lf (capital ell).

It should henceforth make sense that %lf for both printf and scanf correspond to double and double * arguments... %f is exceptional only because of the default argument promotions, for the reasons mentioned earlier.

... and %Ld does not mean long, either. What that means is undefined behaviour.

Community
  • 1
  • 1
autistic
  • 1
  • 3
  • 35
  • 80
  • 4
    But a `short` gets promoted to an `int` when used as a variadic argument to `printf` and `%hd` exists – Basile Starynkevitch Sep 11 '15 at 08:10
  • @BasileStarynkevitch The OPs question is "why did they omit `float`?"... Not "why didn't they omit `short`?". Your concern seems irrelevant to me, but if you really want me to, just bark and I'll explain why that may be so. – autistic Sep 11 '15 at 08:15
  • 4
    My point is that following your argument, there is no way to pass a `short` value to `printf`, however `%hd` exists for `printf`! – Basile Starynkevitch Sep 11 '15 at 08:19
  • @BasileStarynkevitch How's that? – autistic Sep 11 '15 at 08:26
  • `printf` is getting an `int` when you pass it some `short` value. – Basile Starynkevitch Sep 11 '15 at 08:27
  • @BasileStarynkevitch It sure is, and that `int` value is then converted back to a `short` value... in an implementation-defined manner, when it's outside of range. If there were a `%hf`, then passing a `double` value that's similarly out of range can only result in undefined behaviour. – autistic Sep 11 '15 at 08:29
  • 3
    @BasileStarynkevitch Why does `%hd` exist? Try `short x = -72; printf( "%x\n", x );` and compare the result to `short x = -72; printf("%hx\n", x);` – Andrew Henle Sep 11 '15 at 11:30
  • @Seb: Strictly speaking, `%hd` and `%hhd` are probably not useful for `printf` -- but they're harmless, and perhaps useful as documentation, when passing values of type `short` and `signed char`. And `%hn` and `%hhn` are useful. They're also useful for `scanf`, which works with objects rather than values and so are not subject to promotion. – Keith Thompson Sep 11 '15 at 19:13
  • @KeithThompson I can agree with regards to `%hd` and `%hhd`, but I think `%hu` and `%hhu` might present more of a valid usecase... hence the reason I mentioned those explicitly (don't know if you realised that?) – autistic Sep 12 '15 at 03:13
  • I think `printf("%hhu\n", UINT_MAX);` is UB, the standard says that the corresponding argument must have been `char` (possibly signed/unsigned) before promotion – M.M Jan 16 '16 at 01:49
  • @M.M Where does the standard say that? – autistic Jan 16 '16 at 02:48
  • @Seb "`hh` Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument" – M.M Jan 16 '16 at 02:52
  • @M.M According to [part 2 of the ISO/IEC directives](http://www.iec.ch/members_experts/refdocs/iec/isoiec-dir2%7Bed6.0%7Den.pdf), that doesn't contain a valid verbal form for expression of provisions. See "Annex H" for keywords that must be present. – autistic Jan 16 '16 at 02:56
  • In that case it'd be undefined behaviour by omission , since it is not specified what `hh` applies to. I don't want to have a protracted argument here, since the text is way too imprecise , these arguments can go on forever with the parties not reaching any agreement. – M.M Jan 16 '16 at 03:01
5

From the ISO C11 standard, 6.5.2.2 Function calls /6 and /7, discussing function calls in the context of expressions (my emphasis):

6/ If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

7/ If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

This means that any float arguments after the ... in the prototype are converted to double and the printf family of calls are defined that way (7.21.6.11 et seq):

int fprintf(FILE * restrict stream, const char * restrict format, ...);

So, since there's no way for printf()-family calls to actually receive a float, it makes little sense to have a special format specifier (or modifier) for it.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • We might have in the standard something like: `%hf` convert the `double` value into a `float` and displays it with reduced precision. BTW, `%hd` has an equivalent meaning (since `printf` does not get a `short` neither) – Basile Starynkevitch Sep 11 '15 at 08:05
  • 2
    But `printf` is not receiving a `short` neither! – Basile Starynkevitch Sep 11 '15 at 08:21
  • @BasileStarynkevitch You're playing a broken record. I've already explained the rationale behind `%hd` when you copy/pasted that same comment into the section for my answer... and that has nothing to do with the question asked here. Do note that by *question* I mean "the string of words ending with a question mark". – autistic Sep 11 '15 at 08:56
2

Considering scanf has separate format specifiers for float, double, or long double, I don't see why printf and similar functions weren't implemented in a similar way, but that's how C / C++ and the standards ended up.

There can be an issue with the minimum size for a push or pop operation depending on the processor and the current mode, but this could have been handled with default padding, similar to default alignment of local variables or variables in a structure. Microsoft dropped support for 80 bit (10 byte) long doubles when it went from 16 bit to 32 / 64 bit compilers, now treating long doubles the same as doubles (64 bit / 8 byte). They could have padded them to 12 or 16 byte boundaries as needed, but this wasn't done.

autistic
  • 1
  • 3
  • 35
  • 80
rcgldr
  • 27,407
  • 3
  • 36
  • 61
  • 1
    The difference is that `scanf` expects a pointer argument when accepting a `float`. The same type promotion doesn't happen when calling `scanf`. – skyking Sep 11 '15 at 08:46
  • @skyking - I understand that, but type promotion could have been originally specified to handle more cases, and the format specifiers from scanf were already defined. – rcgldr Sep 11 '15 at 08:48
  • There's not much type promotion that could be done on a pointer (more than perhaps promoting it to a far pointer). Changing the type of the target is a bad idea, first of all it would mean that you may get a buffer overrun when you dereference a pointer to 16-bit data as if it was 32 bits. In addition you cannot do it the other way either if you have middle endian. – skyking Sep 11 '15 at 08:55
  • @skyking - I meant type promotion for parameters for functions like printf. scanf requires specific format specifiers for each target type. – rcgldr Sep 11 '15 at 08:57
2

Reading the C rationale, below fscanf, the following can be found:

A new feature of C99: The hh and ll length modifiers were added in C99. ll supports the new long long int type. hh adds the ability to treat character types the same as all other integer types; this can be useful in implementing macros such as SCNd8 in (see 7.18).

So supposedly the hh was added for the purpose of providing support for all of the new stdint.h types. This could explain why a length modifier was added for small integers but not for small floats.

It doesn't explain why C90 inconsistently had h but no hh though. The language as specified in C90 is not always consistent, simple as that. And later versions have inherited the inconsistency.

Lundin
  • 195,001
  • 40
  • 254
  • 396
2

When C was invented, all floating-point values were converted to a common type (i.e. double) before being used in computations or passed to functions (including) printf, so there was no need for printf to make any distinctions among floating-point types.

In the interest of promoting arithmetic efficiency and accuracy, the IEEE-754 floating-point standard defined an 80-bit type which was larger than a normal 64-bit double but could be processed faster. The intention was that given an expression like a=b+c+d; it would be both faster and more accurate to convert everything to an 80-bit type, add together three 80-bit numbers, and convert the result to a 64-bit type, than to compute the sum (b+c) as a 64-bit type and then add that to d.

In the interest of supporting the new type, ANSI C defined a new type long double which implementations could have refer to the new 80-bit type or a 64-bit double. Unfortunately, even though the purpose of the IEEE-754 80-bit type was to have all values automatically promote to the new type the way they had been promoted to double, ANSI made it so the new type gets passed to printf or other variadic methods differently from other floating-point types, thus making such automatic promotion untenable.

Consequently, both floating-point types that existed when C was created can use the same %f format specifier, but the long double that was created afterward requires a different %Lf format specifier (with an uppercase L).

supercat
  • 77,689
  • 9
  • 166
  • 211
2

%hhd, %hd, %ld and %lld were added to printf to make the format strings more consistent with scanf, even though they are redundant for printf because of the default argument promotions.

So why wasn't %hf added for float? That's easy: looking at scanf's behaviour, float already has a format specifier. It's %f. And the format specifier for double is %lf.

That %lf is exactly what C99 added to printf. Prior to C99, the behaviour of %lf was undefined (by omission of any definition in the standard). As of C99, it's a synonym for %f.