mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
uucore: format: Use Option for Float precision
The default precision for float actually depends on the format. It's _usually_ 6, but it's architecture-specific for hexadecimal floats. Set the precision as an Option, so that: - We don't need to sprinkle `6` in callers - We can actually handle unspecified precision correctly in float printing (next change).
This commit is contained in:
parent
3ab68bad10
commit
3f24796c8d
3 changed files with 73 additions and 32 deletions
|
@ -169,7 +169,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
variant: FloatVariant::Decimal,
|
variant: FloatVariant::Decimal,
|
||||||
width: padding,
|
width: padding,
|
||||||
alignment: num_format::NumberAlignment::RightZero,
|
alignment: num_format::NumberAlignment::RightZero,
|
||||||
precision,
|
precision: Some(precision),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// format without precision: hexadecimal floats
|
// format without precision: hexadecimal floats
|
||||||
|
|
|
@ -220,7 +220,10 @@ pub struct Float {
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub positive_sign: PositiveSign,
|
pub positive_sign: PositiveSign,
|
||||||
pub alignment: NumberAlignment,
|
pub alignment: NumberAlignment,
|
||||||
pub precision: usize,
|
// For float, the default precision depends on the format, usually 6,
|
||||||
|
// but something architecture-specific for %a. Set this to None to
|
||||||
|
// use the default.
|
||||||
|
pub precision: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Float {
|
impl Default for Float {
|
||||||
|
@ -232,7 +235,7 @@ impl Default for Float {
|
||||||
width: 0,
|
width: 0,
|
||||||
positive_sign: PositiveSign::None,
|
positive_sign: PositiveSign::None,
|
||||||
alignment: NumberAlignment::Left,
|
alignment: NumberAlignment::Left,
|
||||||
precision: 6,
|
precision: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,8 +318,8 @@ impl Formatter<&ExtendedBigDecimal> for Float {
|
||||||
};
|
};
|
||||||
|
|
||||||
let precision = match precision {
|
let precision = match precision {
|
||||||
Some(CanAsterisk::Fixed(x)) => x,
|
Some(CanAsterisk::Fixed(x)) => Some(x),
|
||||||
None => 6, // Default float precision (C standard)
|
None => None,
|
||||||
Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType),
|
Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -360,8 +363,13 @@ fn format_float_non_finite(e: &ExtendedBigDecimal, case: Case) -> String {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_float_decimal(bd: &BigDecimal, precision: usize, force_decimal: ForceDecimal) -> String {
|
fn format_float_decimal(
|
||||||
|
bd: &BigDecimal,
|
||||||
|
precision: Option<usize>,
|
||||||
|
force_decimal: ForceDecimal,
|
||||||
|
) -> String {
|
||||||
debug_assert!(!bd.is_negative());
|
debug_assert!(!bd.is_negative());
|
||||||
|
let precision = precision.unwrap_or(6); // Default %f precision (C standard)
|
||||||
if precision == 0 {
|
if precision == 0 {
|
||||||
let (bi, scale) = bd.as_bigint_and_scale();
|
let (bi, scale) = bd.as_bigint_and_scale();
|
||||||
if scale == 0 && force_decimal != ForceDecimal::Yes {
|
if scale == 0 && force_decimal != ForceDecimal::Yes {
|
||||||
|
@ -376,11 +384,12 @@ fn format_float_decimal(bd: &BigDecimal, precision: usize, force_decimal: ForceD
|
||||||
|
|
||||||
fn format_float_scientific(
|
fn format_float_scientific(
|
||||||
bd: &BigDecimal,
|
bd: &BigDecimal,
|
||||||
precision: usize,
|
precision: Option<usize>,
|
||||||
case: Case,
|
case: Case,
|
||||||
force_decimal: ForceDecimal,
|
force_decimal: ForceDecimal,
|
||||||
) -> String {
|
) -> String {
|
||||||
debug_assert!(!bd.is_negative());
|
debug_assert!(!bd.is_negative());
|
||||||
|
let precision = precision.unwrap_or(6); // Default %e precision (C standard)
|
||||||
let exp_char = match case {
|
let exp_char = match case {
|
||||||
Case::Lowercase => 'e',
|
Case::Lowercase => 'e',
|
||||||
Case::Uppercase => 'E',
|
Case::Uppercase => 'E',
|
||||||
|
@ -421,11 +430,12 @@ fn format_float_scientific(
|
||||||
|
|
||||||
fn format_float_shortest(
|
fn format_float_shortest(
|
||||||
bd: &BigDecimal,
|
bd: &BigDecimal,
|
||||||
precision: usize,
|
precision: Option<usize>,
|
||||||
case: Case,
|
case: Case,
|
||||||
force_decimal: ForceDecimal,
|
force_decimal: ForceDecimal,
|
||||||
) -> String {
|
) -> String {
|
||||||
debug_assert!(!bd.is_negative());
|
debug_assert!(!bd.is_negative());
|
||||||
|
let precision = precision.unwrap_or(6); // Default %g precision (C standard)
|
||||||
|
|
||||||
// Note: Precision here is how many digits should be displayed in total,
|
// Note: Precision here is how many digits should be displayed in total,
|
||||||
// instead of how many digits in the fractional part.
|
// instead of how many digits in the fractional part.
|
||||||
|
@ -503,11 +513,23 @@ fn format_float_shortest(
|
||||||
|
|
||||||
fn format_float_hexadecimal(
|
fn format_float_hexadecimal(
|
||||||
bd: &BigDecimal,
|
bd: &BigDecimal,
|
||||||
precision: usize,
|
precision: Option<usize>,
|
||||||
case: Case,
|
case: Case,
|
||||||
force_decimal: ForceDecimal,
|
force_decimal: ForceDecimal,
|
||||||
) -> String {
|
) -> String {
|
||||||
debug_assert!(!bd.is_negative());
|
debug_assert!(!bd.is_negative());
|
||||||
|
// Default precision for %a is supposed to be sufficient to represent the
|
||||||
|
// exact value. This is platform specific, GNU coreutils uses a `long double`,
|
||||||
|
// which can be equivalent to a f64, f128, or an x86(-64) specific "f80".
|
||||||
|
// We have arbitrary precision in base 10, so we can't always represent
|
||||||
|
// the value exactly (e.g. 0.1 is c.ccccc...).
|
||||||
|
//
|
||||||
|
// Emulate x86(-64) behavior, where 64 bits are printed in total, that's
|
||||||
|
// 16 hex digits, including 1 before the decimal point (so 15 after).
|
||||||
|
//
|
||||||
|
// TODO: Make this configurable? e.g. arm64 value would be 28 (f128),
|
||||||
|
// arm value 13 (f64).
|
||||||
|
let precision = precision.unwrap_or(15);
|
||||||
|
|
||||||
let (prefix, exp_char) = match case {
|
let (prefix, exp_char) = match case {
|
||||||
Case::Lowercase => ("0x", 'p'),
|
Case::Lowercase => ("0x", 'p'),
|
||||||
|
@ -706,7 +728,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn decimal_float() {
|
fn decimal_float() {
|
||||||
use super::format_float_decimal;
|
use super::format_float_decimal;
|
||||||
let f = |x| format_float_decimal(&BigDecimal::from_f64(x).unwrap(), 6, ForceDecimal::No);
|
let f =
|
||||||
|
|x| format_float_decimal(&BigDecimal::from_f64(x).unwrap(), Some(6), ForceDecimal::No);
|
||||||
assert_eq!(f(0.0), "0.000000");
|
assert_eq!(f(0.0), "0.000000");
|
||||||
assert_eq!(f(1.0), "1.000000");
|
assert_eq!(f(1.0), "1.000000");
|
||||||
assert_eq!(f(100.0), "100.000000");
|
assert_eq!(f(100.0), "100.000000");
|
||||||
|
@ -717,11 +740,23 @@ mod test {
|
||||||
assert_eq!(f(1.999_999_5), "1.999999");
|
assert_eq!(f(1.999_999_5), "1.999999");
|
||||||
assert_eq!(f(1.999_999_6), "2.000000");
|
assert_eq!(f(1.999_999_6), "2.000000");
|
||||||
|
|
||||||
let f = |x| format_float_decimal(&BigDecimal::from_f64(x).unwrap(), 0, ForceDecimal::Yes);
|
let f = |x| {
|
||||||
|
format_float_decimal(
|
||||||
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
|
Some(0),
|
||||||
|
ForceDecimal::Yes,
|
||||||
|
)
|
||||||
|
};
|
||||||
assert_eq!(f(100.0), "100.");
|
assert_eq!(f(100.0), "100.");
|
||||||
|
|
||||||
// Test arbitrary precision: long inputs that would not fit in a f64, print 24 digits after decimal point.
|
// Test arbitrary precision: long inputs that would not fit in a f64, print 24 digits after decimal point.
|
||||||
let f = |x| format_float_decimal(&BigDecimal::from_str(x).unwrap(), 24, ForceDecimal::No);
|
let f = |x| {
|
||||||
|
format_float_decimal(
|
||||||
|
&BigDecimal::from_str(x).unwrap(),
|
||||||
|
Some(24),
|
||||||
|
ForceDecimal::No,
|
||||||
|
)
|
||||||
|
};
|
||||||
assert_eq!(f("0.12345678901234567890"), "0.123456789012345678900000");
|
assert_eq!(f("0.12345678901234567890"), "0.123456789012345678900000");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
f("1234567890.12345678901234567890"),
|
f("1234567890.12345678901234567890"),
|
||||||
|
@ -737,7 +772,11 @@ mod test {
|
||||||
// TODO: Enable after https://github.com/akubera/bigdecimal-rs/issues/144 is fixed,
|
// TODO: Enable after https://github.com/akubera/bigdecimal-rs/issues/144 is fixed,
|
||||||
// as our workaround is in .fmt.
|
// as our workaround is in .fmt.
|
||||||
let f = |digits, scale| {
|
let f = |digits, scale| {
|
||||||
format_float_decimal(&BigDecimal::from_bigint(digits, scale), 6, ForceDecimal::No)
|
format_float_decimal(
|
||||||
|
&BigDecimal::from_bigint(digits, scale),
|
||||||
|
Some(6),
|
||||||
|
ForceDecimal::No,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
assert_eq!(f(0.into(), 0), "0.000000");
|
assert_eq!(f(0.into(), 0), "0.000000");
|
||||||
assert_eq!(f(0.into(), -10), "0.000000");
|
assert_eq!(f(0.into(), -10), "0.000000");
|
||||||
|
@ -750,7 +789,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_scientific(
|
format_float_scientific(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
6,
|
None,
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -766,7 +805,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_scientific(
|
format_float_scientific(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
6,
|
Some(6),
|
||||||
Case::Uppercase,
|
Case::Uppercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -778,7 +817,7 @@ mod test {
|
||||||
let f = |digits, scale| {
|
let f = |digits, scale| {
|
||||||
format_float_scientific(
|
format_float_scientific(
|
||||||
&BigDecimal::from_bigint(digits, scale),
|
&BigDecimal::from_bigint(digits, scale),
|
||||||
6,
|
Some(6),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -795,7 +834,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_scientific(
|
format_float_scientific(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
0,
|
Some(0),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -811,7 +850,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_scientific(
|
format_float_scientific(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
0,
|
Some(0),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::Yes,
|
ForceDecimal::Yes,
|
||||||
)
|
)
|
||||||
|
@ -831,7 +870,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_shortest(
|
format_float_shortest(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
6,
|
None,
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -853,7 +892,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_shortest(
|
format_float_shortest(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
6,
|
None,
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::Yes,
|
ForceDecimal::Yes,
|
||||||
)
|
)
|
||||||
|
@ -875,7 +914,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_shortest(
|
format_float_shortest(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
0,
|
Some(0),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -894,7 +933,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_shortest(
|
format_float_shortest(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
0,
|
Some(0),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::Yes,
|
ForceDecimal::Yes,
|
||||||
)
|
)
|
||||||
|
@ -920,7 +959,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_hexadecimal(
|
format_float_hexadecimal(
|
||||||
&BigDecimal::from_str(x).unwrap(),
|
&BigDecimal::from_str(x).unwrap(),
|
||||||
6,
|
Some(6),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -935,7 +974,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_hexadecimal(
|
format_float_hexadecimal(
|
||||||
&BigDecimal::from_str(x).unwrap(),
|
&BigDecimal::from_str(x).unwrap(),
|
||||||
0,
|
Some(0),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -947,7 +986,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_hexadecimal(
|
format_float_hexadecimal(
|
||||||
&BigDecimal::from_str(x).unwrap(),
|
&BigDecimal::from_str(x).unwrap(),
|
||||||
0,
|
Some(0),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::Yes,
|
ForceDecimal::Yes,
|
||||||
)
|
)
|
||||||
|
@ -959,7 +998,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_hexadecimal(
|
format_float_hexadecimal(
|
||||||
&BigDecimal::from_str(x).unwrap(),
|
&BigDecimal::from_str(x).unwrap(),
|
||||||
6,
|
Some(6),
|
||||||
Case::Uppercase,
|
Case::Uppercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -971,7 +1010,7 @@ mod test {
|
||||||
let f = |digits, scale| {
|
let f = |digits, scale| {
|
||||||
format_float_hexadecimal(
|
format_float_hexadecimal(
|
||||||
&BigDecimal::from_bigint(digits, scale),
|
&BigDecimal::from_bigint(digits, scale),
|
||||||
6,
|
Some(6),
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -1001,7 +1040,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_shortest(
|
format_float_shortest(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
6,
|
None,
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
@ -1019,7 +1058,7 @@ mod test {
|
||||||
let f = |x| {
|
let f = |x| {
|
||||||
format_float_shortest(
|
format_float_shortest(
|
||||||
&BigDecimal::from_f64(x).unwrap(),
|
&BigDecimal::from_f64(x).unwrap(),
|
||||||
6,
|
None,
|
||||||
Case::Lowercase,
|
Case::Lowercase,
|
||||||
ForceDecimal::No,
|
ForceDecimal::No,
|
||||||
)
|
)
|
||||||
|
|
|
@ -432,11 +432,13 @@ impl Spec {
|
||||||
precision,
|
precision,
|
||||||
} => {
|
} => {
|
||||||
let width = resolve_asterisk(*width, &mut args).unwrap_or(0);
|
let width = resolve_asterisk(*width, &mut args).unwrap_or(0);
|
||||||
let precision = resolve_asterisk(*precision, &mut args).unwrap_or(6);
|
let precision = resolve_asterisk(*precision, &mut args);
|
||||||
let f: ExtendedBigDecimal = args.get_extended_big_decimal();
|
let f: ExtendedBigDecimal = args.get_extended_big_decimal();
|
||||||
|
|
||||||
if precision as u64 > i32::MAX as u64 {
|
if precision.is_some_and(|p| p as u64 > i32::MAX as u64) {
|
||||||
return Err(FormatError::InvalidPrecision(precision.to_string()));
|
return Err(FormatError::InvalidPrecision(
|
||||||
|
precision.unwrap().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
num_format::Float {
|
num_format::Float {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue