Skip to content

Commit 3f24796

Browse files
committed
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).
1 parent 3ab68ba commit 3f24796

File tree

3 files changed

+73
-32
lines changed

3 files changed

+73
-32
lines changed

src/uu/seq/src/seq.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
169169
variant: FloatVariant::Decimal,
170170
width: padding,
171171
alignment: num_format::NumberAlignment::RightZero,
172-
precision,
172+
precision: Some(precision),
173173
..Default::default()
174174
},
175175
// format without precision: hexadecimal floats

src/uucore/src/lib/features/format/num_format.rs

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,10 @@ pub struct Float {
220220
pub width: usize,
221221
pub positive_sign: PositiveSign,
222222
pub alignment: NumberAlignment,
223-
pub precision: usize,
223+
// For float, the default precision depends on the format, usually 6,
224+
// but something architecture-specific for %a. Set this to None to
225+
// use the default.
226+
pub precision: Option<usize>,
224227
}
225228

226229
impl Default for Float {
@@ -232,7 +235,7 @@ impl Default for Float {
232235
width: 0,
233236
positive_sign: PositiveSign::None,
234237
alignment: NumberAlignment::Left,
235-
precision: 6,
238+
precision: None,
236239
}
237240
}
238241
}
@@ -315,8 +318,8 @@ impl Formatter<&ExtendedBigDecimal> for Float {
315318
};
316319

317320
let precision = match precision {
318-
Some(CanAsterisk::Fixed(x)) => x,
319-
None => 6, // Default float precision (C standard)
321+
Some(CanAsterisk::Fixed(x)) => Some(x),
322+
None => None,
320323
Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType),
321324
};
322325

@@ -360,8 +363,13 @@ fn format_float_non_finite(e: &ExtendedBigDecimal, case: Case) -> String {
360363
s
361364
}
362365

