Summary
od's floating-point output (-t f2, -t f4, -t f8, and the fH/fB aliases) does not match GNU. GNU prints the shortest decimal representation that round-trips, rendered with printf's %g rules; uutils instead prints a fixed number of significant digits and switches to scientific notation far too early.
The most visible consequence is that ordinary values print incorrectly — od -t f4 of 1.0 gives 1.0000000 instead of 1, and 0.01 gives 9.9999998e-3 instead of 0.01.
Steps to reproduce
$ printf '\x00\x00\x80\x3f\x0a\xd7\x23\x3c\xac\xc5\x27\x37\x00\x00\x20\x41' | od -An -t f4
(the four little-endian float values 1.0, 0.01, 1e-05, 10.0)
GNU coreutils 9.7:
uutils (this repo, a01c36d):
1.0000000 9.9999998e-3 9.9999997e-6 10.000000
Same for double, with 1.0 and 0.1:
$ printf '\x00\x00\x00\x00\x00\x00\xf0\x3f\x9a\x99\x99\x99\x99\x99\xb9\x3f' | od -An -t f8
GNU : 1 0.1
uutils : 1.0000000000000000 0.10000000000000001
Distinct defects
Comparing every one of the 65536 half-precision bit patterns (-t f2) against GNU, 10624 render differently. They fall into four groups:
- Trailing zeros are not stripped.
1 → 1.0000000, 0.25 → 0.25000000.
- Scientific notation kicks in far too early. uutils switches at an exponent of
-2; GNU (following %g) switches only below 1e-5. So 0.01 → 9.9999998e-3 instead of 0.01, and 0.0625 → 6.2500000e-2 instead of 0.0625.
- Exponents are not zero-padded to two digits.
5.9604645e-8 instead of GNU's 5.9604645e-08.
NaN is spelled with the wrong case and loses its sign. GNU prints nan / -nan; uutils prints NaN for both.
Because uutils pads to a fixed significant-digit count instead of using the shortest round-tripping form, some values also gain visible representation error that GNU never shows — 1e-05 prints as 9.9999997e-6, and 1e38 prints as 9.9999997e+37 instead of 1e+38.
The half-precision path is inconsistent with the wider types today: format_item_f16/format_item_bf16 in src/uu/od/src/prn_float.rs run their result through trim_float_repr, which fixes defect (1) for those types only, while format_item_f32/format_item_f64 do not.
The rule GNU follows
I derived this purely by black-box comparison against the gnu* binaries (no GNU sources were consulted). Let
D = the smallest number of significant digits whose decimal rendering parses back to exactly the same value (≤ 9 for float, ≤ 17 for double),
X = the decimal exponent,
P = max(D, DIG) where DIG is 6 for float and 15 for double (FLT_DIG / DBL_DIG).
Then GNU renders scientific notation with D-1 fractional digits when X < -4 || X >= P, and fixed notation with D-1-X fractional digits otherwise, stripping trailing zeros in both cases — i.e. exactly %g with the shortest round-tripping precision, except that the fixed-vs-scientific choice uses at least DIG digits.
I validated this model against GNU on all 63490 non-NaN half-precision values and on 2462 float / 2468 double samples (all powers of ten from 1e-45 to 1e38, random bit patterns including subnormals, and random decimals): it reproduces GNU's output exactly, with zero mismatches.
Note this is not a recent GNU change — od from GNU coreutils 8.30, 8.32, 9.4 and 9.7 all agree on the outputs above.
Environment
- uutils commit
a01c36d14aa9101a0254eeafe28fe30fde92b7f9 (od (uutils coreutils) 0.9.0), release build
- GNU coreutils 9.7 (also checked 8.30 / 8.32 / 9.4)
- Ubuntu 25.10, x86_64,
LANG=C
Out of scope
-t fL (long double) is broken for an unrelated reason — the 80-bit x87 value is read as an f64, so od -An -t fL prints 0 where GNU prints 1. That's a decoding bug rather than a formatting one and isn't covered here.
Summary
od's floating-point output (-t f2,-t f4,-t f8, and thefH/fBaliases) does not match GNU. GNU prints the shortest decimal representation that round-trips, rendered withprintf's%grules; uutils instead prints a fixed number of significant digits and switches to scientific notation far too early.The most visible consequence is that ordinary values print incorrectly —
od -t f4of1.0gives1.0000000instead of1, and0.01gives9.9999998e-3instead of0.01.Steps to reproduce
$ printf '\x00\x00\x80\x3f\x0a\xd7\x23\x3c\xac\xc5\x27\x37\x00\x00\x20\x41' | od -An -t f4(the four little-endian
floatvalues1.0,0.01,1e-05,10.0)GNU coreutils 9.7:
uutils (this repo,
a01c36d):Same for
double, with1.0and0.1:Distinct defects
Comparing every one of the 65536 half-precision bit patterns (
-t f2) against GNU, 10624 render differently. They fall into four groups:1→1.0000000,0.25→0.25000000.-2; GNU (following%g) switches only below1e-5. So0.01→9.9999998e-3instead of0.01, and0.0625→6.2500000e-2instead of0.0625.5.9604645e-8instead of GNU's5.9604645e-08.NaNis spelled with the wrong case and loses its sign. GNU printsnan/-nan; uutils printsNaNfor both.Because uutils pads to a fixed significant-digit count instead of using the shortest round-tripping form, some values also gain visible representation error that GNU never shows —
1e-05prints as9.9999997e-6, and1e38prints as9.9999997e+37instead of1e+38.The half-precision path is inconsistent with the wider types today:
format_item_f16/format_item_bf16insrc/uu/od/src/prn_float.rsrun their result throughtrim_float_repr, which fixes defect (1) for those types only, whileformat_item_f32/format_item_f64do not.The rule GNU follows
I derived this purely by black-box comparison against the
gnu*binaries (no GNU sources were consulted). LetD= the smallest number of significant digits whose decimal rendering parses back to exactly the same value (≤ 9 forfloat, ≤ 17 fordouble),X= the decimal exponent,P = max(D, DIG)whereDIGis 6 forfloatand 15 fordouble(FLT_DIG/DBL_DIG).Then GNU renders scientific notation with
D-1fractional digits whenX < -4 || X >= P, and fixed notation withD-1-Xfractional digits otherwise, stripping trailing zeros in both cases — i.e. exactly%gwith the shortest round-tripping precision, except that the fixed-vs-scientific choice uses at leastDIGdigits.I validated this model against GNU on all 63490 non-NaN half-precision values and on 2462
float/ 2468doublesamples (all powers of ten from1e-45to1e38, random bit patterns including subnormals, and random decimals): it reproduces GNU's output exactly, with zero mismatches.Note this is not a recent GNU change —
odfrom GNU coreutils 8.30, 8.32, 9.4 and 9.7 all agree on the outputs above.Environment
a01c36d14aa9101a0254eeafe28fe30fde92b7f9(od (uutils coreutils) 0.9.0), release buildLANG=COut of scope
-t fL(long double) is broken for an unrelated reason — the 80-bit x87 value is read as anf64, sood -An -t fLprints0where GNU prints1. That's a decoding bug rather than a formatting one and isn't covered here.