Nice floating point number formatting


#1

Python can do it.

>>> 10.0
10.0
>>> (10 ** -20)
1e-20
>>> (10 ** -20) * (1 + 2**-52)
1.0000000000000001e-20

Haskell can do it.

λ> 10 :: Double
10.0
λ> (10 ** (-20))
1.0e-20
λ> (10 ** (-20)) * (1 + (2 ** (-52)))
1.0000000000000001e-20

Why can’t rust do it?

(code)

   {} -   "10" -   "0.00000000000000000001" - "0.000000000000000000010000000000000001"
 {:?} -   "10" -   "0.00000000000000000001" - "0.000000000000000000010000000000000001"
 {:e} -  "1e1" -                    "1e-20" -                 "1.0000000000000001e-20"

I’m talking about automatic switching between exponential and non-exponential form based on magnitude; printing numbers fit for human consumption. The default format is so silly that you’re basically required to use exponential format in any sort of general context; but then I end up with those occassional late nights where I see 5e0 and it takes me a couple of seconds to remember whether this is larger than or less than 1.

That’s just silly!


…and yes, I know what you’re about to say, and I’ve already tried exactly that. A wrapper type doesn’t cut it. You can’t wrap every element of every Vec and MyOneOffStruct that you ever need to debug.

Nice should be the default.


How to print floating points with *limited* precision? ideally with %g semantics
#2

In C’s printf, this would be the %g format.

   g, G   The double argument is converted in style f or e (or F or E
          for G conversions).  The precision specifies the number of
          significant digits.  If the precision is missing, 6 digits are
          given; if the precision is zero, it is treated as 1.  Style e
          is used if the exponent from its conversion is less than -4 or
          greater than or equal to the precision.  Trailing zeros are
          removed from the fractional part of the result; a decimal
          point appears only if it is followed by at least one digit.

#3

It’s close (and my original title was “General floating number point formatting”, after what I assume “g” stands for), though my preference is to default to minimal round-trip precision like our existing representations do (as well as the defaults in Haskell and Python).


#4

I have never had a case where "{}" or "{:e}" for a float was the formatting I wanted. I have always had to work around it.

In serde_json I have been using dtoa to get reasonable printing of floats. The API is somewhat less convenient but it is faster for serde_json’s use case than std::fmt, and it seems to use the same formatting as Python in your examples. [playground]

extern crate dtoa;

use std::{f64, io};

fn print_f64(f: f64) {
    dtoa::write(io::stdout(), f).unwrap();
    println!();
}

fn main() {
    print_f64(10f64);
    print_f64(10f64.powi(-20));
    print_f64(10f64.powi(-20) * (1f64 + f64::EPSILON));
}
10.0
1e-20
1.0000000000000001e-20

#5

Lacking a %g equivalent is frustrating. There doesn’t even seem to be a good crate for this (@dtolnay’s does not appear to support displaying a particular number of significant digits). I pretty much only ever want to print a float with a specific number of significant digits, and rust does not seem to make this possible short of calling out to libc.


#6

I’ve hand-rolled something like this when I’ve needed to set a minimum number of significant digits before: https://play.rust-lang.org/?gist=5a253812c41dd51b3bc9c1f478e1ebde&version=stable


#7

The %g format specifier for printf is different from what current Python does. Python produces a short string which converts back to the original value. %g is lossy. But then, C does not require that string-to-floating-point is exact, either, so C isn’t really a good model here (Java is much better in this regard, as far as languages with a separate specification go).