Skip to content

Commit 003f21a

Browse files
fix(od):fix GNU coreutils test od float.sh (#9534)
* feat: add compact float formatting for half and bfloat16 in od Implement trim_float_repr() to remove trailing zeros from float strings while preserving signs and exponents, and pad_float_repr() to align trimmed floats to fixed width. Update format_item_f16() and format_item_bf16() to produce compact output matching GNU od. Add regression tests for float16 and bfloat16 compact printing. * refactor(od): format multiline format! in format_item_bf16 for readability Reformat the format! macro call in the format_item_bf16 function in prn_float.rs to span multiple lines, improving code readability without changing functionality. * fix(od): preserve canonical precision for f16/bf16 float formats Remove trimming of trailing zeros from f16 and bf16 float representations in od output to maintain original precision and align behavior with f32/f64 formatters, ensuring stable output across platforms. Update corresponding tests to reflect the change in expected output. * refactor(od): simplify float padding format and update tests - Remove redundant `width = width` parameter from `format!` macro in `pad_float_repr` - Add "bfloat" to spell-checker ignore list for better test coverage on bf16 format * feat(od): trim trailing zeros in float outputs for GNU compatibility Add `trim_trailing_zeros` function to remove trailing zeros and redundant decimal points from formatted floats, ensuring compact output matching GNU od for f16 and bf16 types. Update `format_item_f16` and `format_item_bf16` to apply trimming before padding. * fix: preserve trailing zeros in F16 and BF16 float formats to match GNU od output Remove the `trim_trailing_zeros` function and update `format_item_f16` and `format_item_bf16` to keep the raw formatted strings without trimming trailing zeros. This ensures consistent column widths and aligns with GNU od behavior for 16-bit float representations, preventing misalignment in output tables. * refactor(od/prn_float): combine multiline format! into single line in format_item_f16 The format! macro call in format_item_f16 was split across multiple lines with newlines. This change consolidates it into a single line for improved code readability and consistency with similar patterns in the file, without altering the function's output or logic. * feat(od): trim trailing zeros in half-precision and bfloat16 float outputs - Add `trim_float_repr` function to remove unnecessary trailing zeros and padding from normalized float strings, leaving exponents unchanged. - Update `format_item_f16` and `format_item_bf16` to apply trimming while maintaining column alignment via re-padding. - Update test expectations to reflect the more compact float representations (e.g., "1" instead of "1.0000000"). * refactor: simplify float trimming condition in prn_float.rs Replace `if let Some(_) = s.find('.')` with `s.find('.').is_some()` in the `trim_float_repr` function to improve code clarity and idiomatic Rust usage while maintaining the same logic for checking decimal presence=black. * Update src/uu/od/src/prn_float.rs Co-authored-by: Daniel Hofstetter <[email protected]> --------- Co-authored-by: Daniel Hofstetter <[email protected]>
1 parent ee39b35 commit 003f21a

File tree

2 files changed

+88
-6
lines changed

2 files changed

+88
-6
lines changed

src/uu/od/src/prn_float.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,61 @@ pub static FORMAT_ITEM_BF16: FormatterItemInfo = FormatterItemInfo {
3737
formatter: FormatWriter::BFloatWriter(format_item_bf16),
3838
};
3939

40+
/// Clean up a normalized float string by removing unnecessary padding and digits.
41+
/// - Strip leading spaces.
42+
/// - Trim trailing zeros after the decimal point (and the dot itself if empty).
43+
/// - Leave the exponent part (e/E...) untouched.
44+
fn trim_float_repr(raw: &str) -> String {
45+
// Drop padding added by `format!` width specification
46+
let mut s = raw.trim_start().to_string();
47+
48+
// Keep NaN/Inf representations as-is
49+
let lower = s.to_ascii_lowercase();
50+
if lower == "nan" || lower == "inf" || lower == "-inf" {
51+
return s;
52+
}
53+
54+
// Separate exponent from mantissa
55+
let mut exp_part = String::new();
56+
if let Some(idx) = s.find(['e', 'E']) {
57+
exp_part = s[idx..].to_string();
58+
s.truncate(idx);
59+
}
60+
61+
// Trim trailing zeros in mantissa, then remove trailing dot if left alone
62+
if s.contains('.') {
63+
while s.ends_with('0') {
64+
s.pop();
65+
}
66+
if s.ends_with('.') {
67+
s.pop();
68+
}
69+
}
70+
71+
// If everything was trimmed, leave a single zero
72+
if s.is_empty() || s == "-" || s == "+" {
73+
s.push('0');
74+
}
75+
76+
s.push_str(&exp_part);
77+
s
78+
}
79+
80+
/// Pad a floating value to a fixed width for column alignment while keeping
81+
/// the original precision (including trailing zeros). This mirrors the
82+
/// behavior of other float formatters (`f32`, `f64`) and keeps the output
83+
/// stable across platforms.
84+
fn pad_float_repr(raw: &str, width: usize) -> String {
85+
format!("{raw:>width$}")
86+
}
87+
4088
pub fn format_item_f16(f: f64) -> String {
41-
format!(" {}", format_f16(f16::from_f64(f)))
89+
let value = f16::from_f64(f);
90+
let width = FORMAT_ITEM_F16.print_width - 1;
91+
// Format once, trim redundant zeros, then re-pad to the canonical width
92+
let raw = format_f16(value);
93+
let trimmed = trim_float_repr(&raw);
94+
format!(" {}", pad_float_repr(&trimmed, width))
4295
}
4396

4497
pub fn format_item_f32(f: f64) -> String {
@@ -82,7 +135,10 @@ fn format_f64_exp_precision(f: f64, width: usize, precision: usize) -> String {
82135

83136
pub fn format_item_bf16(f: f64) -> String {
84137
let bf = bf16::from_f32(f as f32);
85-
format!(" {}", format_binary16_like(f, 15, 8, is_subnormal_bf16(bf)))
138+
let width = FORMAT_ITEM_BF16.print_width - 1;
139+
let raw = format_binary16_like(f64::from(bf), width, 8, is_subnormal_bf16(bf));
140+
let trimmed = trim_float_repr(&raw);
141+
format!(" {}", pad_float_repr(&trimmed, width))
86142
}
87143

88144
fn format_f16(f: f16) -> String {

tests/by-util/test_od.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore abcdefghijklmnopqrstuvwxyz Anone fdbb littl
6+
// spell-checker:ignore abcdefghijklmnopqrstuvwxyz Anone fdbb littl bfloat
77

88
#[cfg(unix)]
99
use std::io::Read;
@@ -197,6 +197,32 @@ fn test_hex32() {
197197
.stdout_only(expected_output);
198198
}
199199

200+
// Regression: 16-bit IEEE half should print with canonical precision (no spurious digits)
201+
#[test]
202+
fn test_float16_compact() {
203+
let input: [u8; 4] = [0x3c, 0x00, 0x3c, 0x00]; // two times 1.0 in big-endian half
204+
new_ucmd!()
205+
.arg("--endian=big")
206+
.arg("-An")
207+
.arg("-tfH")
208+
.run_piped_stdin(&input[..])
209+
.success()
210+
.stdout_only(" 1 1\n");
211+
}
212+
213+
// Regression: 16-bit bfloat should print with canonical precision (no spurious digits)
214+
#[test]
215+
fn test_bfloat16_compact() {
216+
let input: [u8; 4] = [0x3f, 0x80, 0x3f, 0x80]; // two times 1.0 in big-endian bfloat16
217+
new_ucmd!()
218+
.arg("--endian=big")
219+
.arg("-An")
220+
.arg("-tfB")
221+
.run_piped_stdin(&input[..])
222+
.success()
223+
.stdout_only(" 1 1\n");
224+
}
225+
200226
#[test]
201227
fn test_f16() {
202228
let input: [u8; 14] = [
@@ -210,7 +236,7 @@ fn test_f16() {
210236
]; // 0x8400 -6.104e-5
211237
let expected_output = unindent(
212238
"
213-
0000000 1.0000000 0 -0 inf
239+
0000000 1 0 -0 inf
214240
0000010 -inf NaN -6.1035156e-5
215241
0000016
216242
",
@@ -237,7 +263,7 @@ fn test_fh() {
237263
]; // 0x8400 -6.1035156e-5
238264
let expected_output = unindent(
239265
"
240-
0000000 1.0000000 0 -0 inf
266+
0000000 1 0 -0 inf
241267
0000010 -inf NaN -6.1035156e-5
242268
0000016
243269
",
@@ -264,7 +290,7 @@ fn test_fb() {
264290
]; // -6.1035156e-5
265291
let expected_output = unindent(
266292
"
267-
0000000 1.0000000 0 -0 inf
293+
0000000 1 0 -0 inf
268294
0000010 -inf NaN -6.1035156e-5
269295
0000016
270296
",

0 commit comments

Comments
 (0)