]> git.lizzy.rs Git - rust.git/commitdiff
Automatic exponential formatting in Debug
authorMichael Lamparski <diagonaldevice@gmail.com>
Sat, 19 Jun 2021 19:50:29 +0000 (15:50 -0400)
committerMichael Lamparski <diagonaldevice@gmail.com>
Sun, 20 Jun 2021 00:53:26 +0000 (20:53 -0400)
* {:.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.

library/core/src/fmt/float.rs
library/core/src/num/f32.rs
library/core/src/num/f64.rs
library/core/tests/fmt/float.rs

index ece3cde00158074feaaa79348a0e40a4cf51e3dd..9ddd6c96b9642cb4a587d0941069f0424731958b 100644 (file)
@@ -2,6 +2,26 @@
 use crate::mem::MaybeUninit;
 use crate::num::flt2dec;
 
+#[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)]
@@ -53,8 +73,7 @@ fn float_to_decimal_common_shortest<T>(
     fmt.pad_formatted_parts(&formatted)
 }
 
-// Common code of floating point Debug and Display.
-fn float_to_decimal_common<T>(fmt: &mut Formatter<'_>, num: &T, min_precision: usize) -> Result
+fn float_to_decimal_display<T>(fmt: &mut Formatter<'_>, num: &T) -> Result
 where
     T: flt2dec::DecodableFloat,
 {
@@ -67,6 +86,7 @@ fn float_to_decimal_common<T>(fmt: &mut Formatter<'_>, num: &T, min_precision: u
     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)
     }
 }
@@ -144,19 +164,44 @@ fn float_to_exponential_common<T>(fmt: &mut Formatter<'_>, num: &T, upper: bool)
     }
 }
 
+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_decimal_common(fmt, self, 1)
+                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_common(fmt, self, 0)
+                float_to_decimal_display(fmt, self)
             }
         }
 
index c47a2e8b05c4bed0b75c2ffca61ba342d8073feb..4104d48b4a2db721620217f587db8e2901c4de0d 100644 (file)
@@ -448,7 +448,7 @@ pub const fn is_nan(self) -> bool {
     // private use internally.
     #[inline]
     #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
-    const fn abs_private(self) -> f32 {
+    pub(crate) const fn abs_private(self) -> f32 {
         f32::from_bits(self.to_bits() & 0x7fff_ffff)
     }
 
index cfcc08b9addeb9b26fa07152161ec8549b744b11..8a8fbae1941c3212634fee9eb629722537c403e8 100644 (file)
@@ -447,7 +447,7 @@ pub const fn is_nan(self) -> bool {
     // private use internally.
     #[inline]
     #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
-    const fn abs_private(self) -> f64 {
+    pub(crate) const fn abs_private(self) -> f64 {
         f64::from_bits(self.to_bits() & 0x7fff_ffff_ffff_ffff)
     }
 
index bd0daf7a8eb8487b1ce795cc4ae6f7fde055719d..47a7400f76ef95ecfb73146e2f295aa649ac3907 100644 (file)
@@ -12,6 +12,16 @@ fn test_format_f64() {
     assert_eq!("1.23456789E3", format!("{:E}", 1234.56789f64));
     assert_eq!("0.0", format!("{:?}", 0.0f64));
     assert_eq!("1.01", format!("{:?}", 1.01f64));
+
+    let high_cutoff = 1e16_f64;
+    assert_eq!("1e16", format!("{:?}", high_cutoff));
+    assert_eq!("-1e16", format!("{:?}", -high_cutoff));
+    assert!(!is_exponential(&format!("{:?}", high_cutoff * (1.0 - 2.0 * f64::EPSILON))));
+    assert_eq!("-3.0", format!("{:?}", -3f64));
+    assert_eq!("0.0001", format!("{:?}", 0.0001f64));
+    assert_eq!("9e-5", format!("{:?}", 0.00009f64));
+    assert_eq!("1234567.9", format!("{:.1?}", 1234567.89f64));
+    assert_eq!("1234.6", format!("{:.1?}", 1234.56789f64));
 }
 
 #[test]
@@ -28,4 +38,18 @@ fn test_format_f32() {
     assert_eq!("1.2345679E3", format!("{:E}", 1234.56789f32));
     assert_eq!("0.0", format!("{:?}", 0.0f32));
     assert_eq!("1.01", format!("{:?}", 1.01f32));
+
+    let high_cutoff = 1e16_f32;
+    assert_eq!("1e16", format!("{:?}", high_cutoff));
+    assert_eq!("-1e16", format!("{:?}", -high_cutoff));
+    assert!(!is_exponential(&format!("{:?}", high_cutoff * (1.0 - 2.0 * f32::EPSILON))));
+    assert_eq!("-3.0", format!("{:?}", -3f32));
+    assert_eq!("0.0001", format!("{:?}", 0.0001f32));
+    assert_eq!("9e-5", format!("{:?}", 0.00009f32));
+    assert_eq!("1234567.9", format!("{:.1?}", 1234567.89f32));
+    assert_eq!("1234.6", format!("{:.1?}", 1234.56789f32));
+}
+
+fn is_exponential(s: &str) -> bool {
+    s.contains("e") || s.contains("E")
 }