diff --git a/src/uu/od/src/prn_float.rs b/src/uu/od/src/prn_float.rs index 155ce7d07..c93b02d25 100644 --- a/src/uu/od/src/prn_float.rs +++ b/src/uu/od/src/prn_float.rs @@ -37,8 +37,61 @@ pub static FORMAT_ITEM_BF16: FormatterItemInfo = FormatterItemInfo { formatter: FormatWriter::BFloatWriter(format_item_bf16), }; +/// Clean up a normalized float string by removing unnecessary padding and digits. +/// - Strip leading spaces. +/// - Trim trailing zeros after the decimal point (and the dot itself if empty). +/// - Leave the exponent part (e/E...) untouched. +fn trim_float_repr(raw: &str) -> String { + // Drop padding added by `format!` width specification + let mut s = raw.trim_start().to_string(); + + // Keep NaN/Inf representations as-is + let lower = s.to_ascii_lowercase(); + if lower == "nan" || lower == "inf" || lower == "-inf" { + return s; + } + + // Separate exponent from mantissa + let mut exp_part = String::new(); + if let Some(idx) = s.find(['e', 'E']) { + exp_part = s[idx..].to_string(); + s.truncate(idx); + } + + // Trim trailing zeros in mantissa, then remove trailing dot if left alone + if s.contains('.') { + while s.ends_with('0') { + s.pop(); + } + if s.ends_with('.') { + s.pop(); + } + } + + // If everything was trimmed, leave a single zero + if s.is_empty() || s == "-" || s == "+" { + s.push('0'); + } + + s.push_str(&exp_part); + s +} + +/// Pad a floating value to a fixed width for column alignment while keeping +/// the original precision (including trailing zeros). This mirrors the +/// behavior of other float formatters (`f32`, `f64`) and keeps the output +/// stable across platforms. +fn pad_float_repr(raw: &str, width: usize) -> String { + format!("{raw:>width$}") +} + pub fn format_item_f16(f: f64) -> String { - format!(" {}", format_f16(f16::from_f64(f))) + let value = f16::from_f64(f); + let width = FORMAT_ITEM_F16.print_width - 1; + // Format once, trim redundant zeros, then re-pad to the canonical width + let raw = format_f16(value); + let trimmed = trim_float_repr(&raw); + format!(" {}", pad_float_repr(&trimmed, width)) } pub fn format_item_f32(f: f64) -> String { @@ -82,7 +135,10 @@ fn format_f64_exp_precision(f: f64, width: usize, precision: usize) -> String { pub fn format_item_bf16(f: f64) -> String { let bf = bf16::from_f32(f as f32); - format!(" {}", format_binary16_like(f, 15, 8, is_subnormal_bf16(bf))) + let width = FORMAT_ITEM_BF16.print_width - 1; + let raw = format_binary16_like(f64::from(bf), width, 8, is_subnormal_bf16(bf)); + let trimmed = trim_float_repr(&raw); + format!(" {}", pad_float_repr(&trimmed, width)) } fn format_f16(f: f16) -> String { diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 54be34551..fea019e3a 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore abcdefghijklmnopqrstuvwxyz Anone fdbb littl +// spell-checker:ignore abcdefghijklmnopqrstuvwxyz Anone fdbb littl bfloat #[cfg(unix)] use std::io::Read; @@ -197,6 +197,32 @@ fn test_hex32() { .stdout_only(expected_output); } +// Regression: 16-bit IEEE half should print with canonical precision (no spurious digits) +#[test] +fn test_float16_compact() { + let input: [u8; 4] = [0x3c, 0x00, 0x3c, 0x00]; // two times 1.0 in big-endian half + new_ucmd!() + .arg("--endian=big") + .arg("-An") + .arg("-tfH") + .run_piped_stdin(&input[..]) + .success() + .stdout_only(" 1 1\n"); +} + +// Regression: 16-bit bfloat should print with canonical precision (no spurious digits) +#[test] +fn test_bfloat16_compact() { + let input: [u8; 4] = [0x3f, 0x80, 0x3f, 0x80]; // two times 1.0 in big-endian bfloat16 + new_ucmd!() + .arg("--endian=big") + .arg("-An") + .arg("-tfB") + .run_piped_stdin(&input[..]) + .success() + .stdout_only(" 1 1\n"); +} + #[test] fn test_f16() { let input: [u8; 14] = [ @@ -210,7 +236,7 @@ fn test_f16() { ]; // 0x8400 -6.104e-5 let expected_output = unindent( " - 0000000 1.0000000 0 -0 inf + 0000000 1 0 -0 inf 0000010 -inf NaN -6.1035156e-5 0000016 ", @@ -237,7 +263,7 @@ fn test_fh() { ]; // 0x8400 -6.1035156e-5 let expected_output = unindent( " - 0000000 1.0000000 0 -0 inf + 0000000 1 0 -0 inf 0000010 -inf NaN -6.1035156e-5 0000016 ", @@ -264,7 +290,7 @@ fn test_fb() { ]; // -6.1035156e-5 let expected_output = unindent( " - 0000000 1.0000000 0 -0 inf + 0000000 1 0 -0 inf 0000010 -inf NaN -6.1035156e-5 0000016 ",