Repository: uutils/coreutils (2025-08-05T05:30:48Z)

Issue:
od: 16-bit floats sometimes have wrong formatting
GNU od:
```
$ echo 'Hello world' | od -tfH
0000000      1352      4528  0.008659363      7644      4552  0.0001950264
0000014
```
uu_od:
```
$ echo 'Hello world' | cargo run -q od -tfH
0000000      1352      4528  8.659e-3      7644      4552  1.950e-4
0000014
```


Discussion:
I had a longer writeup, you can read below -- we can fix this, or at least get closer to GNU behaviour.

It looks like GNU od uses the same width/precision for both f2 and f4, so I think we could just fix that to match (even though that's not strictly necessary and... not super consistent with GNU od's behaviour of printing only necessary digits, see below):
https://github.com/drinkcat/coreutils/commit/2abee610d0a1e74a44f8f38656c99b8d87ca1ac2

Together with another fix that'd give us this output:
```
$ echo 'Hello world' | cargo run od -tf2
0000000            1352            4528    0.0086593628            7644            4552    0.0001950264
0000014
$ echo 'Hello world' | od -tf2
0000000            1352            4528     0.008659363            7644            4552    0.0001950264
0000014
```
Yes, still a slight rounding difference for the 3rd number.

*(longer writeup follows)*

---

First, note that `od` output is already not completely the same between GNU and uutils: GNU is more aggressive at removing digits when the binary representation would be the same, but that applies on larger float formats -- single and double float:

An example with the same input but f64 formatting:
```
$ echo 'Hello world' | cargo run od -tf8
0000000  8.7657764788278537e+228          8.61420855e-316
0000014
$ echo 'Hello world' | od -tf8
0000000   8.765776478827854e+228          8.61420855e-316
```

I'm still not sure what we should do about it, since I feel both output are technically correct?

Back to this one. This is the reverse, uutils trims more digits.

That being said `1.950e-4` does identify the number "unambiguous"-ly ("unambiguous" come from GNU manual), as the previous and next f16 numbers would are 1.949 and 1.951e4, so printing `0.000195`, or even `1.950e-4`, sounds reasonable to me.
```
printf '\x63\x0a\x64\x0a\x65\x0a' | od -tfH
0000000   0.00019490719    0.0001950264    0.0001951456
```




Patch:
diff --git a/src/uu/od/src/prn_float.rs b/src/uu/od/src/prn_float.rs
index f99ebc6382a..37059e226d8 100644
--- a/src/uu/od/src/prn_float.rs
+++ b/src/uu/od/src/prn_float.rs
@@ -9,13 +9,13 @@ use crate::formatter_item_info::{FormatWriter, FormatterItemInfo};
 
 pub static FORMAT_ITEM_F16: FormatterItemInfo = FormatterItemInfo {
     byte_size: 2,
-    print_width: 10,
+    print_width: 16,
     formatter: FormatWriter::FloatWriter(format_item_f16),
 };
 
 pub static FORMAT_ITEM_F32: FormatterItemInfo = FormatterItemInfo {
     byte_size: 4,
-    print_width: 15,
+    print_width: 16,
     formatter: FormatWriter::FloatWriter(format_item_f32),
 };
 
