Skip to content

Commit ca6798a

Browse files
authored
Rollup merge of #86479 - exphp-forks:float-debug-exponential, r=yaahc
Automatic exponential formatting in Debug Context: See [this comment from the libs team](rust-lang/rfcs#2729 (comment)) --- Makes `"{:?}"` switch to exponential for floats based on magnitude. The libs team suggested exploring this idea in the discussion thread for RFC rust-lang/rfcs#2729. (**note:** this is **not** an implementation of the RFC; it is an implementation of one of the alternatives) Thresholds chosen were 1e-4 and 1e16. Justification described [here](rust-lang/rfcs#2729 (comment)). **This will require a crater run.** --- As mentioned in the commit message of 8731d4d, this behavior will not apply when a precision is supplied, because I wanted to preserve the following existing and useful behavior of `{:.PREC?}` (which recursively applies `{:.PREC}` to floats in a struct): ```rust assert_eq!( format!("{:.2?}", [100.0, 0.000004]), "[100.00, 0.00]", ) ``` I looked around and am not sure where there are any tests that actually use this in the test suite, though? All things considered, I'm surprised that this change did not seem to break even a single existing test in `x.py test --stage 2`. (even when I tried a smaller threshold of 1e6)
2 parents 1af55d1 + 8731d4d commit ca6798a

File tree

4 files changed

+75
-6
lines changed

4 files changed

+75
-6
lines changed

library/core/src/fmt/float.rs

+49-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@ use crate::mem::MaybeUninit;
33
use crate::num::flt2dec;
44
use crate::num::fmt as numfmt;
55

6+
#[doc(hidden)]
7+
trait GeneralFormat: PartialOrd {
8+
/// Determines if a value should use exponential based on its magnitude, given the precondition
9+
/// that it will not be rounded any further before it is displayed.
10+
fn already_rounded_value_should_use_exponential(&self) -> bool;
11+
}
12+
13+
macro_rules! impl_general_format {
14+
($($t:ident)*) => {
15+
$(impl GeneralFormat for $t {
16+
fn already_rounded_value_should_use_exponential(&self) -> bool {
17+
let abs = $t::abs_private(*self);
18+
(abs != 0.0 && abs < 1e-4) || abs >= 1e+16
19+
}
20+
})*
21+
}
22+
}
23+
24+
impl_general_format! { f32 f64 }
25+
626
// Don't inline this so callers don't use the stack space this function
727
// requires unless they have to.
828
#[inline(never)]
@@ -54,8 +74,7 @@ where
5474
fmt.pad_formatted_parts(&formatted)
5575
}
5676

57-
// Common code of floating point Debug and Display.
58-
fn float_to_decimal_common<T>(fmt: &mut Formatter<'_>, num: &T, min_precision: usize) -> Result
77+
fn float_to_decimal_display<T>(fmt: &mut Formatter<'_>, num: &T) -> Result
5978
where
6079
T: flt2dec::DecodableFloat,
6180
{
@@ -68,6 +87,7 @@ where
6887
if let Some(precision) = fmt.precision {
6988
float_to_decimal_common_exact(fmt, num, sign, precision)
7089
} else {
90+
let min_precision = 0;
7191
float_to_decimal_common_shortest(fmt, num, sign, min_precision)
7292
}
7393
}
@@ -145,19 +165,44 @@ where
145165
}
146166
}
147167

168+
fn float_to_general_debug<T>(fmt: &mut Formatter<'_>, num: &T) -> Result
169+
where
170+
T: flt2dec::DecodableFloat + GeneralFormat,
171+
{
172+
let force_sign = fmt.sign_plus();
173+
let sign = match force_sign {
174+
false => flt2dec::Sign::Minus,
175+
true => flt2dec::Sign::MinusPlus,
176+
};
177+
178+
if let Some(precision) = fmt.precision {
179+
// this behavior of {:.PREC?} predates exponential formatting for {:?}
180+
float_to_decimal_common_exact(fmt, num, sign, precision)
181+
} else {
182+
// since there is no precision, there will be no rounding
183+
if num.already_rounded_value_should_use_exponential() {
184+
let upper = false;
185+
float_to_exponential_common_shortest(fmt, num, sign, upper)
186+
} else {
187+
let min_precision = 1;
188+
float_to_decimal_common_shortest(fmt, num, sign, min_precision)
189+
}
190+
}
191+
}
192+
148193
macro_rules! floating {
149194
($ty:ident) => {
150195
#[stable(feature = "rust1", since = "1.0.0")]
151196
impl Debug for $ty {
152197
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
153-
float_to_decimal_common(fmt, self, 1)
198+
float_to_general_debug(fmt, self)
154199
}
155200
}
156201

157202
#[stable(feature = "rust1", since = "1.0.0")]
158203
impl Display for $ty {
159204
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
160-
float_to_decimal_common(fmt, self, 0)
205+
float_to_decimal_display(fmt, self)
161206
}
162207
}
163208

library/core/src/num/f32.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ impl f32 {
449449
// private use internally.
450450
#[inline]
451451
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
452-
const fn abs_private(self) -> f32 {
452+
pub(crate) const fn abs_private(self) -> f32 {
453453
f32::from_bits(self.to_bits() & 0x7fff_ffff)
454454
}
455455

library/core/src/num/f64.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ impl f64 {
448448
// private use internally.
449449
#[inline]
450450
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
451-
const fn abs_private(self) -> f64 {
451+
pub(crate) const fn abs_private(self) -> f64 {
452452
f64::from_bits(self.to_bits() & 0x7fff_ffff_ffff_ffff)
453453
}
454454

library/core/tests/fmt/float.rs

+24
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ fn test_format_f64() {
1212
assert_eq!("1.23456789E3", format!("{:E}", 1234.56789f64));
1313
assert_eq!("0.0", format!("{:?}", 0.0f64));
1414
assert_eq!("1.01", format!("{:?}", 1.01f64));
15+
16+
let high_cutoff = 1e16_f64;
17+
assert_eq!("1e16", format!("{:?}", high_cutoff));
18+
assert_eq!("-1e16", format!("{:?}", -high_cutoff));
19+
assert!(!is_exponential(&format!("{:?}", high_cutoff * (1.0 - 2.0 * f64::EPSILON))));
20+
assert_eq!("-3.0", format!("{:?}", -3f64));
21+
assert_eq!("0.0001", format!("{:?}", 0.0001f64));
22+
assert_eq!("9e-5", format!("{:?}", 0.00009f64));
23+
assert_eq!("1234567.9", format!("{:.1?}", 1234567.89f64));
24+
assert_eq!("1234.6", format!("{:.1?}", 1234.56789f64));
1525
}
1626

1727
#[test]
@@ -28,4 +38,18 @@ fn test_format_f32() {
2838
assert_eq!("1.2345679E3", format!("{:E}", 1234.56789f32));
2939
assert_eq!("0.0", format!("{:?}", 0.0f32));
3040
assert_eq!("1.01", format!("{:?}", 1.01f32));
41+
42+
let high_cutoff = 1e16_f32;
43+
assert_eq!("1e16", format!("{:?}", high_cutoff));
44+
assert_eq!("-1e16", format!("{:?}", -high_cutoff));
45+
assert!(!is_exponential(&format!("{:?}", high_cutoff * (1.0 - 2.0 * f32::EPSILON))));
46+
assert_eq!("-3.0", format!("{:?}", -3f32));
47+
assert_eq!("0.0001", format!("{:?}", 0.0001f32));
48+
assert_eq!("9e-5", format!("{:?}", 0.00009f32));
49+
assert_eq!("1234567.9", format!("{:.1?}", 1234567.89f32));
50+
assert_eq!("1234.6", format!("{:.1?}", 1234.56789f32));
51+
}
52+
53+
fn is_exponential(s: &str) -> bool {
54+
s.contains("e") || s.contains("E")
3155
}

0 commit comments

Comments
 (0)