Skip to content

od: float output for -t f2/f4/f8 does not match GNU (trailing zeros, e-notation threshold, NaN spelling) #13608

Description

@HoseungChoi51

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:

               1            0.01           1e-05              10

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:

  1. Trailing zeros are not stripped. 11.0000000, 0.250.25000000.
  2. Scientific notation kicks in far too early. uutils switches at an exponent of -2; GNU (following %g) switches only below 1e-5. So 0.019.9999998e-3 instead of 0.01, and 0.06256.2500000e-2 instead of 0.0625.
  3. Exponents are not zero-padded to two digits. 5.9604645e-8 instead of GNU's 5.9604645e-08.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions