Skip to content

Commit 8731d4d

Browse files
committed
Automatic exponential formatting in Debug
* {:.PREC?} already had legitimately useful behavior (recursive formatting of structs using fixed precision for floats) and I suspect that changes to the output there would be unwelcome. (besides, precision introduces sinister edge cases where a number can be rounded up to one of the thresholds) Thus, the new behavior of Debug is, "dynamically switch to exponential, but only if there's no precision." * This could not be implemented in terms of float_to_decimal_common without repeating the branch on precision, so 'float_to_general_debug' is a new function. The name is '_debug' instead of '_common' because the considerations in the previous bullet make this logic pretty specific to Debug. * 'float_to_decimal_common' is now only used by Display, so I inlined the min_precision argument and renamed the function accordingly.
1 parent 312b894 commit 8731d4d

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
@@ -2,6 +2,26 @@ use crate::fmt::{Debug, Display, Formatter, LowerExp, Result, UpperExp};
22
use crate::mem::MaybeUninit;
33
use crate::num::flt2dec;
44

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

56-
// Common code of floating point Debug and Display.
57-
fn float_to_decimal_common<T>(fmt: &mut Formatter<'_>, num: &T, min_precision: usize) -> Result
76+
fn float_to_decimal_display<T>(fmt: &mut Formatter<'_>, num: &T) -> Result
5877
where
5978
T: flt2dec::DecodableFloat,
6079
{
@@ -67,6 +86,7 @@ where
6786
if let Some(precision) = fmt.precision {
6887
float_to_decimal_common_exact(fmt, num, sign, precision)
6988
} else {
89+
let min_precision = 0;
7090
float_to_decimal_common_shortest(fmt, num, sign, min_precision)
7191
}
7292
}
@@ -144,19 +164,44 @@ where
144164
}
145165
}
146166

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

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

library/core/src/num/f32.rs

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

library/core/src/num/f64.rs

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

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)