rollup merge of #24379: rkruppe/fmt-negative-zero

Fixes #20596 by making `Debug` render negative zero with a `-` without affecting the behavior of `Display`.

While I was at it, I also removed some dead code from `float_to_str_bytes_common` (the one from `libcore/fmt/float.rs`, not the function of the same name in `libstd/num/strconv.rs`). It had support for different bases, and for negative numbers, but the function is internal to core and the couple places that call it (all in `libcore/fmt/mod.rs`) never use those features: They pass in `num.abs()` and base 10.
This commit is contained in:
Alex Crichton 2015-04-14 10:55:51 -07:00
commit 2795811f92
3 changed files with 59 additions and 115 deletions

View File

@ -1,4 +1,4 @@
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
@ -8,14 +8,10 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![allow(missing_docs)]
pub use self::ExponentFormat::*;
pub use self::SignificantDigits::*;
pub use self::SignFormat::*;
use char;
use char::CharExt;
use char::{self, CharExt};
use fmt;
use iter::Iterator;
use num::{cast, Float, ToPrimitive};
@ -46,50 +42,29 @@ pub enum SignificantDigits {
DigExact(usize)
}
/// How to emit the sign of a number.
pub enum SignFormat {
/// `-` will be printed for negative values, but no sign will be emitted
/// for positive numbers.
SignNeg
}
const DIGIT_E_RADIX: u32 = ('e' as u32) - ('a' as u32) + 11;
/// Converts a number to its string representation as a byte vector.
/// This is meant to be a common base implementation for all numeric string
/// conversion functions like `to_string()` or `to_str_radix()`.
/// Converts a float number to its string representation.
/// This is meant to be a common base implementation for various formatting styles.
/// The number is assumed to be non-negative, callers use `Formatter::pad_integral`
/// to add the right sign, if any.
///
/// # Arguments
///
/// - `num` - The number to convert. Accepts any number that
/// - `num` - The number to convert (non-negative). Accepts any number that
/// implements the numeric traits.
/// - `radix` - Base to use. Accepts only the values 2-36. If the exponential notation
/// is used, then this base is only used for the significand. The exponent
/// itself always printed using a base of 10.
/// - `negative_zero` - Whether to treat the special value `-0` as
/// `-0` or as `+0`.
/// - `sign` - How to emit the sign. See `SignFormat`.
/// - `digits` - The amount of digits to use for emitting the fractional
/// part, if any. See `SignificantDigits`.
/// - `exp_format` - Whether or not to use the exponential (scientific) notation.
/// See `ExponentFormat`.
/// - `exp_capital` - Whether or not to use a capital letter for the exponent sign, if
/// exponential notation is desired.
/// - `f` - A closure to invoke with the bytes representing the
/// - `f` - A closure to invoke with the string representing the
/// float.
///
/// # Panics
///
/// - Panics if `radix` < 2 or `radix` > 36.
/// - Panics if `radix` > 14 and `exp_format` is `ExpDec` due to conflict
/// between digit and exponent sign `'e'`.
/// - Panics if `radix` > 25 and `exp_format` is `ExpBin` due to conflict
/// between digit and exponent sign `'p'`.
/// - Panics if `num` is negative.
pub fn float_to_str_bytes_common<T: Float, U, F>(
num: T,
radix: u32,
negative_zero: bool,
sign: SignFormat,
digits: SignificantDigits,
exp_format: ExponentFormat,
exp_upper: bool,
@ -97,16 +72,12 @@ pub fn float_to_str_bytes_common<T: Float, U, F>(
) -> U where
F: FnOnce(&str) -> U,
{
assert!(2 <= radix && radix <= 36);
match exp_format {
ExpDec if radix >= DIGIT_E_RADIX // decimal exponent 'e'
=> panic!("float_to_str_bytes_common: radix {} incompatible with \
use of 'e' as decimal exponent", radix),
_ => ()
}
let _0: T = Float::zero();
let _1: T = Float::one();
let radix: u32 = 10;
let radix_f: T = cast(radix).unwrap();
assert!(num.is_nan() || num >= _0, "float_to_str_bytes_common: number is negative");
match num.classify() {
Fp::Nan => return f("NaN"),
@ -119,41 +90,28 @@ pub fn float_to_str_bytes_common<T: Float, U, F>(
_ => {}
}
let neg = num < _0 || (negative_zero && _1 / num == Float::neg_infinity());
// For an f64 the exponent is in the range of [-1022, 1023] for base 2, so
// we may have up to that many digits. Give ourselves some extra wiggle room
// otherwise as well.
let mut buf = [0; 1536];
// For an f64 the (decimal) exponent is roughly in the range of [-307, 308], so
// we may have up to that many digits. We err on the side of caution and
// add 50% extra wiggle room.
let mut buf = [0; 462];
let mut end = 0;
let radix_gen: T = cast(radix as isize).unwrap();
let (num, exp) = match exp_format {
ExpNone => (num, 0),
ExpDec if num == _0 => (num, 0),
ExpDec => {
let (exp, exp_base) = match exp_format {
ExpDec => (num.abs().log10().floor(), cast::<f64, T>(10.0f64).unwrap()),
ExpNone => panic!("unreachable"),
};
(num / exp_base.powf(exp), cast::<T, i32>(exp).unwrap())
ExpDec if num != _0 => {
let exp = num.log10().floor();
(num / radix_f.powf(exp), cast::<T, i32>(exp).unwrap())
}
_ => (num, 0)
};
// First emit the non-fractional part, looping at least once to make
// sure at least a `0` gets emitted.
let mut deccum = num.trunc();
loop {
// Calculate the absolute value of each digit instead of only
// doing it once for the whole number because a
// representable negative number doesn't necessary have an
// representable additive inverse of the same type
// (See twos complement). But we assume that for the
// numbers [-35 .. 0] we always have [0 .. 35].
let current_digit = (deccum % radix_gen).abs();
let current_digit = deccum % radix_f;
// Decrease the deccumulator one digit at a time
deccum = deccum / radix_gen;
deccum = deccum / radix_f;
deccum = deccum.trunc();
let c = char::from_digit(current_digit.to_isize().unwrap() as u32, radix);
@ -170,15 +128,6 @@ pub fn float_to_str_bytes_common<T: Float, U, F>(
DigExact(count) => (true, count + 1, true)
};
// Decide what sign to put in front
match sign {
SignNeg if neg => {
buf[end] = b'-';
end += 1;
}
_ => ()
}
buf[..end].reverse();
// Remember start of the fractional digits.
@ -205,14 +154,11 @@ pub fn float_to_str_bytes_common<T: Float, U, F>(
)
) {
// Shift first fractional digit into the integer part
deccum = deccum * radix_gen;
deccum = deccum * radix_f;
// Calculate the absolute value of each digit.
// See note in first loop.
let current_digit = deccum.trunc().abs();
let current_digit = deccum.trunc();
let c = char::from_digit(current_digit.to_isize().unwrap() as u32,
radix);
let c = char::from_digit(current_digit.to_isize().unwrap() as u32, radix);
buf[end] = c.unwrap() as u8;
end += 1;
@ -301,12 +247,8 @@ pub fn float_to_str_bytes_common<T: Float, U, F>(
match exp_format {
ExpNone => {},
_ => {
buf[end] = match exp_format {
ExpDec if exp_upper => 'E',
ExpDec if !exp_upper => 'e',
_ => panic!("unreachable"),
} as u8;
ExpDec => {
buf[end] = if exp_upper { b'E' } else { b'e' };
end += 1;
struct Filler<'a> {
@ -324,11 +266,7 @@ pub fn float_to_str_bytes_common<T: Float, U, F>(
}
let mut filler = Filler { buf: &mut buf, end: &mut end };
match sign {
SignNeg => {
let _ = fmt::write(&mut filler, format_args!("{:-}", exp));
}
}
let _ = fmt::write(&mut filler, format_args!("{:-}", exp));
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
@ -18,6 +18,7 @@ use clone::Clone;
use iter::Iterator;
use marker::{Copy, PhantomData, Sized};
use mem;
use num::Float;
use option::Option;
use option::Option::{Some, None};
use result::Result::Ok;
@ -910,33 +911,38 @@ impl<'a, T> Pointer for &'a mut T {
}
}
// Common code of floating point Debug and Display.
fn float_to_str_common<T: Float, F>(num: &T, precision: Option<usize>, post: F) -> Result
where F : FnOnce(&str) -> Result {
let digits = match precision {
Some(i) => float::DigExact(i),
None => float::DigMax(6),
};
float::float_to_str_bytes_common(num.abs(),
digits,
float::ExpNone,
false,
post)
}
macro_rules! floating { ($ty:ident) => {
#[stable(feature = "rust1", since = "1.0.0")]
impl Debug for $ty {
fn fmt(&self, fmt: &mut Formatter) -> Result {
Display::fmt(self, fmt)
float_to_str_common(self, fmt.precision, |absolute| {
// is_positive() counts -0.0 as negative
fmt.pad_integral(self.is_nan() || self.is_positive(), "", absolute)
})
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl Display for $ty {
fn fmt(&self, fmt: &mut Formatter) -> Result {
use num::Float;
let digits = match fmt.precision {
Some(i) => float::DigExact(i),
None => float::DigMax(6),
};
float::float_to_str_bytes_common(self.abs(),
10,
true,
float::SignNeg,
digits,
float::ExpNone,
false,
|bytes| {
fmt.pad_integral(self.is_nan() || *self >= 0.0, "", bytes)
float_to_str_common(self, fmt.precision, |absolute| {
// simple comparison counts -0.0 as positive
fmt.pad_integral(self.is_nan() || *self >= 0.0, "", absolute)
})
}
}
@ -951,9 +957,6 @@ macro_rules! floating { ($ty:ident) => {
None => float::DigMax(6),
};
float::float_to_str_bytes_common(self.abs(),
10,
true,
float::SignNeg,
digits,
float::ExpDec,
false,
@ -973,9 +976,6 @@ macro_rules! floating { ($ty:ident) => {
None => float::DigMax(6),
};
float::float_to_str_bytes_common(self.abs(),
10,
true,
float::SignNeg,
digits,
float::ExpDec,
true,

View File

@ -1,4 +1,4 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
@ -144,6 +144,12 @@ pub fn main() {
t!(format!("{:+10.3e}", 1.2345e6f64), " +1.234e6");
t!(format!("{:+10.3e}", -1.2345e6f64), " -1.234e6");
// Float edge cases
t!(format!("{}", -0.0), "0");
t!(format!("{:?}", -0.0), "-0");
t!(format!("{:?}", 0.0), "0");
// Test that pointers don't get truncated.
{
let val = usize::MAX;