@@ -27,7 +27,7 @@ pub static FORMAT_ITEM_F64: FormatterItemInfo = FormatterItemInfo {
 
 pub static FORMAT_ITEM_BF16: FormatterItemInfo = FormatterItemInfo {
     byte_size: 2,
-    print_width: 15,
+    print_width: 16,
     formatter: FormatWriter::BFloatWriter(format_item_bf16),
 };
 
@@ -75,13 +75,13 @@ pub fn format_item_bf16(f: f64) -> String {
 }
 
 fn format_f16(f: f16) -> String {
-    format_float(f64::from(f), 9, 4)
+    format_float(f64::from(f), 15, 8)
 }
 
 /// formats float with 8 significant digits, eg 12345678 or -1.2345678e+12
 /// always returns a string of 14 characters
 fn format_f32(f: f32) -> String {
-    let width: usize = 14;
+    let width: usize = 15;
     let precision: usize = 8;
 
     if f.classify() == FpCategory::Subnormal {
@@ -128,78 +128,78 @@ fn format_float(f: f64, width: usize, precision: usize) -> String {
 #[allow(clippy::excessive_precision)]
 #[allow(clippy::cognitive_complexity)]
 fn test_format_f32() {
-    assert_eq!(format_f32(1.0), "     1.0000000");
-    assert_eq!(format_f32(9.999_999_0), "     9.9999990");
-    assert_eq!(format_f32(10.0), "     10.000000");
-    assert_eq!(format_f32(99.999_977), "     99.999977");
-    assert_eq!(format_f32(99.999_992), "     99.999992");
-    assert_eq!(format_f32(100.0), "     100.00000");
-    assert_eq!(format_f32(999.99994), "     999.99994");
-    assert_eq!(format_f32(1000.0), "     1000.0000");
-    assert_eq!(format_f32(9999.9990), "     9999.9990");
-    assert_eq!(format_f32(10000.0), "     10000.000");
-    assert_eq!(format_f32(99999.992), "     99999.992");
-    assert_eq!(format_f32(100_000.0), "     100000.00");
-    assert_eq!(format_f32(999_999.94), "     999999.94");
-    assert_eq!(format_f32(1_000_000.0), "     1000000.0");
-    assert_eq!(format_f32(9_999_999.0), "     9999999.0");
-    assert_eq!(format_f32(10_000_000.0), "      10000000");
-    assert_eq!(format_f32(99_999_992.0), "      99999992");
-    assert_eq!(format_f32(100_000_000.0), "  1.0000000e+8");
-    assert_eq!(format_f32(9.999_999_4e8), "  9.9999994e+8");
-    assert_eq!(format_f32(1.0e9), "  1.0000000e+9");
-    assert_eq!(format_f32(9.999_999_0e9), "  9.9999990e+9");
-    assert_eq!(format_f32(1.0e10), " 1.0000000e+10");
-
-    assert_eq!(format_f32(0.1), "    0.10000000");
-    assert_eq!(format_f32(0.999_999_94), "    0.99999994");
-    assert_eq!(format_f32(0.010_000_001), "  1.0000001e-2");
-    assert_eq!(format_f32(0.099_999_994), "  9.9999994e-2");
-    assert_eq!(format_f32(0.001), "  1.0000000e-3");
-    assert_eq!(format_f32(0.009_999_999_8), "  9.9999998e-3");
-
-    assert_eq!(format_f32(-1.0), "    -1.0000000");
-    assert_eq!(format_f32(-9.999_999_0), "    -9.9999990");
-    assert_eq!(format_f32(-10.0), "    -10.000000");
-    assert_eq!(format_f32(-99.999_977), "    -99.999977");
-    assert_eq!(format_f32(-99.999_992), "    -99.999992");
-    assert_eq!(format_f32(-100.0), "    -100.00000");
-    assert_eq!(format_f32(-999.99994), "    -999.99994");
-    assert_eq!(format_f32(-1000.0), "    -1000.0000");
-    assert_eq!(format_f32(-9999.9990), "    -9999.9990");
-    assert_eq!(format_f32(-10000.0), "    -10000.000");
-    assert_eq!(format_f32(-99999.992), "    -99999.992");
-    assert_eq!(format_f32(-100_000.0), "    -100000.00");
-    assert_eq!(format_f32(-999_999.94), "    -999999.94");
-    assert_eq!(format_f32(-1_000_000.0), "    -1000000.0");
-    assert_eq!(format_f32(-9_999_999.0), "    -9999999.0");
-    assert_eq!(format_f32(-10_000_000.0), "     -10000000");
-    assert_eq!(format_f32(-99_999_992.0), "     -99999992");
-    assert_eq!(format_f32(-100_000_000.0), " -1.0000000e+8");
-    assert_eq!(format_f32(-9.999_999_4e8), " -9.9999994e+8");
-    assert_eq!(format_f32(-1.0e9), " -1.0000000e+9");
-    assert_eq!(format_f32(-9.999_999_0e9), " -9.9999990e+9");
-    assert_eq!(format_f32(-1.0e10), "-1.0000000e+10");
-
-    assert_eq!(format_f32(-0.1), "   -0.10000000");
-    assert_eq!(format_f32(-0.999_999_94), "   -0.99999994");
-    assert_eq!(format_f32(-0.010_000_001), " -1.0000001e-2");
-    assert_eq!(format_f32(-0.099_999_994), " -9.9999994e-2");
-    assert_eq!(format_f32(-0.001), " -1.0000000e-3");
-    assert_eq!(format_f32(-0.009_999_999_8), " -9.9999998e-3");
-
-    assert_eq!(format_f32(3.402_823_3e38), " 3.4028233e+38");
-    assert_eq!(format_f32(-3.402_823_3e38), "-3.4028233e+38");
-    assert_eq!(format_f32(-1.166_310_8e-38), "-1.1663108e-38");
-    assert_eq!(format_f32(-4.701_977_1e-38), "-4.7019771e-38");
-    assert_eq!(format_f32(1e-45), "         1e-45");
-
-    assert_eq!(format_f32(-3.402_823_466e+38), "-3.4028235e+38");
-    assert_eq!(format_f32(f32::NAN), "           NaN");
-    assert_eq!(format_f32(f32::INFINITY), "           inf");
-    assert_eq!(format_f32(f32::NEG_INFINITY), "          -inf");
-    assert_eq!(format_f32(-0.0), "            -0");
-    assert_eq!(format_f32(0.0), "             0");
+    assert_eq!(format_f32(1.0), "      1.0000000");
+    assert_eq!(format_f32(9.999_999_0), "      9.9999990");
+    assert_eq!(format_f32(10.0), "      10.000000");
+    assert_eq!(format_f32(99.999_977), "      99.999977");
+    assert_eq!(format_f32(99.999_992), "      99.999992");
+    assert_eq!(format_f32(100.0), "      100.00000");
+    assert_eq!(format_f32(999.99994), "      999.99994");
+    assert_eq!(format_f32(1000.0), "      1000.0000");
+    assert_eq!(format_f32(9999.9990), "      9999.9990");
+    assert_eq!(format_f32(10000.0), "      10000.000");
+    assert_eq!(format_f32(99999.992), "      99999.992");
+    assert_eq!(format_f32(100_000.0), "      100000.00");
+    assert_eq!(format_f32(999_999.94), "      999999.94");
+    assert_eq!(format_f32(1_000_000.0), "      1000000.0");
+    assert_eq!(format_f32(9_999_999.0), "      9999999.0");
+    assert_eq!(format_f32(10_000_000.0), "       10000000");
+    assert_eq!(format_f32(99_999_992.0), "       99999992");
+    assert_eq!(format_f32(100_000_000.0), "   1.0000000e+8");
+    assert_eq!(format_f32(9.999_999_4e8), "   9.9999994e+8");
+    assert_eq!(format_f32(1.0e9), "   1.0000000e+9");
+    assert_eq!(format_f32(9.999_999_0e9), "   9.9999990e+9");
+    assert_eq!(format_f32(1.0e10), "  1.0000000e+10");
+
+    assert_eq!(format_f32(0.1), "     0.10000000");
+    assert_eq!(format_f32(0.999_999_94), "     0.99999994");
+    assert_eq!(format_f32(0.010_000_001), "   1.0000001e-2");
+    assert_eq!(format_f32(0.099_999_994), "   9.9999994e-2");
+    assert_eq!(format_f32(0.001), "   1.0000000e-3");
+    assert_eq!(format_f32(0.009_999_999_8), "   9.9999998e-3");
+
+    assert_eq!(format_f32(-1.0), "     -1.0000000");
+    assert_eq!(format_f32(-9.999_999_0), "     -9.9999990");
+    assert_eq!(format_f32(-10.0), "     -10.000000");
+    assert_eq!(format_f32(-99.999_977), "     -99.999977");
+    assert_eq!(format_f32(-99.999_992), "     -99.999992");
+    assert_eq!(format_f32(-100.0), "     -100.00000");
+    assert_eq!(format_f32(-999.99994), "     -999.99994");
+    assert_eq!(format_f32(-1000.0), "     -1000.0000");
+    assert_eq!(format_f32(-9999.9990), "     -9999.9990");
+    assert_eq!(format_f32(-10000.0), "     -10000.000");
+    assert_eq!(format_f32(-99999.992), "     -99999.992");
+    assert_eq!(format_f32(-100_000.0), "     -100000.00");
+    assert_eq!(format_f32(-999_999.94), "     -999999.94");
+    assert_eq!(format_f32(-1_000_000.0), "     -1000000.0");
+    assert_eq!(format_f32(-9_999_999.0), "     -9999999.0");
+    assert_eq!(format_f32(-10_000_000.0), "      -10000000");
+    assert_eq!(format_f32(-99_999_992.0), "      -99999992");
+    assert_eq!(format_f32(-100_000_000.0), "  -1.0000000e+8");
+    assert_eq!(format_f32(-9.999_999_4e8), "  -9.9999994e+8");
+    assert_eq!(format_f32(-1.0e9), "  -1.0000000e+9");
+    assert_eq!(format_f32(-9.999_999_0e9), "  -9.9999990e+9");
+    assert_eq!(format_f32(-1.0e10), " -1.0000000e+10");
+
+    assert_eq!(format_f32(-0.1), "    -0.10000000");
+    assert_eq!(format_f32(-0.999_999_94), "    -0.99999994");
+    assert_eq!(format_f32(-0.010_000_001), "  -1.0000001e-2");
+    assert_eq!(format_f32(-0.099_999_994), "  -9.9999994e-2");
+    assert_eq!(format_f32(-0.001), "  -1.0000000e-3");
+    assert_eq!(format_f32(-0.009_999_999_8), "  -9.9999998e-3");
+
+    assert_eq!(format_f32(3.402_823_3e38), "  3.4028233e+38");
+    assert_eq!(format_f32(-3.402_823_3e38), " -3.4028233e+38");
+    assert_eq!(format_f32(-1.166_310_8e-38), " -1.1663108e-38");
+    assert_eq!(format_f32(-4.701_977_1e-38), " -4.7019771e-38");
+    assert_eq!(format_f32(1e-45), "          1e-45");
+
+    assert_eq!(format_f32(-3.402_823_466e+38), " -3.4028235e+38");
+    assert_eq!(format_f32(f32::NAN), "            NaN");
+    assert_eq!(format_f32(f32::INFINITY), "            inf");
+    assert_eq!(format_f32(f32::NEG_INFINITY), "           -inf");
+    assert_eq!(format_f32(-0.0), "             -0");
+    assert_eq!(format_f32(0.0), "              0");
 }
 
 #[test]
@@ -238,25 +238,25 @@ fn test_format_f64() {
 #[test]
 #[allow(clippy::cognitive_complexity)]
 fn test_format_f16() {
-    assert_eq!(format_f16(f16::from_bits(0x8400u16)), "-6.104e-5");
-    assert_eq!(format_f16(f16::from_bits(0x8401u16)), "-6.109e-5");
-    assert_eq!(format_f16(f16::from_bits(0x8402u16)), "-6.115e-5");
-    assert_eq!(format_f16(f16::from_bits(0x8403u16)), "-6.121e-5");
-
-    assert_eq!(format_f16(f16::from_f32(1.0)), "    1.000");
-    assert_eq!(format_f16(f16::from_f32(10.0)), "    10.00");
-    assert_eq!(format_f16(f16::from_f32(100.0)), "    100.0");
-    assert_eq!(format_f16(f16::from_f32(1000.0)), "     1000");
-    assert_eq!(format_f16(f16::from_f32(10000.0)), " 1.000e+4");
-
-    assert_eq!(format_f16(f16::from_f32(-0.2)), "  -0.2000");
-    assert_eq!(format_f16(f16::from_f32(-0.02)), "-2.000e-2");
-
-    assert_eq!(format_f16(f16::MIN_POSITIVE_SUBNORMAL), " 5.960e-8");
-    assert_eq!(format_f16(f16::MIN), "-6.550e+4");
-    assert_eq!(format_f16(f16::NAN), "      NaN");
-    assert_eq!(format_f16(f16::INFINITY), "      inf");
-    assert_eq!(format_f16(f16::NEG_INFINITY), "     -inf");
-    assert_eq!(format_f16(f16::NEG_ZERO), "       -0");
-    assert_eq!(format_f16(f16::ZERO), "        0");
+    assert_eq!(format_f16(f16::from_bits(0x8400u16)), "  -6.1035156e-5");
+    assert_eq!(format_f16(f16::from_bits(0x8401u16)), "  -6.1094761e-5");
+    assert_eq!(format_f16(f16::from_bits(0x8402u16)), "  -6.1154366e-5");
+    assert_eq!(format_f16(f16::from_bits(0x8403u16)), "  -6.1213970e-5");
+
+    assert_eq!(format_f16(f16::from_f32(1.0)), "      1.0000000");
+    assert_eq!(format_f16(f16::from_f32(10.0)), "      10.000000");
+    assert_eq!(format_f16(f16::from_f32(100.0)), "      100.00000");
+    assert_eq!(format_f16(f16::from_f32(1000.0)), "      1000.0000");
+    assert_eq!(format_f16(f16::from_f32(10000.0)), "      10000.000");
+
+    assert_eq!(format_f16(f16::from_f32(-0.2)), "    -0.19995117");
+    assert_eq!(format_f16(f16::from_f32(-0.02)), "  -2.0004272e-2");
+
+    assert_eq!(format_f16(f16::MIN_POSITIVE_SUBNORMAL), "   5.9604645e-8");
+    assert_eq!(format_f16(f16::MIN), "     -65504.000");
+    assert_eq!(format_f16(f16::NAN), "            NaN");
+    assert_eq!(format_f16(f16::INFINITY), "            inf");
+    assert_eq!(format_f16(f16::NEG_INFINITY), "           -inf");
+    assert_eq!(format_f16(f16::NEG_ZERO), "             -0");
+    assert_eq!(format_f16(f16::ZERO), "              0");
 }


Test patch:
diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs
index 2c3ae2a959f..2d76a8dd8b3 100644
--- a/tests/by-util/test_od.rs
+++ b/tests/by-util/test_od.rs
@@ -206,8 +206,8 @@ fn test_f16() {
     ]; // 0x8400 -6.104e-5
     let expected_output = unindent(
         "
-            0000000     1.000         0        -0       inf
-            0000010      -inf       NaN -6.104e-5
+            0000000       1.0000000               0              -0             inf
+            0000010            -inf             NaN   -6.1035156e-5
             0000016
             ",
     );
@@ -231,11 +231,11 @@ fn test_fh() {
         0x00, 0xfc, // 0xFC00 -Inf
         0x00, 0xfe, // 0xFE00 NaN
         0x00, 0x84,
-    ]; // 0x8400 -6.104e-5
+    ]; // 0x8400 -6.1035156e-5
     let expected_output = unindent(
         "
-            0000000     1.000         0        -0       inf
-            0000010      -inf       NaN -6.104e-5
+            0000000       1.0000000               0              -0             inf
+            0000010            -inf             NaN   -6.1035156e-5
             0000016
         ",
     );
@@ -262,8 +262,8 @@ fn test_fb() {
     ]; // -6.1035156e-5
     let expected_output = unindent(
         "
-            0000000      1.0000000              0             -0            inf
-            0000010           -inf            NaN  -6.1035156e-5
+            0000000       1.0000000               0              -0             inf
+            0000010            -inf             NaN   -6.1035156e-5
             0000016
         ",
     );
@@ -290,8 +290,8 @@ fn test_f32() {
     ]; // 0x807f0000 -1.1663108E-38
     let expected_output = unindent(
         "
-            0000000     -1.2345679       12345678 -9.8765427e+37             -0
-            0000020            NaN          1e-40 -1.1663108e-38
+            0000000      -1.2345679        12345678  -9.8765427e+37              -0
+            0000020             NaN           1e-40  -1.1663108e-38
             0000034
             ",
     );
@@ -467,10 +467,10 @@ fn test_big_endian() {
 
     let expected_output = unindent(
         "
-        0000000           -2.0000000000000000
-                    -2.0000000              0
-                      c0000000       00000000
-                   c000   0000    0000   0000
+        0000000             -2.0000000000000000
+                     -2.0000000               0
+                       c0000000        00000000
+                   c000    0000    0000    0000
         0000010
         ",
     );