Why wasn’t the `float` specifier defined in` printf`?

It looks like it could be, there are length modifiers (at least on C99) 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 .

Question: why did they miss the float ? Following the pattern, it could be %hf .

+67
c floating-point printf c-standard-library
Sep 11 '15 at 7:29
source share
8 answers

Since in calls with variable variation C any argument float advanced (i.e. converted) to double , so printf gets double and will use va_arg(arglist, double) to get it inside its implementation.

In the past (C89 and K & RC), each float argument has been converted to double . The current standard skips this promotion for fixed arity functions that have an explicit prototype. This is due to (and explained in more detail) the ABI and the calling implementation conventions . In practice, the float value will often be loaded into the double-floating-point register when passed as an argument, but the data may vary. As an example, consider the Linux x86-64 ABI specification .

In addition, there is no practical reason for specifying a specific format control string for float , since you can adjust the output width (for example, using %8.5f ) as desired, and %hd much more useful (almost necessary) in scanf than in printf

In addition, I believe that the reason (omit %hf with float programmed to double in caller-in printf ) is historical : at first C was a system programming language, not HPC (Fortran was preferred in HPC, perhaps until the end 1990s) and the float not very important; it was (and still is) like short , a way to reduce memory consumption. And today, FPUs are fast enough (on desktop or server computers) to avoid using float , except that there is less memory to use. You should basically assume that each float is somewhere (possibly inside an FPU or CPU) converted to double .

Actually, your question may be rephrased as: why %hd exists for printf (where it is mostly useless, since printf gets an int when you pass it a few short , however scanf > does!). I do not know why, but I think that in system programming this might be more useful.

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

+47
Sep 11 '15 at 7:31
source share

Due to default promotions .

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

C11 §6.5.2.2 Functional calls

6 If the expression indicating the function to be called is of a type that does not contain a prototype, whole promotions are executed for each argument, and arguments with the float type are executed up to double . These are called default promotions.

7 The ellipsis designation in the function prototype declaration causes the conversion of the argument type to stop after the last declared parameter. Advancement of default arguments is performed by trailing arguments.

+15
Sep 11 '15 at 7:32
source share

Due to promotions, by default when calling variative functions, float values ​​are implicitly converted to double before the function is called, and it is not possible to pass float the printf value . Since there is no way to pass the float value to printf , there is no need for an explicit format specifier for the float values.

Having said that AntoineL raised an interesting point in the comment that %lf (currently used in scanf to match the type of the double * argument) might have once stood for the " long float ", which was a type synonym in pre-C89 days, according to page 42 justification C99 . By this logic, it might make sense that %f should have stood for the float value that was converted to double .




As for the length modifiers hh and h , %hhu and %hu represent a well-defined precedent for these format specifiers: you can print the least significant byte of a large unsigned int or unsigned short without casting, 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 is not particularly clearly defined, which will lead to a narrowing of the conversion from int to char or short , but it is at least determined by the implementation, which means that the implementation is necessary to document this decision.

Following the pattern, it should have been %hf .

Following the pattern you observed, %hf should convert the values ​​outside the range of float back to float . However, such a narrowing of the conversion from double to float leads to undefined behavior , and there is no such thing as unsigned float . The pattern you see does not make sense.




To formally be correct, %lf does not denote the long double argument, and if you must pass the long double argument you would invoke undefined behavior . From the documentation it is clear that:

l (ell) ... does not affect the following a , a , e , e , f , f , g or g conversion specifier.

I am surprised that no one took it upon themselves? %lf denotes the double argument, as %lf %f . If you want to print a long double , use %lf (capital ell).

From now on, it should be borne in mind that %lf for printf and scanf correspond to the arguments double and double * ... %f is exclusive only because of the promo arguments by default, for the reasons mentioned earlier.

... and %Ld does not mean long . This means undefined behavior .

+10
Sep 11 '15 at 8:09
source share

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

6 / If the expression denoting the function to be called is of a type that does not include a prototype, then whole promotions are executed for each argument, and arguments of the float type are raised to double. These are called default promotions.

7 / If the expression that indicates the function to be called is of the type that includes the prototype, the arguments are implicitly converted, as if by design, to the types of the corresponding parameters, taking the type of each parameter as an unqualified version of its declared type. An ellipsis designation in a function prototype declaration causes an argument type conversion to stop after the last declared parameter. Advancement of default arguments is performed by trailing arguments.

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

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

So, since there is no way for a printf() -family call to actually receive a float, it makes no sense to have a special format specifier (or modifier) ​​for it.

+4
Sep 11 '15 at 7:51
source share

Given that scanf has separate formatting specifiers for float, double, or long double, I don’t understand why printf and similar functions were not implemented in the same way, but that both C / C ++ and standards ended.

There may be a problem with the minimum size for a push or pop operation depending on the processor and the current mode, but this could be handled with a default padding similar to the default alignment of local variables or variables in the structure. Microsoft refused to support 80-bit (10 bytes) long double , when it switched from 16-bit to 32/64-bit compilers, it now processes long double in the same way as double (64 bit / 8 bytes). They could fill them up to 12 or 16 bytes as needed, but this was not done.

+2
Sep 11 '15 at 8:43
source share

Considering the C logic below fscanf, you can find the following:

New C99 feature: hh and ll length modifiers have been added to C99. ll supports the new long long int type. hh adds the ability to handle character types in the same way as all other integer types; this may be useful when implementing macros such as SCNd8 in (see 7.18).

So, suppose hh was added in order to support all new types of stdint.h . This may explain why the length modifier was added for small integers, but not for small floats.

This does not explain why the C90 inconsistently had h , but not hh . The language specified in C90 is not always consistent, simple. And later versions inherited inconsistency.

+2
Sep 11 '15 at 9:15
source share

When C was invented, all floating point values ​​were converted to a common type (i.e. double ) before being used in calculations or passed to printf functions (including), so printf was not needed for any differences between floating point types.

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

In the interest of supporting the new type, ANSI C has defined a new type, long double , the implementation of which could refer to a new 80-bit type or 64-bit double . Unfortunately, although the purpose of the IEEE-754 80-bit type was to make all values ​​automatically move to the new type as they moved to double , ANSI has the new type passed to printf or other variational methods different from others floating point types, which makes automatic promotion impossible.

Consequently, both floating point types that existed when C was created could use the same %f format specifier, but the long double subsequently created requires a different %Lf format specifier (with upper case L ).

+2
Sep 11 '15 at 21:12
source share
%hhd , %ld and %lld were added to printf so that the format strings are more consistent with scanf , even if they are redundant for printf due to the stock default argument.

So why was not %hf added for float ? It's easy: looking at the behavior of scanf , float already has a format specifier. This is %f . And the format specifier for double is %lf .

This %lf is exactly what C99 is added to printf . Prior to C99, the behavior of %lf was undefined (by skipping any definition in the standard). With C99, it is a synonym for %f .

+2
Sep 12 '15 at 10:33
source share



All Articles