Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions src/uu/od/src/prn_float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
34 changes: 30 additions & 4 deletions tests/by-util/test_od.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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] = [
Expand All @@ -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
",
Expand All @@ -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
",
Expand All @@ -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
",
Expand Down
Loading