Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
steveklabnik opened this issue Feb 14, 2015 · 8 comments
Open

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

steveklabnik opened this issue Feb 14, 2015 · 8 comments
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.

Comments

@steveklabnik
Copy link
Member

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

For earlier discussion, see rust-lang/rust#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
Copy link

414owen commented Sep 23, 2016

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
Copy link

rampion commented Feb 21, 2017

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
Copy link

HadrienG2 commented May 30, 2017

+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.

@petrochenkov petrochenkov added T-libs-api Relevant to the library API team, which will review and decide on the RFC. and removed A-libs labels Jan 29, 2018
@HadrienG2
Copy link

HadrienG2 commented Oct 3, 2018

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
Copy link

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

@droundy
Copy link

droundy commented Oct 3, 2018

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
Copy link
Contributor

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
Copy link
Contributor

bestouff commented Jul 9, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

7 participants