mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
printf: fix and test float formatting
This commit is contained in:
parent
4aafb3f88b
commit
955640aac8
2 changed files with 198 additions and 9 deletions
|
@ -329,8 +329,24 @@ fn format_float_scientific(
|
||||||
return format_float_nonfinite(f, case);
|
return format_float_nonfinite(f, case);
|
||||||
}
|
}
|
||||||
|
|
||||||
let exponent: i32 = f.log10().floor() as i32;
|
if f == 0.0 {
|
||||||
let normalized = f / 10.0_f64.powi(exponent);
|
return if force_decimal == ForceDecimal::Yes && precision == 0 {
|
||||||
|
"0.e+00".into()
|
||||||
|
} else {
|
||||||
|
format!("{:.*}e+00", precision, 0.0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mut exponent: i32 = f.log10().floor() as i32;
|
||||||
|
let mut normalized = f / 10.0_f64.powi(exponent);
|
||||||
|
|
||||||
|
// If the normalized value will be rounded to a value greater than 10
|
||||||
|
// we need to correct.
|
||||||
|
if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0 {
|
||||||
|
normalized /= 10.0;
|
||||||
|
exponent += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal {
|
let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal {
|
||||||
"."
|
"."
|
||||||
|
@ -349,20 +365,89 @@ fn format_float_scientific(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This could be optimized. It's not terribly important though.
|
|
||||||
fn format_float_shortest(
|
fn format_float_shortest(
|
||||||
f: f64,
|
f: f64,
|
||||||
precision: usize,
|
precision: usize,
|
||||||
case: Case,
|
case: Case,
|
||||||
force_decimal: ForceDecimal,
|
force_decimal: ForceDecimal,
|
||||||
) -> String {
|
) -> String {
|
||||||
let a = format_float_decimal(f, precision, case, force_decimal);
|
// If the float is NaN, -Nan, Inf or -Inf, format like any other float
|
||||||
let b = format_float_scientific(f, precision, case, force_decimal);
|
if !f.is_finite() {
|
||||||
|
return format_float_nonfinite(f, case);
|
||||||
|
}
|
||||||
|
|
||||||
if a.len() > b.len() {
|
// Precision here is about how many digits should be displayed
|
||||||
b
|
// instead of how many digits for the fractional part, this means that if
|
||||||
|
// we pass this to rust's format string, it's always gonna be one less.
|
||||||
|
let precision = precision.saturating_sub(1);
|
||||||
|
|
||||||
|
if f == 0.0 {
|
||||||
|
return match (force_decimal, precision) {
|
||||||
|
(ForceDecimal::Yes, 0) => "0.".into(),
|
||||||
|
(ForceDecimal::Yes, _) => {
|
||||||
|
format!("{:.*}", precision, 0.0)
|
||||||
|
}
|
||||||
|
(ForceDecimal::No, _) => "0".into(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut exponent = f.log10().floor() as i32;
|
||||||
|
if f != 0.0 && exponent <= -4 || exponent > precision as i32 {
|
||||||
|
// Scientific-ish notation (with a few differences)
|
||||||
|
let mut normalized = f / 10.0_f64.powi(exponent);
|
||||||
|
|
||||||
|
// If the normalized value will be rounded to a value greater than 10
|
||||||
|
// we need to correct.
|
||||||
|
if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0 {
|
||||||
|
normalized /= 10.0;
|
||||||
|
exponent += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal {
|
||||||
|
"."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut normalized = format!("{normalized:.*}", precision);
|
||||||
|
|
||||||
|
if force_decimal == ForceDecimal::No {
|
||||||
|
while normalized.ends_with('0') {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
if normalized.ends_with('.') {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let exp_char = match case {
|
||||||
|
Case::Lowercase => 'e',
|
||||||
|
Case::Uppercase => 'E',
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{normalized}{additional_dot}{exp_char}{exponent:+03}")
|
||||||
} else {
|
} else {
|
||||||
a
|
// Decimal-ish notation with a few differences:
|
||||||
|
// - The precision works differently and specifies the total number
|
||||||
|
// of digits instead of the digits in the fractional part.
|
||||||
|
// - If we don't force the decimal, '0' and `.` are trimmed.
|
||||||
|
let decimal_places = (precision as i32).saturating_sub(exponent) as usize;
|
||||||
|
let mut formatted = if decimal_places == 0 && force_decimal == ForceDecimal::Yes {
|
||||||
|
format!("{f:.0}.")
|
||||||
|
} else {
|
||||||
|
format!("{f:.*}", decimal_places)
|
||||||
|
};
|
||||||
|
|
||||||
|
if force_decimal == ForceDecimal::No {
|
||||||
|
while formatted.ends_with('0') {
|
||||||
|
formatted.pop();
|
||||||
|
}
|
||||||
|
if formatted.ends_with('.') {
|
||||||
|
formatted.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,3 +483,107 @@ fn format_float_hexadecimal(
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::format::num_format::{Case, ForceDecimal};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decimal_float() {
|
||||||
|
use super::format_float_decimal;
|
||||||
|
let f = |x| format_float_decimal(x, 6, Case::Lowercase, ForceDecimal::No);
|
||||||
|
assert_eq!(f(0.0), "0.000000");
|
||||||
|
assert_eq!(f(1.0), "1.000000");
|
||||||
|
assert_eq!(f(100.0), "100.000000");
|
||||||
|
assert_eq!(f(123456.789), "123456.789000");
|
||||||
|
assert_eq!(f(12.3456789), "12.345679");
|
||||||
|
assert_eq!(f(1000000.0), "1000000.000000");
|
||||||
|
assert_eq!(f(99999999.0), "99999999.000000");
|
||||||
|
assert_eq!(f(1.9999995), "1.999999");
|
||||||
|
assert_eq!(f(1.9999996), "2.000000");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scientific_float() {
|
||||||
|
use super::format_float_scientific;
|
||||||
|
let f = |x| format_float_scientific(x, 6, Case::Lowercase, ForceDecimal::No);
|
||||||
|
assert_eq!(f(0.0), "0.000000e+00");
|
||||||
|
assert_eq!(f(1.0), "1.000000e+00");
|
||||||
|
assert_eq!(f(100.0), "1.000000e+02");
|
||||||
|
assert_eq!(f(123456.789), "1.234568e+05");
|
||||||
|
assert_eq!(f(12.3456789), "1.234568e+01");
|
||||||
|
assert_eq!(f(1000000.0), "1.000000e+06");
|
||||||
|
assert_eq!(f(99999999.0), "1.000000e+08");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scientific_float_zero_precision() {
|
||||||
|
use super::format_float_scientific;
|
||||||
|
|
||||||
|
let f = |x| format_float_scientific(x, 0, Case::Lowercase, ForceDecimal::No);
|
||||||
|
assert_eq!(f(0.0), "0e+00");
|
||||||
|
assert_eq!(f(1.0), "1e+00");
|
||||||
|
assert_eq!(f(100.0), "1e+02");
|
||||||
|
assert_eq!(f(123456.789), "1e+05");
|
||||||
|
assert_eq!(f(12.3456789), "1e+01");
|
||||||
|
assert_eq!(f(1000000.0), "1e+06");
|
||||||
|
assert_eq!(f(99999999.0), "1e+08");
|
||||||
|
|
||||||
|
let f = |x| format_float_scientific(x, 0, Case::Lowercase, ForceDecimal::Yes);
|
||||||
|
assert_eq!(f(0.0), "0.e+00");
|
||||||
|
assert_eq!(f(1.0), "1.e+00");
|
||||||
|
assert_eq!(f(100.0), "1.e+02");
|
||||||
|
assert_eq!(f(123456.789), "1.e+05");
|
||||||
|
assert_eq!(f(12.3456789), "1.e+01");
|
||||||
|
assert_eq!(f(1000000.0), "1.e+06");
|
||||||
|
assert_eq!(f(99999999.0), "1.e+08");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shortest_float() {
|
||||||
|
use super::format_float_shortest;
|
||||||
|
let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::No);
|
||||||
|
assert_eq!(f(0.0), "0");
|
||||||
|
assert_eq!(f(1.0), "1");
|
||||||
|
assert_eq!(f(100.0), "100");
|
||||||
|
assert_eq!(f(123456.789), "123457");
|
||||||
|
assert_eq!(f(12.3456789), "12.3457");
|
||||||
|
assert_eq!(f(1000000.0), "1e+06");
|
||||||
|
assert_eq!(f(99999999.0), "1e+08");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shortest_float_force_decimal() {
|
||||||
|
use super::format_float_shortest;
|
||||||
|
let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::Yes);
|
||||||
|
assert_eq!(f(0.0), "0.00000");
|
||||||
|
assert_eq!(f(1.0), "1.00000");
|
||||||
|
assert_eq!(f(100.0), "100.000");
|
||||||
|
assert_eq!(f(123456.789), "123457.");
|
||||||
|
assert_eq!(f(12.3456789), "12.3457");
|
||||||
|
assert_eq!(f(1000000.0), "1.00000e+06");
|
||||||
|
assert_eq!(f(99999999.0), "1.00000e+08");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shortest_float_force_decimal_zero_precision() {
|
||||||
|
use super::format_float_shortest;
|
||||||
|
let f = |x| format_float_shortest(x, 0, Case::Lowercase, ForceDecimal::No);
|
||||||
|
assert_eq!(f(0.0), "0");
|
||||||
|
assert_eq!(f(1.0), "1");
|
||||||
|
assert_eq!(f(100.0), "1e+02");
|
||||||
|
assert_eq!(f(123456.789), "1e+05");
|
||||||
|
assert_eq!(f(12.3456789), "1e+01");
|
||||||
|
assert_eq!(f(1000000.0), "1e+06");
|
||||||
|
assert_eq!(f(99999999.0), "1e+08");
|
||||||
|
|
||||||
|
let f = |x| format_float_shortest(x, 0, Case::Lowercase, ForceDecimal::Yes);
|
||||||
|
assert_eq!(f(0.0), "0.");
|
||||||
|
assert_eq!(f(1.0), "1.");
|
||||||
|
assert_eq!(f(100.0), "1.e+02");
|
||||||
|
assert_eq!(f(123456.789), "1.e+05");
|
||||||
|
assert_eq!(f(12.3456789), "1.e+01");
|
||||||
|
assert_eq!(f(1000000.0), "1.e+06");
|
||||||
|
assert_eq!(f(99999999.0), "1.e+08");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -251,7 +251,7 @@ fn sub_num_float_e_no_round() {
|
||||||
#[test]
|
#[test]
|
||||||
fn sub_num_float_round() {
|
fn sub_num_float_round() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["two is %f", "1.9999995"])
|
.args(&["two is %f", "1.9999996"])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("two is 2.000000");
|
.stdout_only("two is 2.000000");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue