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

Split format and format_finite #15

Merged
merged 1 commit into from
Jun 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
Expand Down
2 changes: 1 addition & 1 deletion examples/upstream_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
79 changes: 74 additions & 5 deletions src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<F: Float>(&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.
///
Expand All @@ -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<F: Float>(&mut self, f: F) -> &str {
pub fn format_finite<F: Float>(&mut self, f: F) -> &str {
unsafe {
let n = f.write_to_ryu_buffer(&mut self.bytes[0]);
debug_assert!(n <= self.bytes.len());
Expand All @@ -74,21 +99,65 @@ 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::<f32, u32>(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::<f32, u32>(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)
}
}

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::<f64, u64>(self) };
bits & EXP_MASK == EXP_MASK

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Does this wind up being faster than !number.f64::is_finite()?

Also, thanks so much for this! :-)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

}

#[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::<f64, u64>(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)
}
Expand Down
3 changes: 0 additions & 3 deletions src/d2s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 0 additions & 3 deletions src/f2s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
7 changes: 5 additions & 2 deletions tests/d2s_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand All @@ -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);
}
}

Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion tests/exhaustive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions tests/f2s_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand All @@ -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);
}
}

Expand All @@ -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]
Expand Down