ca6798ab07
Automatic exponential formatting in Debug
Context: See [this comment from the libs team](https://github.com/rust-lang/rfcs/pull/2729#issuecomment-853454204)
---
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](https://github.com/rust-lang/rfcs/pull/2729#issuecomment-864482954).
**This will require a crater run.**
---
As mentioned in the commit message of 8731d4dfb4
, 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)
227 lines
6.7 KiB
Rust
227 lines
6.7 KiB
Rust
use crate::fmt::{Debug, Display, Formatter, LowerExp, Result, UpperExp};
|
|
use crate::mem::MaybeUninit;
|
|
use crate::num::flt2dec;
|
|
use crate::num::fmt as numfmt;
|
|
|
|
#[doc(hidden)]
|
|
trait GeneralFormat: PartialOrd {
|
|
/// Determines if a value should use exponential based on its magnitude, given the precondition
|
|
/// that it will not be rounded any further before it is displayed.
|
|
fn already_rounded_value_should_use_exponential(&self) -> bool;
|
|
}
|
|
|
|
macro_rules! impl_general_format {
|
|
($($t:ident)*) => {
|
|
$(impl GeneralFormat for $t {
|
|
fn already_rounded_value_should_use_exponential(&self) -> bool {
|
|
let abs = $t::abs_private(*self);
|
|
(abs != 0.0 && abs < 1e-4) || abs >= 1e+16
|
|
}
|
|
})*
|
|
}
|
|
}
|
|
|
|
impl_general_format! { f32 f64 }
|
|
|
|
// Don't inline this so callers don't use the stack space this function
|
|
// requires unless they have to.
|
|
#[inline(never)]
|
|
fn float_to_decimal_common_exact<T>(
|
|
fmt: &mut Formatter<'_>,
|
|
num: &T,
|
|
sign: flt2dec::Sign,
|
|
precision: usize,
|
|
) -> Result
|
|
where
|
|
T: flt2dec::DecodableFloat,
|
|
{
|
|
let mut buf: [MaybeUninit<u8>; 1024] = MaybeUninit::uninit_array(); // enough for f32 and f64
|
|
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 4] = MaybeUninit::uninit_array();
|
|
let formatted = flt2dec::to_exact_fixed_str(
|
|
flt2dec::strategy::grisu::format_exact,
|
|
*num,
|
|
sign,
|
|
precision,
|
|
&mut buf,
|
|
&mut parts,
|
|
);
|
|
fmt.pad_formatted_parts(&formatted)
|
|
}
|
|
|
|
// Don't inline this so callers that call both this and the above won't wind
|
|
// up using the combined stack space of both functions in some cases.
|
|
#[inline(never)]
|
|
fn float_to_decimal_common_shortest<T>(
|
|
fmt: &mut Formatter<'_>,
|
|
num: &T,
|
|
sign: flt2dec::Sign,
|
|
precision: usize,
|
|
) -> Result
|
|
where
|
|
T: flt2dec::DecodableFloat,
|
|
{
|
|
// enough for f32 and f64
|
|
let mut buf: [MaybeUninit<u8>; flt2dec::MAX_SIG_DIGITS] = MaybeUninit::uninit_array();
|
|
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 4] = MaybeUninit::uninit_array();
|
|
let formatted = flt2dec::to_shortest_str(
|
|
flt2dec::strategy::grisu::format_shortest,
|
|
*num,
|
|
sign,
|
|
precision,
|
|
&mut buf,
|
|
&mut parts,
|
|
);
|
|
fmt.pad_formatted_parts(&formatted)
|
|
}
|
|
|
|
fn float_to_decimal_display<T>(fmt: &mut Formatter<'_>, num: &T) -> Result
|
|
where
|
|
T: flt2dec::DecodableFloat,
|
|
{
|
|
let force_sign = fmt.sign_plus();
|
|
let sign = match force_sign {
|
|
false => flt2dec::Sign::Minus,
|
|
true => flt2dec::Sign::MinusPlus,
|
|
};
|
|
|
|
if let Some(precision) = fmt.precision {
|
|
float_to_decimal_common_exact(fmt, num, sign, precision)
|
|
} else {
|
|
let min_precision = 0;
|
|
float_to_decimal_common_shortest(fmt, num, sign, min_precision)
|
|
}
|
|
}
|
|
|
|
// Don't inline this so callers don't use the stack space this function
|
|
// requires unless they have to.
|
|
#[inline(never)]
|
|
fn float_to_exponential_common_exact<T>(
|
|
fmt: &mut Formatter<'_>,
|
|
num: &T,
|
|
sign: flt2dec::Sign,
|
|
precision: usize,
|
|
upper: bool,
|
|
) -> Result
|
|
where
|
|
T: flt2dec::DecodableFloat,
|
|
{
|
|
let mut buf: [MaybeUninit<u8>; 1024] = MaybeUninit::uninit_array(); // enough for f32 and f64
|
|
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 6] = MaybeUninit::uninit_array();
|
|
let formatted = flt2dec::to_exact_exp_str(
|
|
flt2dec::strategy::grisu::format_exact,
|
|
*num,
|
|
sign,
|
|
precision,
|
|
upper,
|
|
&mut buf,
|
|
&mut parts,
|
|
);
|
|
fmt.pad_formatted_parts(&formatted)
|
|
}
|
|
|
|
// Don't inline this so callers that call both this and the above won't wind
|
|
// up using the combined stack space of both functions in some cases.
|
|
#[inline(never)]
|
|
fn float_to_exponential_common_shortest<T>(
|
|
fmt: &mut Formatter<'_>,
|
|
num: &T,
|
|
sign: flt2dec::Sign,
|
|
upper: bool,
|
|
) -> Result
|
|
where
|
|
T: flt2dec::DecodableFloat,
|
|
{
|
|
// enough for f32 and f64
|
|
let mut buf: [MaybeUninit<u8>; flt2dec::MAX_SIG_DIGITS] = MaybeUninit::uninit_array();
|
|
let mut parts: [MaybeUninit<numfmt::Part<'_>>; 6] = MaybeUninit::uninit_array();
|
|
let formatted = flt2dec::to_shortest_exp_str(
|
|
flt2dec::strategy::grisu::format_shortest,
|
|
*num,
|
|
sign,
|
|
(0, 0),
|
|
upper,
|
|
&mut buf,
|
|
&mut parts,
|
|
);
|
|
fmt.pad_formatted_parts(&formatted)
|
|
}
|
|
|
|
// Common code of floating point LowerExp and UpperExp.
|
|
fn float_to_exponential_common<T>(fmt: &mut Formatter<'_>, num: &T, upper: bool) -> Result
|
|
where
|
|
T: flt2dec::DecodableFloat,
|
|
{
|
|
let force_sign = fmt.sign_plus();
|
|
let sign = match force_sign {
|
|
false => flt2dec::Sign::Minus,
|
|
true => flt2dec::Sign::MinusPlus,
|
|
};
|
|
|
|
if let Some(precision) = fmt.precision {
|
|
// 1 integral digit + `precision` fractional digits = `precision + 1` total digits
|
|
float_to_exponential_common_exact(fmt, num, sign, precision + 1, upper)
|
|
} else {
|
|
float_to_exponential_common_shortest(fmt, num, sign, upper)
|
|
}
|
|
}
|
|
|
|
fn float_to_general_debug<T>(fmt: &mut Formatter<'_>, num: &T) -> Result
|
|
where
|
|
T: flt2dec::DecodableFloat + GeneralFormat,
|
|
{
|
|
let force_sign = fmt.sign_plus();
|
|
let sign = match force_sign {
|
|
false => flt2dec::Sign::Minus,
|
|
true => flt2dec::Sign::MinusPlus,
|
|
};
|
|
|
|
if let Some(precision) = fmt.precision {
|
|
// this behavior of {:.PREC?} predates exponential formatting for {:?}
|
|
float_to_decimal_common_exact(fmt, num, sign, precision)
|
|
} else {
|
|
// since there is no precision, there will be no rounding
|
|
if num.already_rounded_value_should_use_exponential() {
|
|
let upper = false;
|
|
float_to_exponential_common_shortest(fmt, num, sign, upper)
|
|
} else {
|
|
let min_precision = 1;
|
|
float_to_decimal_common_shortest(fmt, num, sign, min_precision)
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! floating {
|
|
($ty:ident) => {
|
|
#[stable(feature = "rust1", since = "1.0.0")]
|
|
impl Debug for $ty {
|
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
|
|
float_to_general_debug(fmt, self)
|
|
}
|
|
}
|
|
|
|
#[stable(feature = "rust1", since = "1.0.0")]
|
|
impl Display for $ty {
|
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
|
|
float_to_decimal_display(fmt, self)
|
|
}
|
|
}
|
|
|
|
#[stable(feature = "rust1", since = "1.0.0")]
|
|
impl LowerExp for $ty {
|
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
|
|
float_to_exponential_common(fmt, self, false)
|
|
}
|
|
}
|
|
|
|
#[stable(feature = "rust1", since = "1.0.0")]
|
|
impl UpperExp for $ty {
|
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
|
|
float_to_exponential_common(fmt, self, true)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
floating! { f32 }
|
|
floating! { f64 }
|