Open steveklabnik opened 9 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.
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
+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.
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...
If there is anything I can do to help you get rid of this little papercut, feel free to ask :wink:
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.
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.
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.
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.