Are %f and %lf interchangeable when using printf?

172 views Asked by At

In an article on the w3resource.com, I've seen the following code:

double x, y;
pr1 = sqrt(pr1);
x = (-b + pr1)/(2*a);
y = (-b - pr1)/(2*a);
printf("Root1 = %.5lf\n", x);
printf("Root1 = %.5lf\n", y);

The article says to take three floating-point numbers. But in their solution (this code snippet), they have used the %lf format specifier. Why have they used %lf instead of %f? Can I also use %f instead of %lf for floating-point numbers?

If I use a float keyword instead of double, and use %f, is there any issue? The output is the same for me.

2

There are 2 answers

0
Jan Schultke On

%f has different meaning in printf and scanf family functions:

  • in printf, %f and %lf are equivalent, and both are matched by double. This is because in variadic functions, all float arguments are promoted to double when the function is called, so there is no difference between passing float and double at a language level.
  • in scanf, %f matches a float* and %lf matches a double*, and this distinction is very important. Using the wrong type of pointer will result in undefined behavior.

You can also use cdecl+ to better understand printf and scanf calls. The output is as follows:

printf("Root1 = %.5lf\n", x)
Write "Root1 = "
Write a decimal double with lower-case infinity/NaN symbols
    precision: 5
Write "\n"

ARGUMENTS & EXPECTED TYPES
--------------------------
x (double)

When in doubt, use %f for float and %lf for double, even in printf, where there is no difference between the two. You will introduce pointless inconsistencies between your scanf and printf format strings otherwise.

Note on floating-point numbers

You may have gotten confused by the meaning of float and floating-point number. float, double, long double, and more are floating-point numbers in C. When the article says floating-point number, they could also mean double, _Decimal32, _Float128, etc.

Why doesn't scanf promote float* to double* similar to printf?

This is not possible, because the object in which we're storing the float cannot be used to store a double.

  • At a language level, this would be a strict aliasing violation.
  • At an implementation-level, we would try to store e.g. eight bytes of double in a float which consists of four bytes, which is impossible.
7
Vlad from Moscow On

From the C Standard (7.21.6.1 The fprintf function)

l (ell) Specifies that a following d, i, o, u, x, or X conversion specifier applies to a long int or unsigned long int argument; that a following n conversion specifier applies to a pointer to a long int argument; that a following c conversion specifier applies to a wint_t argument; that a following s conversion specifier applies to a pointer to a wchar_t argument; or has no effect on a following a, A, e, E, f, F, g, or G conversion specifier.

and (6.5.2.2 Function calls)

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.

and

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.

Using the length modifier l with the conversion specifier f will only confuse readers of the code. Its using will arise questions. The readers of the code will think that either the author of the code does not know that the length modifier has no effect or that without the length modifier a call of printf (or fprintf) will indeed have undefined behavior and using the length modifier is necessary.

As for using the length modifier with a call of scanf then the function deals with pointers of the type float * or double *. That is the default argument promotions are not applied to them. So to distinguish whether a pointer points to an object of the type float or to an object of the type double you have to use the length modifier l with the conversion specifier f with pointers of the type double * or without the length modifier with pointers of the type float *.

Adepts of using the length modifier l with objects of the type double in calls of printf (or fprintf) only make code less maintainable because for example if the type of an outputted variable will be changed from the type double to the type float they will have to find and change all calls printf where the variable is used.