From 20c5b9878ce52359b2149107403f796458f2784c Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Fri, 15 May 2015 12:27:44 -0700 Subject: [PATCH] Rewrite ::fmt() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement new formatting behavior that is smarter about the unit it uses and handles precision. format!("{}", Duration::new(0, 1_001)) // 1.001µs format!("{}", Duration::new(0, 1_001_001)) // 1.001ms format!("{:.6}", Duration::new(0, 1_001_001)) // 1.001001ms // etc The default is variable precision up to 3 decimals. No unit higher than seconds are used. See rust-lang/rfcs#1121 for more information. --- src/libstd/time/duration.rs | 164 ++++++++++++++++++++++++++++++++---- 1 file changed, 149 insertions(+), 15 deletions(-) diff --git a/src/libstd/time/duration.rs b/src/libstd/time/duration.rs index 8001df29d1fc7..23bce8eae25d0 100644 --- a/src/libstd/time/duration.rs +++ b/src/libstd/time/duration.rs @@ -14,13 +14,18 @@ use prelude::v1::*; +use cmp; use fmt; +use io::{self, Cursor, SeekFrom}; +use io::prelude::*; use ops::{Add, Sub, Mul, Div}; +use str; use sys::time::SteadyTime; const NANOS_PER_SEC: u32 = 1_000_000_000; const NANOS_PER_MILLI: u32 = 1_000_000; const MILLIS_PER_SEC: u64 = 1_000; +const NANOS_PER_MICRO: u32 = 1_000; /// A duration type to represent a span of time, typically used for system /// timeouts. @@ -168,15 +173,114 @@ impl Div for Duration { impl fmt::Display for Duration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match (self.secs, self.nanos) { - (s, 0) => write!(f, "{}s", s), - (0, n) if n % NANOS_PER_MILLI == 0 => write!(f, "{}ms", - n / NANOS_PER_MILLI), - (0, n) if n % 1_000 == 0 => write!(f, "{}µs", n / 1_000), - (0, n) => write!(f, "{}ns", n), - (s, n) => write!(f, "{}.{}s", s, - format!("{:09}", n).trim_right_matches('0')) + // FIXME: fmt::Formatter::with_padding() isn't public, and the pad() function applies + // precision in a way we don't want. At such time as it becomes reasonable to support + // padding without reimplementing fmt::Formatter::with_padding() here, this should be + // updated to support padding. In preparation for that, we're already writing to a + // stack buffer. + + // µ (U+00B5) is 0xC2B5 in UTF-8. + // The following is >= the longest possible string we can format. + let mut buf = *b"18446744073709551615.999999999\xC2\xB5s"; + + fn write_to_buf<'a>(dur: &Duration, f: &fmt::Formatter, mut buf: &'a mut [u8]) + -> io::Result<&'a str> { + fn backup_over_zeros<'a>(cursor: &mut Cursor<&'a mut [u8]>) { + let mut pos = cursor.position() as usize; + { + let buf = cursor.get_ref(); + loop { + match buf[pos-1] { + b'0' => { pos -= 1; } + b'.' => { pos -= 1; break; } + _ => { break; } + } + } + } + cursor.set_position(pos as u64); + } + + let mut cursor = Cursor::new(&mut buf[..]); + let precision = f.precision(); + match (dur.secs, dur.nanos) { + (0, n) if n < NANOS_PER_MICRO => try!(write!(cursor, "{}ns", n)), + (s, 0) if precision.unwrap_or(0) == 0 => try!(write!(cursor, "{}s", s)), + (0, n) => { + let (unit, suffix, max_prec) = if n >= NANOS_PER_MILLI { + (NANOS_PER_MILLI, "ms", 6) + } else { + (NANOS_PER_MICRO, "µs", 3) + }; + // Leverage our existing floating-point formatting implementation + let n = n as f64 / unit as f64; + if let Some(precision) = precision { + try!(write!(cursor, "{:.*}{}", cmp::min(precision, max_prec), n, suffix)); + } else { + // variable precision up to 3 decimals + // just print all 3 decimals and then back up over zeroes. + try!(write!(cursor, "{:.3}", n)); + backup_over_zeros(&mut cursor); + try!(write!(cursor, "{}", suffix)); + } + } + (s, n) => { + try!(write!(cursor, "{}", s)); + // we're going to cheat a little here and re-use the same float trick above. + // but because 0.1234 has a leading 0, we're going to back up a byte, save it, + // overwrite it, and then restore it. + let n = n as f64 / NANOS_PER_SEC as f64; + try!(cursor.seek(SeekFrom::Current(-1))); + let saved_pos = cursor.position() as usize; + let saved_digit = cursor.get_ref()[saved_pos]; + if let Some(precision) = precision { + try!(write!(cursor, "{:.*}s", cmp::min(precision, 9), n)); + } else { + // variable precision up to 3 decimals + try!(write!(cursor, "{:.3}", n)); + backup_over_zeros(&mut cursor); + try!(write!(cursor, "s")); + } + // make sure we didn't round up to 1.0 when printing the float + if cursor.get_ref()[saved_pos] == b'1' { + // we did. back up and rewrite the seconds + if s == u64::max_value() { + // we can't represent a larger value. Luckily, we know that + // u64::max_value() ends with '5', so we can just replace it with '6'. + cursor.get_mut()[saved_pos] = b'6'; + } else { + try!(cursor.seek(SeekFrom::Start(0))); + try!(write!(cursor, "{}", s+1)); + match precision { + None | Some(0) => {}, + Some(precision) => { + // we need to write out the trailing zeroes + try!(write!(cursor, ".")); + for _ in 0..cmp::min(precision, 9) { + try!(write!(cursor, "0")); + } + } + } + try!(write!(cursor, "s")); + } + } else { + // restore the digit we overwrite earlier + cursor.get_mut()[saved_pos] = saved_digit; + } + } + } + // buf now contains our fully-formatted value + let pos = cursor.position() as usize; + let buf = &cursor.into_inner()[..pos]; + // formatting always writes strings + Ok(unsafe { str::from_utf8_unchecked(buf) }) } + + let result = write_to_buf(self, f, &mut buf); + // writing to the stack buffer should never fail + debug_assert!(result.is_ok(), "Error in ::fmt: {:?}", result); + let buf_str = try!(result.or(Err(fmt::Error))); + // If fmt::Formatter::with_padding() was public, this is where we'd use it. + write!(f, "{}", buf_str) } } @@ -260,15 +364,45 @@ mod tests { #[test] fn display() { + assert_eq!(Duration::new(0, 1).to_string(), "1ns"); + assert_eq!(Duration::new(0, 1_000).to_string(), "1µs"); + assert_eq!(Duration::new(0, 1_000_000).to_string(), "1ms"); + assert_eq!(Duration::new(1, 0).to_string(), "1s"); + assert_eq!(Duration::new(0, 999).to_string(), "999ns"); + assert_eq!(Duration::new(0, 1_001).to_string(), "1.001µs"); + assert_eq!(Duration::new(0, 1_100).to_string(), "1.1µs"); + assert_eq!(Duration::new(0, 1_234_567).to_string(), "1.235ms"); + assert_eq!(Duration::new(1, 234_567_890).to_string(), "1.235s"); + assert_eq!(Duration::new(1, 999_999).to_string(), "1.001s"); + assert_eq!(Duration::new(0, 2).to_string(), "2ns"); + assert_eq!(Duration::new(0, 2_000).to_string(), "2µs"); assert_eq!(Duration::new(0, 2_000_000).to_string(), "2ms"); assert_eq!(Duration::new(2, 0).to_string(), "2s"); - assert_eq!(Duration::new(2, 2).to_string(), "2.000000002s"); - assert_eq!(Duration::new(2, 2_000_000).to_string(), - "2.002s"); - assert_eq!(Duration::new(0, 2_000_002).to_string(), - "2000002ns"); - assert_eq!(Duration::new(2, 2_000_002).to_string(), - "2.002000002s"); + assert_eq!(Duration::new(2, 2).to_string(), "2s"); + assert_eq!(Duration::new(2, 2_000_000).to_string(), "2.002s"); + assert_eq!(Duration::new(0, 2_000_002).to_string(), "2ms"); + assert_eq!(Duration::new(2, 2_000_002).to_string(), "2.002s"); + assert_eq!(Duration::new(0, 0).to_string(), "0ns"); + + assert_eq!(format!("{:.9}", Duration::new(2, 2)), "2.000000002s"); + assert_eq!(format!("{:.6}", Duration::new(2, 2)), "2.000000s"); + assert_eq!(format!("{:.6}", Duration::new(0, 2_000_002)), "2.000002ms"); + assert_eq!(format!("{:.20}", Duration::new(1, 1)), "1.000000001s"); + assert_eq!(format!("{:.3}", Duration::new(1, 0)), "1.000s"); + assert_eq!(format!("{:.3}", Duration::new(0, 1)), "1ns"); + assert_eq!(format!("{:.0}", Duration::new(0, 1_001)), "1µs"); + assert_eq!(format!("{:.0}", Duration::new(0, 1_500)), "2µs"); + assert_eq!(format!("{:.9}", Duration::new(0, 100)), "100ns"); + assert_eq!(format!("{:.9}", Duration::new(0, 100_000)), "100.000µs"); + assert_eq!(format!("{:.9}", Duration::new(0, 100_000_000)), "100.000000ms"); + assert_eq!(format!("{:.9}", Duration::new(100,0)), "100.000000000s"); + + assert_eq!(format!("{:.9}", Duration::new(u64::max_value(), 999_999_999)), + "18446744073709551615.999999999s"); + assert_eq!(Duration::new(u64::max_value(), 999_999_999).to_string(), + "18446744073709551616s"); + assert_eq!(format!("{:.3}", Duration::new(u64::max_value(), 999_999_999)), + "18446744073709551616.000s") } }