See §6.5.2.2/6-7 in the C99 standard.
§6.5.2.2/6 defines the default argument promotions: (emphasis added)
the integer promotions are performed on each argument, and arguments that have type float are promoted to double.
and specifies that these promotions are performed on arguments to a function declared with no prototype (that is, with an empty parameter list () instead of (void), where the latter is used to indicate no arguments).
Paragraph 7 additionally specifies that if the prototype of the function has a trailing ellipsis (...):
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.
The integer promotions are defined in §6.3.1.1/2; they apply to
objects or expressions of an integer type whose "integer conversion rank is less than or equal to the rank of int and unsigned int": roughly speaking, any smaller integer type, such as boolean or character types;
bit-fields of type _Bool, int, signed int or unsigned int.
If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned
int. These are called the integer promotions.
All other types are unchanged by the integer promotions.
In short, if you have varargs function, such as printf or scanf:
Integer arguments which are no larger than int are converted to (possibly unsigned) int. (This does not include long.)
Floating point arguments which are no larger than double are converted to double. (This includes float.)
Pointers are unaltered.
Other non-pointer types are unaltered.
So printf doesn't need to distinguish between float and double, because it will never be passed a float. It does need to distinguish between int and long.
But scanf does need to know whether an argument is a pointer to a float or a pointer to a double, because pointers are unchanged by the default argument promotions.