I think this is the crux of it. I think of formatting as the composition of two separate operations: a rounding operation (whose target is some kind of intermediate decimal type), followed by a presentation operation. Those two operations are largely independent, but can’t be controlled independently: f format says “round to p places after the point, then present in non-scientific notation”, while e format says “round to p+1 significant figures, then present in scientific notation”. There’s no built-in way to mix and match (“round to p significant figures, then present in non-scientific notation”), even though it’s perfectly meaningful to do so. (As already mentioned, g mode is more of a do-what-I-mean-without-me-having-to-think-too-much mode, and has hard-coded choices that make it not particularly useful in situations where you want fine control.)
The more general problem here, which seems quite fundamental, is that (a) there are many many possible variations that people might want for formatting, but (b) there’s a limit to what can reasonably be encoded in the formatting mini-language without it becoming an unmaintainable and complex (not to mention unreadable) mess - IMO, the problem space is just too big to be able to be expressed clearly in the mini-language. The best that the mini-language can hope to do is to cover common use-cases.
Luckily, it’s easy to escape the restrictions of the mini-language by doing your own formatting directly, and I think f-strings make that option non-horrible to read:
from fancy_formatting import FancyFormatter
f5sig = FancyFormatter(
significant_figures=5,
presentation_mode="fixed",
trim_trailing_zeros=False,
sign_on_negative_zero=False,
... # more options here
)
fruitiness: float = do_calculations_here()
print(f"Optimal fruitiness is {f5sig(fruitiness)}")