rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.88k stars 1.56k forks source link

extend fmt! to add %g analogous to printf '%g' #844

Open steveklabnik opened 9 years ago

steveklabnik commented 9 years ago

Issue by erickt Thursday Oct 18, 2012 at 23:02 GMT

For earlier discussion, see https://github.com/rust-lang/rust/issues/3810

This issue was labelled with: A-libs, I-enhancement in the Rust repository


It's sometimes nice not to include trailing zeroes when printing floats. It'd be nice if we copied printf's %g to handle this.

414owen commented 7 years ago

Any thoughts on possible syntaxes/implementations of this? I believe it used to be the default in rust with the format trait 'f', but that appears to have been removed, can someone explain why? I have a project that would benefit greatly from this.

rampion commented 7 years ago

From http://www.cplusplus.com/reference/cstdio/printf/

f   Decimal floating point, lowercase                   392.65
F   Decimal floating point, uppercase                   392.65
e   Scientific notation (mantissa/exponent), lowercase  3.9265e+2
E   Scientific notation (mantissa/exponent), uppercase  3.9265E+2
g   Use the shortest representation: %e or %f           392.65
G   Use the shortest representation: %E or %F           392.65
HadrienG2 commented 7 years ago

+1 to this. Actually, if it weren't for backwards compatibility, I'd even like this behaviour to become the default.

There's plenty of prior art of using this so-called "engineering notation" in scientific calculators, and here's the likely reason: very large and very small numbers, which are quite common in scientific computing, don't mix well with raw decimal formatting at all. And conversely, there is no need to get scientific notation involved for numbers close to 1, where decimal notation is more easily readable. In this sense, the engineering notation is the most sensible default for printing floats of unknown value, because it is the notation which is most likely to feel right for any number.

HadrienG2 commented 5 years ago

Here is a basic implementation based on the existing formatters, which I wrote for a project that needed it more than usual. It differs a bit from printf's %g in that it does not allow itself to add extra zeroes on the back of a large number (as that could give the illusion of more significant digits than were intended).

/// Write a floating-point number using "engineering" notation
///
/// Analogous to the %g format of the C printf function, this method switches
/// between naive and scientific notation for floating-point numbers when the
/// number being printed becomes so small that printing leading zeroes could end
/// up larger than the scientific notation, or so large that we would be forced
/// to print more significant digits than requested.
///
pub fn write_engineering(writer: &mut impl Write, x: Real, sig_digits: usize) {
    let mut precision = sig_digits - 1;
    let log_x = x.abs().log10();
    if (log_x >= -3. && log_x <= (sig_digits as Real)) || x == 0. {
        // Print using naive notation
        if x != 0. {
            // Since Rust's precision controls number of digits after the
            // decimal point, we must adjust it depending on magnitude in order
            // to operate at a constant number of significant digits.
            precision = (precision as isize - log_x.trunc() as isize) as usize;

            // Numbers smaller than 1 must get one extra digit since the leading
            // zero does not count as a significant digit.
            if log_x < 0. { precision += 1 }
        }
        write!(writer, "{:.1$}", x, precision);
    } else {
        // Print using scientific notation
        write!(writer, "{:.1$e}", x, precision);
    }
}

What upstream integration would bring with respect to this approximation is much greater ease of use (as there is no need to break out of the format string workflow) and access to the other format!() controls: padding, alignment...

HadrienG2 commented 5 years ago

If there is anything I can do to help you get rid of this little papercut, feel free to ask :wink:

droundy commented 5 years ago

I have implemented something a bit more subtle (and also less efficient) at:

https://github.com/droundy/sad-monte-carlo/blob/master/src/prettyfloat.rs

I format both ways, and then pick whichever is shorter, which I consider a very nice heuristic.

bestouff commented 3 years ago

I would need this right now, for a project that needs to mimic C's %g output byte-for-byte. I don't think I'm alone in this case.

bestouff commented 3 years ago

FWIW I have published https://crates.io/crates/gpoint which is a wrapper around f64 using libc's printf with the "%g" format. This enabled me to RIIR a project at work and have its output exactly match its C counterpart.