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.
%lfforlong double, the standard however specifies%Lffor that. I've updated the question.%Lddoes not meanlong, either. What that means is undefined behaviour (the same section and paragraph as passing an incorrect argument renders your previous%lf->long doublecorrespondence undefined). Note the lack of pattern?scanfspecifier, so next subclause applies. Aboutlong float, see for example C99 Rationale, page 42. What would be a guess could be to decide whether the ANSI committee added explicit%lftoprintf(it was undefined behaviour (by omission) in V7) to either cater with then-existing usage forlong float, to align withscanf, or if both apply.