mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27: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,
|
||||
width: padding,
|
||||
alignment: num_format::NumberAlignment::RightZero,
|
||||
precision,
|
||||
precision: Some(precision),
|
||||
..Default::default()
|
||||
},
|
||||
// format without precision: hexadecimal floats
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue