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
173 changes: 158 additions & 15 deletions src/uu/numfmt/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,28 @@ fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
}
let suffix = match iter.next_back() {
Some('K') => Some((RawSuffix::K, with_i)),
Some('k') => Some((RawSuffix::K, with_i)),
Some('M') => Some((RawSuffix::M, with_i)),
Some('G') => Some((RawSuffix::G, with_i)),
Some('T') => Some((RawSuffix::T, with_i)),
Some('P') => Some((RawSuffix::P, with_i)),
Some('E') => Some((RawSuffix::E, with_i)),
Some('Z') => Some((RawSuffix::Z, with_i)),
Some('Y') => Some((RawSuffix::Y, with_i)),
Some('R') => Some((RawSuffix::R, with_i)),
Some('Q') => Some((RawSuffix::Q, with_i)),
Some('0'..='9') if !with_i => None,
_ => {
// If with_i is true, the string ends with 'i' but there's no valid suffix letter
// This is always an invalid suffix (e.g., "1i", "2Ai")
if with_i {
return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote()));
}
// For other cases, check if the number part (without the last character) is valid
let number_part = &s[..s.len() - 1];
if number_part.is_empty() || number_part.parse::<f64>().is_err() {
return Err(translate!("numfmt-error-invalid-number", "input" => s.quote()));
}
return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote()));
}
};
Expand Down Expand Up @@ -123,6 +136,8 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
RawSuffix::E => Ok(i * 1e18),
RawSuffix::Z => Ok(i * 1e21),
RawSuffix::Y => Ok(i * 1e24),
RawSuffix::R => Ok(i * 1e27),
RawSuffix::Q => Ok(i * 1e30),
},
(Some((raw_suffix, false)), &Unit::Iec(false))
| (Some((raw_suffix, true)), &Unit::Auto | &Unit::Iec(true)) => match raw_suffix {
Expand All @@ -134,6 +149,8 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
RawSuffix::E => Ok(i * IEC_BASES[6]),
RawSuffix::Z => Ok(i * IEC_BASES[7]),
RawSuffix::Y => Ok(i * IEC_BASES[8]),
RawSuffix::R => Ok(i * IEC_BASES[9]),
RawSuffix::Q => Ok(i * IEC_BASES[10]),
},
(Some((raw_suffix, false)), &Unit::Iec(true)) => Err(
translate!("numfmt-error-missing-i-suffix", "number" => i, "suffix" => format!("{raw_suffix:?}")),
Expand Down Expand Up @@ -212,10 +229,10 @@ fn consider_suffix(
round_method: RoundMethod,
precision: usize,
) -> Result<(f64, Option<Suffix>)> {
use crate::units::RawSuffix::{E, G, K, M, P, T, Y, Z};
use crate::units::RawSuffix::{E, G, K, M, P, Q, R, T, Y, Z};

let abs_n = n.abs();
let suffixes = [K, M, G, T, P, E, Z, Y];
let suffixes = [K, M, G, T, P, E, Z, Y, R, Q];

let (bases, with_i) = match *u {
Unit::Si => (&SI_BASES, false),
Expand All @@ -234,6 +251,8 @@ fn consider_suffix(
_ if abs_n < bases[7] => 6,
_ if abs_n < bases[8] => 7,
_ if abs_n < bases[9] => 8,
_ if abs_n < bases[10] => 9,
_ if abs_n < bases[10] * 1000.0 => 10,
_ => return Err(translate!("numfmt-error-number-too-big")),
};

Expand Down Expand Up @@ -334,38 +353,41 @@ fn format_string(

fn format_and_print_delimited(s: &str, options: &NumfmtOptions) -> Result<()> {
let delimiter = options.delimiter.as_ref().unwrap();
let mut output = String::new();

for (n, field) in (1..).zip(s.split(delimiter)) {
let field_selected = uucore::ranges::contain(&options.fields, n);

// print delimiter before second and subsequent fields
// add delimiter before second and subsequent fields
if n > 1 {
print!("{delimiter}");
output.push_str(delimiter);
}

if field_selected {
print!("{}", format_string(field.trim_start(), options, None)?);
output.push_str(&format_string(field.trim_start(), options, None)?);
} else {
// print unselected field without conversion
print!("{field}");
// add unselected field without conversion
output.push_str(field);
}
}

println!();
println!("{output}");

Ok(())
}

fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> {
let mut output = String::new();

for (n, (prefix, field)) in (1..).zip(WhitespaceSplitter { s: Some(s) }) {
let field_selected = uucore::ranges::contain(&options.fields, n);

if field_selected {
let empty_prefix = prefix.is_empty();

// print delimiter before second and subsequent fields
// add delimiter before second and subsequent fields
let prefix = if n > 1 {
print!(" ");
output.push(' ');
&prefix[1..]
} else {
prefix
Expand All @@ -377,22 +399,24 @@ fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> {
None
};

print!("{}", format_string(field, options, implicit_padding)?);
output.push_str(&format_string(field, options, implicit_padding)?);
} else {
// the -z option converts an initial \n into a space
let prefix = if options.zero_terminated && prefix.starts_with('\n') {
print!(" ");
output.push(' ');
&prefix[1..]
} else {
prefix
};
// print unselected field without conversion
print!("{prefix}{field}");
// add unselected field without conversion
output.push_str(prefix);
output.push_str(field);
}
}

let eol = if options.zero_terminated { '\0' } else { '\n' };
print!("{eol}");
output.push(eol);
print!("{output}");

Ok(())
}
Expand Down Expand Up @@ -445,4 +469,123 @@ mod tests {
assert_eq!(2, parse_implicit_precision("1.23K"));
assert_eq!(3, parse_implicit_precision("1.234K"));
}

#[test]
fn test_parse_suffix_q_r_k() {
let result = parse_suffix("1Q");
assert!(result.is_ok());
let (number, suffix) = result.unwrap();
assert_eq!(number, 1.0);
assert!(suffix.is_some());
let (raw_suffix, with_i) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::Q as i32);
assert!(!with_i);

let result = parse_suffix("2R");
assert!(result.is_ok());
let (number, suffix) = result.unwrap();
assert_eq!(number, 2.0);
assert!(suffix.is_some());
let (raw_suffix, with_i) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::R as i32);
assert!(!with_i);

let result = parse_suffix("3k");
assert!(result.is_ok());
let (number, suffix) = result.unwrap();
assert_eq!(number, 3.0);
assert!(suffix.is_some());
let (raw_suffix, with_i) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::K as i32);
assert!(!with_i);

let result = parse_suffix("4Qi");
assert!(result.is_ok());
let (number, suffix) = result.unwrap();
assert_eq!(number, 4.0);
assert!(suffix.is_some());
let (raw_suffix, with_i) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::Q as i32);
assert!(with_i);

let result = parse_suffix("5Ri");
assert!(result.is_ok());
let (number, suffix) = result.unwrap();
assert_eq!(number, 5.0);
assert!(suffix.is_some());
let (raw_suffix, with_i) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::R as i32);
assert!(with_i);
}

