uutils / uutils/coreutils

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

Open
#13,608 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

U - od
Dominant language
Rust
Stars
24.1k
Forks
2k
Avg merge
1d 5h
Merged PRs (30d)
365

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/uu/od/src/prn_float.rs, especially format_item_f16, format_item_bf16, format_item_f32, format_item_f64, and trim_float_repr. Reproduce the examples with od -An -t f4 and -t f8, then make floating-point output follow the described shortest round-tripping and %g-style rules, including exponent padding and signed NaN spelling. Done means the listed values and the half-precision comparison match GNU.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.