Why does printf() promote a float to a double?
Asked Answered
S

4

42

From a previous question:

If you attempt to pass a float to printf, it'll be promoted to double before printf receives it

printf() is a variadic function right? So does a variadic function promote a float argument to a double before passing it?

Sip answered 22/1, 2015 at 19:57 Comment(18)
Yes, via the "default argument promotions".Pathogenesis
Similarly, char is promoted to int, so char c=127; printf("%d", c); works correctly too.Sluff
would you rather demote a double to a float? Would you rather add the complexity of having each type independent of other, closely related types? Promoting floats seems like the common-sense solution to me.Recording
short does not necessarily run faster than int because a promotion of short to int happens with every arithmetic calculation.Sluff
I don't think the quote is not entirely correct (or doesn't include complete context); float values are not always converted to double when passed to a function, but they are when passed as trailing arguments to a var-args function like printf.Jenniejennifer
Your quote is misquoted, or perhaps from an old edition. Googling C Primer Plus turns up a PDF of the fifth edition where the actual quote is "That's because C automatically expands type float values to type double when they are passed as arguments to any function, such as printf(), that doesn't explicitly prototype the argument type."Eppes
@Weather Vane: Thankfully the optimizer typically has the freedom use narrow arithmetic if the results end up written back to a short type, with the exception of division/modulo/right-shift and indexing. Of course compilers for architectures where short arithmetic is more efficient tend to narrow int as well but the point stands for char (and a few 68k compilers when used to target older chip reversions).Sackbut
@XiaoHan a moderator can merge the questions if there are answers worth keeping. You can flag the question and make your case for merging if you feel that is appropriate.Rising
@Sackbut the optimizer cannot know the value of operands at runtime, so how does that prevent overflow? For example in a signed short calculation with runtime operation 32000 + 15000 - 20000 the intermediate sum will overflow and cause undefined behaviour, unless promoted to int.Sluff
@Eppes (This is a response to your first comment. I have deleted my previous comment as it wasn't worded correctly.) Function printf defines a prototype. I'm not sure what you're saying by the last sentence, but you cannot have a prototype for only certain argument. Either there is a prototype for the entire function or not. The rule that is relevant here is the rule that relates to functions with a prototype and with trailing arguments.Tini
Taking another look at the (N1570 draft of the) C11 standard, I seem to have misremembered the definition of a function prototype; a function declaration is only a prototype if it specifies the argument types, so void foo(f); would only be a declaration, not a prototype.Eppes
@WeatherVane: That's the magic of two's complement arithmetic, simply adding up the parts and truncating still yields to same result. Put another way consider the most significant bits of an unsigned or two's complement adder. Clearly higher bits do not influence the lower ones and can safely be ignored. Of course the signed result may also overflow but in that case the truncated representation is obviously still a valid implementation choice in the undefined case.Sackbut
@Sackbut I do understand "two's complement", but signed integer overflow is undefined behaviour. Two's complement might "work" but not if that is not the signed system. So an optimising compiler would only "get away with it" if it knows that is the system.Sluff
@Weather Vane: Hence the typically. It is a trick performed by all compilers targetting 8-bit embedded architectures systems which I have used. In practice an optimizing compiler which doesn't know the properties of the architecture it is optimizing for would be a strange beast, and C compilers straying outside of two's complement integer representations are similarly exceedingly rare.Sackbut
@Sackbut so "undefined behaviour" reduces to "implementation defined behaviour", the very thing I was told off recently for saying w.r.t. signed integer overflow? ;)Sluff
@Weather Vane: Basically. The result are correct for all well-defined inputs and the compiler is permitted not to care about the invalid ones for performance reasons (effectively not to bothering to trap on overflow.)Sackbut
@Sackbut thank you, having learnt assembler before C, and using embedded code generated by C, I never understood why my specific code was supposed to be "portable" by the purists.Sluff
@WeatherVane: The authors of the Standard apply the phrase "Undefined Behavior", among other things, to situations where many or even most implementations should behave in predictable fashion (or often even the same predictable fashion) but it may not be practical for all implementations to behave predictable. The phrase "Implementation-Defined Behavior" is used only for situations where all implementations would be required to behave in a consistent documented fashion, even if guaranteeing any remotely consistent behavior would be expensive, and no possible behavior would be useful.Uncleanly
U
36

Yes, float arguments to variadic function are promoted to double.

The draft C99 standard section 6.5.2.2 Function calls says:

[...]and arguments that have type float are promoted to double. These are called the default argument promotions.[...]

from the draft C++ standard section 5.2.2 Function call:

[...]a floating point type that is subject to the floating point promotion (4.6), the value of the argument is converted to the promoted type before the call. [...]

and section 4.6:

A prvalue of type float can be converted to a prvalue of type double. The value is unchanged

cppreference covers the default conversions for variadic function in C++ well:

  • std::nullptr_t is converted to void*
  • float arguments are converted to double as in floating-point promotion
  • bool, char, short, and unscoped enumerations are converted to int or wider integer types as in integer promotion

We can see in C and presumably in C++ this conversion was kept around for compatibility with K&R C, from Rationale for International Standard—Programming Languages—C (emphasis mine):

For compatibility with past practice, all argument promotions occur as described in K&R in the absence of a prototype declaration, including the not always desirable promotion of float to double.

Unbowed answered 22/1, 2015 at 20:2 Comment(1)
Yes, float arguments to variadic function are promoted to double. Only in trailing arguments. Please fix this.Tini
G
24

As for the why part of the question, it's simple: the C (and C++) standards consider double to be the "default" floating point type. Not float (which is what many of us programmers default to when using floating point numbers).

This can be seen by observing:

  1. 3.14 is a double (if you want a float, you've got to take an extra step and append an f)
  2. The standard math functions take a double by default (for example, sin() takes a double; if you want a float you've got to use sinf())

With this, it seems more "natural" that a float would be promoted to double in a variadic function call, given that double is the "natural" default in the language.

Guttering answered 22/1, 2015 at 20:7 Comment(2)
"Not float is what many of us programmers default to when using floating point numbers)." I don't know how others do it, but I ususally take double. I only resort to float if it is about storing e. g. lots of measurement data with a quite high intrinsic error which need to be stored in an file.Pierce
One of my big regrets about C is that when long double was added, no mechanism was included to specify whether a variadic function expected all floating-point values to be converted to the longest type (long double), specific type double, or that it expected long double and double to be passed differently. IMHO, a lot of time and portability headaches would have been saved over the years if code could have said "convert everything integer-ish to this type and everything float-ish to this type".Uncleanly
T
17

Given a function prototype, type float is only automatically promoted1 when used in trailing arguments. Function print uses those:

int printf(const char * restrict format, ...);

1 (Quoted from: ISO/IEC 9899:201x 6.5.2.2 Function calls)
6. 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 default argument promotions are performed on trailing arguments.

Tini answered 22/12, 2016 at 13:11 Comment(0)
S
12

Because the (C99 or C11) standard says so. See answer by 2501.

There are several pragmatical reasons for that: history (first implementations of C have been used for system programming, where floating point operations don't matter), and the fact that on current (tablet, desktop, server...) processors, arithmetic operations on double are about as efficient as float (but some cheap microcontrollers don't have any FPU, or can only add float by hardware, and require a library for every operation on double). At last, I guess that such a rule enables slightly simpler calling conventions and ABIs.

Think of float as a sort-of short double (which of course is illegal in C). A float is useful mostly when you need to compact memory (and can afford the loss of precision). See also http://floating-point-gui.de/ for more.

Shop answered 22/12, 2016 at 13:3 Comment(8)
Where C99 or C11 standard says so ? Will you please include an exact citation from standard ?Cerberus
I am not sure to have time for that. Or wait for a couple of days.Shop
A float is also useful in current microcontrollers for embedded systems. Not necessarily because of the size, but rather because there are a few controllers that only have FPU support for single precision.Pegboard
"Because the [...] standard says so". So does "The C Programming Language" (1st edition, 1978) by Kernighan and Ritchie.Hafiz
Note: also "char" and "short" are automatically expanded and passed as "int".Goldeye
The C standard does not say so. The quote is either misquoted or taken from an edition where that line was wrong.Eppes
I believe the promotion happens only for the variadic args in variadic functions like printf. If you have a function prototyped to take a float, a float will be passed.Strobila
@CodeMonkey: I think it unfortunate that the Standard requires that DBL_DIG be too large to allow double to be efficiently processed on implementations without a double-precision FPU, rather than recommending that implementations wishing to maximize compatibility should make DBL_DIG at least 12 when practical.Uncleanly

© 2022 - 2024 — McMap. All rights reserved.