#[test]
fn test_parse_suffix_error_messages() {
let result = parse_suffix("foo");
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.contains("numfmt-error-invalid-number") || error.contains("invalid number"));
assert!(!error.contains("invalid suffix"));

let result = parse_suffix("World");
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.contains("numfmt-error-invalid-number") || error.contains("invalid number"));
assert!(!error.contains("invalid suffix"));

let result = parse_suffix("123i");
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.contains("numfmt-error-invalid-suffix") || error.contains("invalid suffix"));
}

#[test]
fn test_remove_suffix_q_r() {
use crate::units::Unit;

let result = remove_suffix(1.0, Some((RawSuffix::Q, false)), &Unit::Si);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 1e30);

let result = remove_suffix(1.0, Some((RawSuffix::R, false)), &Unit::Si);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 1e27);

let result = remove_suffix(1.0, Some((RawSuffix::Q, true)), &Unit::Iec(true));
assert!(result.is_ok());
assert_eq!(result.unwrap(), IEC_BASES[10]);

let result = remove_suffix(1.0, Some((RawSuffix::R, true)), &Unit::Iec(true));
assert!(result.is_ok());
assert_eq!(result.unwrap(), IEC_BASES[9]);
}

#[test]
fn test_consider_suffix_q_r() {
use crate::options::RoundMethod;
use crate::units::Unit;

let result = consider_suffix(1e27, &Unit::Si, RoundMethod::FromZero, 0);
assert!(result.is_ok());
let (value, suffix) = result.unwrap();
assert!(suffix.is_some());
let (raw_suffix, _) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::R as i32);
assert_eq!(value, 1.0);

let result = consider_suffix(1e30, &Unit::Si, RoundMethod::FromZero, 0);
assert!(result.is_ok());
let (value, suffix) = result.unwrap();
assert!(suffix.is_some());
let (raw_suffix, _) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::Q as i32);
assert_eq!(value, 1.0);

let result = consider_suffix(5e30, &Unit::Si, RoundMethod::FromZero, 0);
assert!(result.is_ok());
let (value, suffix) = result.unwrap();
assert!(suffix.is_some());
let (raw_suffix, _) = suffix.unwrap();
assert_eq!(raw_suffix as i32, RawSuffix::Q as i32);
assert_eq!(value, 5.0);
}
}
4 changes: 2 additions & 2 deletions src/uu/numfmt/src/numfmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,9 +460,9 @@ mod tests {
let result_display = format!("{result}");
assert_eq!(
result_debug,
"FormattingError(\"numfmt-error-invalid-suffix\")"
"FormattingError(\"numfmt-error-invalid-number\")"
);
assert_eq!(result_display, "numfmt-error-invalid-suffix");
assert_eq!(result_display, "numfmt-error-invalid-number");
assert_eq!(result.code(), 2);
}

Expand Down
9 changes: 7 additions & 2 deletions src/uu/numfmt/src/units.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
// file that was distributed with this source code.
use std::fmt;

pub const SI_BASES: [f64; 10] = [1., 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24, 1e27];
pub const SI_BASES: [f64; 11] = [1., 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24, 1e27, 1e30];

pub const IEC_BASES: [f64; 10] = [
pub const IEC_BASES: [f64; 11] = [
1.,
1_024.,
1_048_576.,
Expand All @@ -17,6 +17,7 @@ pub const IEC_BASES: [f64; 10] = [
1_180_591_620_717_411_303_424.,
1_208_925_819_614_629_174_706_176.,
1_237_940_039_285_380_274_899_124_224.,
1_267_650_600_228_229_401_496_703_205_376.,
];

pub type WithI = bool;
Expand All @@ -41,6 +42,8 @@ pub enum RawSuffix {
E,
Z,
Y,
R,
Q,
}

pub type Suffix = (RawSuffix, WithI);
Expand All @@ -60,6 +63,8 @@ impl fmt::Display for DisplayableSuffix {
(RawSuffix::E, _) => write!(f, "E"),
(RawSuffix::Z, _) => write!(f, "Z"),
(RawSuffix::Y, _) => write!(f, "Y"),
(RawSuffix::R, _) => write!(f, "R"),
(RawSuffix::Q, _) => write!(f, "Q"),
}
.and_then(|()| match with_i {
true => write!(f, "i"),
Expand Down
Loading
Loading