1
Fork 0
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:
Nicolas Boichat 2025-03-25 11:18:28 +01:00
parent 3ab68bad10
commit 3f24796c8d
3 changed files with 73 additions and 32 deletions

View file

@ -169,7 +169,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
variant: FloatVariant::Decimal,
width: padding,
alignment: num_format::NumberAlignment::RightZero,
precision,
precision: Some(precision),
..Default::default()
},
// format without precision: hexadecimal floats

View file

@ -220,7 +220,10 @@ pub struct Float {
pub width: usize,
pub positive_sign: PositiveSign,
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 {
@ -232,7 +235,7 @@ impl Default for Float {
width: 0,
positive_sign: PositiveSign::None,
alignment: NumberAlignment::Left,
precision: 6,
precision: None,
}
}
}
@ -315,8 +318,8 @@ impl Formatter<&ExtendedBigDecimal> for Float {
};
let precision = match precision {
Some(CanAsterisk::Fixed(x)) => x,
None => 6, // Default float precision (C standard)
Some(CanAsterisk::Fixed(x)) => Some(x),
None => None,
Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType),
};
@ -360,8 +363,13 @@ fn format_float_non_finite(e: &ExtendedBigDecimal, case: Case) -> String {
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());
let precision = precision.unwrap_or(6); // Default %f precision (C standard)
if precision == 0 {
let (bi, scale) = bd.as_bigint_and_scale();
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(
bd: &BigDecimal,
precision: usize,
precision: Option<usize>,
case: Case,
force_decimal: ForceDecimal,
) -> String {
debug_assert!(!bd.is_negative());
let precision = precision.unwrap_or(6); // Default %e precision (C standard)
let exp_char = match case {
Case::Lowercase => 'e',
Case::Uppercase => 'E',
@ -421,11 +430,12 @@ fn format_float_scientific(
fn format_float_shortest(
bd: &BigDecimal,
precision: usize,
precision: Option<usize>,
case: Case,
force_decimal: ForceDecimal,
) -> String {
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,
// instead of how many digits in the fractional part.
@ -503,11 +513,23 @@ fn format_float_shortest(
fn format_float_hexadecimal(
bd: &BigDecimal,
precision: usize,
precision: Option<usize>,
case: Case,
force_decimal: ForceDecimal,
) -> String {
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 {
Case::Lowercase => ("0x", 'p'),
@ -706,7 +728,8 @@ mod test {
#[test]
fn decimal_float() {
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(1.0), "1.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_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.");
// 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("1234567890.12345678901234567890"),
@ -737,7 +772,11 @@ mod test {
// TODO: Enable after https://github.com/akubera/bigdecimal-rs/issues/144 is fixed,
// as our workaround is in .fmt.
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(), -10), "0.000000");
@ -750,7 +789,7 @@ mod test {
let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
6,
None,
Case::Lowercase,
ForceDecimal::No,
)
@ -766,7 +805,7 @@ mod test {
let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
6,
Some(6),
Case::Uppercase,
ForceDecimal::No,
)
@ -778,7 +817,7 @@ mod test {
let f = |digits, scale| {
format_float_scientific(
&BigDecimal::from_bigint(digits, scale),
6,
Some(6),
Case::Lowercase,
ForceDecimal::No,
)
@ -795,7 +834,7 @@ mod test {
let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
0,
Some(0),
Case::Lowercase,
ForceDecimal::No,
)
@ -811,7 +850,7 @@ mod test {
let f = |x| {
format_float_scientific(
&BigDecimal::from_f64(x).unwrap(),
0,
Some(0),
Case::Lowercase,
ForceDecimal::Yes,
)
@ -831,7 +870,7 @@ mod test {
let f = |x| {
format_float_shortest(
&BigDecimal::from_f64(x).unwrap(),
6,
None,
Case::Lowercase,
ForceDecimal::No,
)
@ -853,7 +892,7 @@ mod test {
let f = |x| {
format_float_shortest(
&BigDecimal::from_f64(x).unwrap(),
6,
None,
Case::Lowercase,
ForceDecimal::Yes,
)
@ -875,7 +914,7 @@ mod test {
let f = |x| {
format_float_shortest(
&BigDecimal::from_f64(x).unwrap(),
0,
Some(0),
Case::Lowercase,
ForceDecimal::No,
)
@ -894,7 +933,7 @@ mod test {
let f = |x| {
format_float_shortest(
&BigDecimal::from_f64(x).unwrap(),
0,
Some(0),
Case::Lowercase,
ForceDecimal::Yes,
)
@ -920,7 +959,7 @@ mod test {
let f = |x| {
format_float_hexadecimal(
&BigDecimal::from_str(x).unwrap(),
6,
Some(6),
Case::Lowercase,
ForceDecimal::No,
)
@ -935,7 +974,7 @@ mod test {
let f = |x| {
format_float_hexadecimal(
&BigDecimal::from_str(x).unwrap(),
0,
Some(0),
Case::Lowercase,
ForceDecimal::No,
)
@ -947,7 +986,7 @@ mod test {
let f = |x| {
format_float_hexadecimal(
&BigDecimal::from_str(x).unwrap(),
0,
Some(0),
Case::Lowercase,
ForceDecimal::Yes,
)
@ -959,7 +998,7 @@ mod test {
let f = |x| {
format_float_hexadecimal(
&BigDecimal::from_str(x).unwrap(),
6,
Some(6),
Case::Uppercase,
ForceDecimal::No,
)
@ -971,7 +1010,7 @@ mod test {
let f = |digits, scale| {
format_float_hexadecimal(
&BigDecimal::from_bigint(digits, scale),
6,
Some(6),
Case::Lowercase,
ForceDecimal::No,
)
@ -1001,7 +1040,7 @@ mod test {
let f = |x| {
format_float_shortest(
&BigDecimal::from_f64(x).unwrap(),
6,
None,
Case::Lowercase,
ForceDecimal::No,
)
@ -1019,7 +1058,7 @@ mod test {
let f = |x| {
format_float_shortest(
&BigDecimal::from_f64(x).unwrap(),
6,
None,
Case::Lowercase,
ForceDecimal::No,
)

View file

@ -432,11 +432,13 @@ impl Spec {
precision,
} => {
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();
if precision as u64 > i32::MAX as u64 {
return Err(FormatError::InvalidPrecision(precision.to_string()));
if precision.is_some_and(|p| p as u64 > i32::MAX as u64) {
return Err(FormatError::InvalidPrecision(
precision.unwrap().to_string(),
));
}
num_format::Float {