{v:g} was str()'s form, not %g — while the correct %g sat one file away
- Type: bug (NilPy format specs —
pyformat_of(d: Double; spec)incompiler/builtin/pylib.pas). Track N. - Status: done
- Found: 2026-08-16, NilPy oracle sweep vs CPython 3 (numeric/formatting topic).
- Shape: one concept, two implementations, and the second one was wrong —
devdocs/dev/normalise-dont-special-case.md.PyFmtGis correct and has been all along; the printf-style%goperator calls it. The f-string /format()spec path calledFloatToStrinstead.
Symptom
| expression | CPython | pxx |
|---|---|---|
f"{1e-5:g}" |
1e-05 |
0.00001 |
f"{1e-7:g}" |
1e-07 |
0.0000001 |
f"{1e16:g}" |
1e+16 |
10000000000000000.0 |
f"{123456.789:g}" |
123457 |
123456.789000000004307 |
f"{123456.789:.3}" |
1.23e+05 |
123456.789 |
%g rounds to prec SIGNIFICANT digits (default 6) and switches to the
exponential form when the exponent leaves [-4, prec). FloatToStr is
str()'s shortest-round-trip form, which answers a different question — and on
the last row it is not even that, because FloatToStr is Pascal's fifteen-place
formatter rather than NilPy's own PyFloatStr repr.
Fix
g/Gwith an explicit type char, or with a precision, now callsPyFmtG(d, prec, upper).- A spec naming no type (
{v},{v:,},{v:10}) stays str()-shaped, which is CPython's rule —format(123456.789, '')is123456.789, not123457— and now usesPyFloatStr(NilPy's repr) rather thanFloatToStr, which is what fixed{v:,}printing123,456.789000000004307. - A precision with no type char is general format in CPython (
{v:.3}is{v:.3g}), so the default kind is nowgwhether or not a precision was given; it used to stay on the initialfand print fixed-point.
Gate
make compiler/pascal26 fixedpoint; tools/gate.sh quick GREEN;
make stabilize-fast && make pin (v341) because this is a compiler/builtin
change; test/test_nilpy_format_g_spec.npy — every spec form crossed with
eleven values, plus the no-type rows that must NOT move — matches CPython 3
byte for byte.