363-
fn format_float_decimal(bd: &BigDecimal, precision: usize, force_decimal: ForceDecimal) -> String {
366+
fn format_float_decimal(
367+
bd: &BigDecimal,
368+
precision: Option<usize>,
369+
force_decimal: ForceDecimal,
370+
) -> String {
364371
debug_assert!(!bd.is_negative());
372+
let precision = precision.unwrap_or(6); // Default %f precision (C standard)
365373
if precision == 0 {
366374
let (bi, scale) = bd.as_bigint_and_scale();
367375
if scale == 0 && force_decimal != ForceDecimal::Yes {
@@ -376,11 +384,12 @@ fn format_float_decimal(bd: &BigDecimal, precision: usize, force_decimal: ForceD
376384

377385
fn format_float_scientific(
378386
bd: &BigDecimal,
379-
precision: usize,
387+
precision: Option<usize>,
380388
case: Case,
381389
force_decimal: ForceDecimal,
382390
) -> String {
383391
debug_assert!(!bd.is_negative());
392+
let precision = precision.unwrap_or(6); // Default %e precision (C standard)
384393
let exp_char = match case {
385394
Case::Lowercase => 'e',
386395
Case::Uppercase => 'E',
@@ -421,11 +430,12 @@ fn format_float_scientific(
421430

422431
fn format_float_shortest(
423432
bd: &BigDecimal,
424-
precision: usize,
433+
precision: Option<usize>,
425434
case: Case,
426435
force_decimal: ForceDecimal,
427436
) -> String {
428437
debug_assert!(!bd.is_negative());
438+
let precision = precision.unwrap_or(6); // Default %g precision (C standard)
429439

430440
// Note: Precision here is how many digits should be displayed in total,
431441
// instead of how many digits in the fractional part.
@@ -503,11 +513,23 @@ fn format_float_shortest(
503513

504514
fn format_float_hexadecimal(
505515
bd: &BigDecimal,
506-
precision: usize,
516+
precision: Option<usize>,
507517
case: Case,
508518
force_decimal: ForceDecimal,
509519
) -> String {
510520
debug_assert!(!bd.is_negative());
521+
// Default precision for %a is supposed to be sufficient to represent the
522+
// exact value. This is platform specific, GNU coreutils uses a `long double`,
523+
// which can be equivalent to a f64, f128, or an x86(-64) specific "f80".
524+
// We have arbitrary precision in base 10, so we can't always represent
525+
// the value exactly (e.g. 0.1 is c.ccccc...).
526+
//
527+
// Emulate x86(-64) behavior, where 64 bits are printed in total, that's
528+
// 16 hex digits, including 1 before the decimal point (so 15 after).
529+
//
530+
// TODO: Make this configurable? e.g. arm64 value would be 28 (f128),
531+
// arm value 13 (f64).
532+
let precision = precision.unwrap_or(15);
511533

512534
let (prefix, exp_char) = match case {
513535
Case::Lowercase => ("0x", 'p'),
@@ -706,7 +728,8 @@ mod test {
706728
#[test]
707729
fn decimal_float() {
708730
use super::format_float_decimal;
709-
let f = |x| format_float_decimal(&BigDecimal::from_f64(x).unwrap(), 6, ForceDecimal::No);
731+
let f =
732+
|x| format_float_decimal(&BigDecimal::from_f64(x).unwrap(), Some(6), ForceDecimal::No);
710733
assert_eq!(f(0.0), "0.000000");
711734
assert_eq!(f(1.0), "1.000000");
712735
assert_eq!(f(100.0), "100.000000");
@@ -717,11 +740,23 @@ mod test {
717740
assert_eq!(f(1.999_999_5), "1.999999");
718741
assert_eq!(f(1.999_999_6), "2.000000");
719742

720-
let f = |x| format_float_decimal(&BigDecimal::from_f64(x).unwrap(), 0, ForceDecimal::Yes);
743+
let f = |x| {
744+
format_float_decimal(
745+
&BigDecimal::from_f64(x).unwrap(),
746+
Some(0),
747+
ForceDecimal::Yes,
748+
)
749+
};
721750
assert_eq!(f(100.0), "100.");
722751

723752
// Test arbitrary precision: long inputs that would not fit in a f64, print 24 digits after decimal point.
724-
let f = |x| format_float_decimal(&BigDecimal::from_str(x).unwrap(), 24, ForceDecimal::No);
753+
let f = |x| {
754+
format_float_decimal(
755+
&BigDecimal::from_str(x).unwrap(),
756+
Some(24),
757+
ForceDecimal::No,
758+
)
759+
};
725760
assert_eq!(f("0.12345678901234567890"), "0.123456789012345678900000");
726761
assert_eq!(
727762
f("1234567890.12345678901234567890"),
@@ -737,7 +772,11 @@ mod test {
737772
// TODO: Enable after https://github.com/akubera/bigdecimal-rs/issues/144 is fixed,
738773
// as our workaround is in .fmt.
739774
let f = |digits, scale| {
740-
format_float_decimal(&BigDecimal::from_bigint(digits, scale), 6, ForceDecimal::No)
775+
format_float_decimal(
776+
&BigDecimal::from_bigint(digits, scale),
777+
Some(6),
778+
ForceDecimal::No,
779+
)
741780
};
742781
assert_eq!(f(0.into(), 0), "0.000000");
743782
assert_eq!(f(0.into(), -10), "0.000000");
@@ -750,7 +789,7 @@ mod test {
750789
let f = |x| {
751790
format_float_scientific(
752791
&BigDecimal::from_f64(x).unwrap(),
753-
6,
792+
None,
754793
Case::Lowercase,
755794
ForceDecimal::No,
756795
)
@@ -766,7 +805,7 @@ mod test {
766805
let f = |x| {
767806
format_float_scientific(
768807
&BigDecimal::from_f64(x).unwrap(),
769-
6,
808+
Some(6),
770809
Case::Uppercase,
771810
ForceDecimal::No,
772811
)
@@ -778,7 +817,7 @@ mod test {
778817
let f = |digits, scale| {
779818
format_float_scientific(
780819
&BigDecimal::from_bigint(digits, scale),
781-
6,
820+
Some(6),
782821
Case::Lowercase,
783822
ForceDecimal::No,
784823
)
@@ -795,7 +834,7 @@ mod test {
795834
let f = |x| {
796835
format_float_scientific(
797836
&BigDecimal::from_f64(x).unwrap(),
798-
0,
837+
Some(0),
799838
Case::Lowercase,
800839
ForceDecimal::No,
801840
)
@@ -811,7 +850,7 @@ mod test {
811850
let f = |x| {
812851
format_float_scientific(
813852
&BigDecimal::from_f64(x).unwrap(),
814-
0,
853+
Some(0),
815854
Case::Lowercase,
816855
ForceDecimal::Yes,
817856
)
@@ -831,7 +870,7 @@ mod test {
831870
let f = |x| {
832871
format_float_shortest(
833872
&BigDecimal::from_f64(x).unwrap(),
834-
6,
873+
None,
835874
Case::Lowercase,
836875
ForceDecimal::No,
837876
)
@@ -853,7 +892,7 @@ mod test {
853892
let f = |x| {
854893
format_float_shortest(
855894
&BigDecimal::from_f64(x).unwrap(),
856-
6,
895+
None,
857896
Case::Lowercase,
858897
ForceDecimal::Yes,
859898
)
@@ -875,7 +914,7 @@ mod test {
875914
let f = |x| {
876915
format_float_shortest(
877916
&BigDecimal::from_f64(x).unwrap(),
878-
0,
917+
Some(0),
879918
Case::Lowercase,
880919
ForceDecimal::No,
881920
)
@@ -894,7 +933,7 @@ mod test {
894933
let f = |x| {
895934
format_float_shortest(
896935
&BigDecimal::from_f64(x).unwrap(),
897-
0,
936+
Some(0),
898937
Case::Lowercase,
899938
ForceDecimal::Yes,
900939
)
@@ -920,7 +959,7 @@ mod test {
920959
let f = |x| {
921960
format_float_hexadecimal(
922961
&BigDecimal::from_str(x).unwrap(),
923-
6,
962+
Some(6),
924963
Case::Lowercase,
925964
ForceDecimal::No,
926965
)
@@ -935,7 +974,7 @@ mod test {
935974
let f = |x| {
936975
format_float_hexadecimal(
937976
&BigDecimal::from_str(x).unwrap(),
938-
0,
977+
Some(0),
939978
Case::Lowercase,
940979
ForceDecimal::No,
941980
)
@@ -947,7 +986,7 @@ mod test {
947986
let f = |x| {
948987
format_float_hexadecimal(
949988
&BigDecimal::from_str(x).unwrap(),
950-
0,
989+
Some(0),
951990
Case::Lowercase,
952991
ForceDecimal::Yes,
953992
)
@@ -959,7 +998,7 @@ mod test {
959998
let f = |x| {
960999
format_float_hexadecimal(
9611000
&BigDecimal::from_str(x).unwrap(),
962-
6,
1001+
Some(6),
9631002
Case::Uppercase,
9641003
ForceDecimal::No,
9651004
)
@@ -971,7 +1010,7 @@ mod test {
9711010
let f = |digits, scale| {
9721011
format_float_hexadecimal(
9731012
&BigDecimal::from_bigint(digits, scale),
974-
6,
1013+
Some(6),
9751014
Case::Lowercase,
9761015
ForceDecimal::No,
9771016
)
@@ -1001,7 +1040,7 @@ mod test {
10011040
let f = |x| {
10021041
format_float_shortest(
10031042
&BigDecimal::from_f64(x).unwrap(),
1004-
6,
1043+
None,
10051044
Case::Lowercase,
10061045
ForceDecimal::No,
10071046
)
@@ -1019,7 +1058,7 @@ mod test {
10191058
let f = |x| {
10201059
format_float_shortest(
10211060
&BigDecimal::from_f64(x).unwrap(),
1022-
6,
1061+
None,
10231062
Case::Lowercase,
10241063
ForceDecimal::No,
10251064
)

src/uucore/src/lib/features/format/spec.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,13 @@ impl Spec {
432432
precision,
433433
} => {
434434
let width = resolve_asterisk(*width, &mut args).unwrap_or(0);
435-
let precision = resolve_asterisk(*precision, &mut args).unwrap_or(6);
435+
let precision = resolve_asterisk(*precision, &mut args);
436436
let f: ExtendedBigDecimal = args.get_extended_big_decimal();
437437

438-
if precision as u64 > i32::MAX as u64 {
439-
return Err(FormatError::InvalidPrecision(precision.to_string()));
438+
if precision.is_some_and(|p| p as u64 > i32::MAX as u64) {
439+
return Err(FormatError::InvalidPrecision(
440+
precision.unwrap().to_string(),
441+
));
440442
}
441443

442444
num_format::Float {

0 commit comments

Comments
 (0)