From 1512b1f77b44705518ca1df58138aefdae12c1de Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Tue, 11 Jun 2019 11:16:31 -0700 Subject: [PATCH] Split format and format_finite --- benches/bench.rs | 2 +- examples/upstream_benchmark.rs | 2 +- src/buffer/mod.rs | 79 +++++++++++++++++++++++++++++++--- src/d2s.rs | 3 -- src/f2s.rs | 3 -- tests/d2s_test.rs | 7 ++- tests/exhaustive.rs | 2 +- tests/f2s_test.rs | 7 ++- 8 files changed, 87 insertions(+), 18 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index b78831e..04d9bee 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -18,7 +18,7 @@ macro_rules! benches { b.iter(move || { let value = black_box($value); - let formatted = buf.format(value); + let formatted = buf.format_finite(value); black_box(formatted); }); } diff --git a/examples/upstream_benchmark.rs b/examples/upstream_benchmark.rs index f97730d..c9ef3ae 100644 --- a/examples/upstream_benchmark.rs +++ b/examples/upstream_benchmark.rs @@ -56,7 +56,7 @@ macro_rules! benchmark { let t1 = std::time::SystemTime::now(); for _ in 0..ITERATIONS { - throwaway += ryu::Buffer::new().format(f).len(); + throwaway += ryu::Buffer::new().format_finite(f).len(); } let duration = t1.elapsed().unwrap(); let nanos = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64; diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 1fd2b28..5731aea 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -5,13 +5,17 @@ use raw; #[cfg(feature = "no-panic")] use no_panic::no_panic; +const NAN: &'static str = "NaN"; +const INFINITY: &'static str = "inf"; +const NEG_INFINITY: &'static str = "-inf"; + /// Safe API for formatting floating point numbers to text. /// /// ## Example /// /// ```edition2018 /// let mut buffer = ryu::Buffer::new(); -/// let printed = buffer.format(1.234); +/// let printed = buffer.format_finite(1.234); /// assert_eq!(printed, "1.234"); /// ``` #[derive(Copy, Clone)] @@ -30,6 +34,27 @@ impl Buffer { } } + /// Print a floating point number into this buffer and return a reference to + /// its string representation within the buffer. + /// + /// # Special cases + /// + /// This function formats NaN as the string "NaN", positive infinity as + /// "inf", and negative infinity as "-inf" to match std::fmt. + /// + /// If your input is known to be finite, you may get better performance by + /// calling the `format_finite` method instead of `format` to avoid the + /// checks for special cases. + #[cfg_attr(feature = "no-panic", inline)] + #[cfg_attr(feature = "no-panic", no_panic)] + pub fn format(&mut self, f: F) -> &str { + if f.is_nonfinite() { + f.format_nonfinite() + } else { + self.format_finite(f) + } + } + /// Print a floating point number into this buffer and return a reference to /// its string representation within the buffer. /// @@ -47,7 +72,7 @@ impl Buffer { /// [`is_infinite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite #[inline] #[cfg_attr(feature = "no-panic", no_panic)] - pub fn format(&mut self, f: F) -> &str { + pub fn format_finite(&mut self, f: F) -> &str { unsafe { let n = f.write_to_ryu_buffer(&mut self.bytes[0]); debug_assert!(n <= self.bytes.len()); @@ -74,13 +99,36 @@ pub trait Float: Sealed {} impl Float for f32 {} impl Float for f64 {} -pub trait Sealed { +pub trait Sealed: Copy { + fn is_nonfinite(self) -> bool; + fn format_nonfinite(self) -> &'static str; unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize; } impl Sealed for f32 { #[inline] - #[cfg_attr(feature = "no-panic", no_panic)] + fn is_nonfinite(self) -> bool { + const EXP_MASK: u32 = 0x7f800000; + let bits = unsafe { mem::transmute::(self) }; + bits & EXP_MASK == EXP_MASK + } + + #[cold] + #[cfg_attr(feature = "no-panic", inline)] + fn format_nonfinite(self) -> &'static str { + const MANTISSA_MASK: u32 = 0x007fffff; + const SIGN_MASK: u32 = 0x80000000; + let bits = unsafe { mem::transmute::(self) }; + if bits & MANTISSA_MASK != 0 { + NAN + } else if bits & SIGN_MASK != 0 { + NEG_INFINITY + } else { + INFINITY + } + } + + #[inline] unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { raw::format32(self, result) } @@ -88,7 +136,28 @@ impl Sealed for f32 { impl Sealed for f64 { #[inline] - #[cfg_attr(feature = "no-panic", no_panic)] + fn is_nonfinite(self) -> bool { + const EXP_MASK: u64 = 0x7ff0000000000000; + let bits = unsafe { mem::transmute::(self) }; + bits & EXP_MASK == EXP_MASK + } + + #[cold] + #[cfg_attr(feature = "no-panic", inline)] + fn format_nonfinite(self) -> &'static str { + const MANTISSA_MASK: u64 = 0x000fffffffffffff; + const SIGN_MASK: u64 = 0x8000000000000000; + let bits = unsafe { mem::transmute::(self) }; + if bits & MANTISSA_MASK != 0 { + NAN + } else if bits & SIGN_MASK != 0 { + NEG_INFINITY + } else { + INFINITY + } + } + + #[inline] unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { raw::format64(self, result) } diff --git a/src/d2s.rs b/src/d2s.rs index 614db6d..7e95884 100644 --- a/src/d2s.rs +++ b/src/d2s.rs @@ -27,9 +27,6 @@ use d2s_intrinsics::*; #[cfg(feature = "small")] use d2s_small_table::*; -#[cfg(feature = "no-panic")] -use no_panic::no_panic; - pub const DOUBLE_MANTISSA_BITS: u32 = 52; pub const DOUBLE_EXPONENT_BITS: u32 = 11; diff --git a/src/f2s.rs b/src/f2s.rs index d932e1a..f36c203 100644 --- a/src/f2s.rs +++ b/src/f2s.rs @@ -20,9 +20,6 @@ use common::*; -#[cfg(feature = "no-panic")] -use no_panic::no_panic; - pub const FLOAT_MANTISSA_BITS: u32 = 23; pub const FLOAT_EXPONENT_BITS: u32 = 8; diff --git a/tests/d2s_test.rs b/tests/d2s_test.rs index 5d46a25..da5f7eb 100644 --- a/tests/d2s_test.rs +++ b/tests/d2s_test.rs @@ -54,7 +54,7 @@ fn test_random() { let mut buffer = ryu::Buffer::new(); for _ in 0..1000000 { let f: f64 = rand::random(); - assert_eq!(f, buffer.format(f).parse().unwrap()); + assert_eq!(f, buffer.format_finite(f).parse().unwrap()); } } @@ -63,7 +63,7 @@ fn test_non_finite() { for i in 0u64..1 << 23 { let f = f64::from_bits((((1 << 11) - 1) << 52) + (i << 29)); assert!(!f.is_finite(), "f={}", f); - ryu::Buffer::new().format(f); + ryu::Buffer::new().format_finite(f); } } @@ -73,6 +73,9 @@ fn test_basic() { check!(-0.0); check!(1.0); check!(-1.0); + assert_eq!(pretty(f64::NAN), "NaN"); + assert_eq!(pretty(f64::INFINITY), "inf"); + assert_eq!(pretty(f64::NEG_INFINITY), "-inf"); } #[test] diff --git a/tests/exhaustive.rs b/tests/exhaustive.rs index 1548c6c..5c36969 100644 --- a/tests/exhaustive.rs +++ b/tests/exhaustive.rs @@ -40,7 +40,7 @@ fn test_exhaustive() { } let n = unsafe { ryu::raw::format32(f, &mut bytes[0]) }; assert_eq!(Ok(Ok(f)), str::from_utf8(&bytes[..n]).map(str::parse)); - assert_eq!(Ok(f), buffer.format(f).parse()); + assert_eq!(Ok(f), buffer.format_finite(f).parse()); } let increment = (max - min + 1) as usize; diff --git a/tests/f2s_test.rs b/tests/f2s_test.rs index d4d27bc..82968b4 100644 --- a/tests/f2s_test.rs +++ b/tests/f2s_test.rs @@ -49,7 +49,7 @@ fn test_random() { let mut buffer = ryu::Buffer::new(); for _ in 0..1000000 { let f: f32 = rand::random(); - assert_eq!(f, buffer.format(f).parse().unwrap()); + assert_eq!(f, buffer.format_finite(f).parse().unwrap()); } } @@ -58,7 +58,7 @@ fn test_non_finite() { for i in 0u32..1 << 23 { let f = f32::from_bits((((1 << 8) - 1) << 23) + i); assert!(!f.is_finite(), "f={}", f); - ryu::Buffer::new().format(f); + ryu::Buffer::new().format_finite(f); } } @@ -68,6 +68,9 @@ fn test_basic() { check!(-0.0); check!(1.0); check!(-1.0); + assert_eq!(pretty(f32::NAN), "NaN"); + assert_eq!(pretty(f32::INFINITY), "inf"); + assert_eq!(pretty(f32::NEG_INFINITY), "-inf"); } #[test]