Skip to content

Commit 1512b1f

Browse files
committed
Split format and format_finite
1 parent 397923c commit 1512b1f

File tree

8 files changed

+87
-18
lines changed

8 files changed

+87
-18
lines changed

benches/bench.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ macro_rules! benches {
1818

1919
b.iter(move || {
2020
let value = black_box($value);
21-
let formatted = buf.format(value);
21+
let formatted = buf.format_finite(value);
2222
black_box(formatted);
2323
});
2424
}

examples/upstream_benchmark.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ macro_rules! benchmark {
5656

5757
let t1 = std::time::SystemTime::now();
5858
for _ in 0..ITERATIONS {
59-
throwaway += ryu::Buffer::new().format(f).len();
59+
throwaway += ryu::Buffer::new().format_finite(f).len();
6060
}
6161
let duration = t1.elapsed().unwrap();
6262
let nanos = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;

src/buffer/mod.rs

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ use raw;
55
#[cfg(feature = "no-panic")]
66
use no_panic::no_panic;
77

8+
const NAN: &'static str = "NaN";
9+
const INFINITY: &'static str = "inf";
10+
const NEG_INFINITY: &'static str = "-inf";
11+
812
/// Safe API for formatting floating point numbers to text.
913
///
1014
/// ## Example
1115
///
1216
/// ```edition2018
1317
/// let mut buffer = ryu::Buffer::new();
14-
/// let printed = buffer.format(1.234);
18+
/// let printed = buffer.format_finite(1.234);
1519
/// assert_eq!(printed, "1.234");
1620
/// ```
1721
#[derive(Copy, Clone)]
@@ -30,6 +34,27 @@ impl Buffer {
3034
}
3135
}
3236

37+
/// Print a floating point number into this buffer and return a reference to
38+
/// its string representation within the buffer.
39+
///
40+
/// # Special cases
41+
///
42+
/// This function formats NaN as the string "NaN", positive infinity as
43+
/// "inf", and negative infinity as "-inf" to match std::fmt.
44+
///
45+
/// If your input is known to be finite, you may get better performance by
46+
/// calling the `format_finite` method instead of `format` to avoid the
47+
/// checks for special cases.
48+
#[cfg_attr(feature = "no-panic", inline)]
49+
#[cfg_attr(feature = "no-panic", no_panic)]
50+
pub fn format<F: Float>(&mut self, f: F) -> &str {
51+
if f.is_nonfinite() {
52+
f.format_nonfinite()
53+
} else {
54+
self.format_finite(f)
55+
}
56+
}
57+
3358
/// Print a floating point number into this buffer and return a reference to
3459
/// its string representation within the buffer.
3560
///
@@ -47,7 +72,7 @@ impl Buffer {
4772
/// [`is_infinite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite
4873
#[inline]
4974
#[cfg_attr(feature = "no-panic", no_panic)]
50-
pub fn format<F: Float>(&mut self, f: F) -> &str {
75+
pub fn format_finite<F: Float>(&mut self, f: F) -> &str {
5176
unsafe {
5277
let n = f.write_to_ryu_buffer(&mut self.bytes[0]);
5378
debug_assert!(n <= self.bytes.len());
@@ -74,21 +99,65 @@ pub trait Float: Sealed {}
7499
impl Float for f32 {}
75100
impl Float for f64 {}
76101

77-
pub trait Sealed {
102+
pub trait Sealed: Copy {
103+
fn is_nonfinite(self) -> bool;
104+
fn format_nonfinite(self) -> &'static str;
78105
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize;
79106
}
80107

81108
impl Sealed for f32 {
82109
#[inline]
83-
#[cfg_attr(feature = "no-panic", no_panic)]
110+
fn is_nonfinite(self) -> bool {
111+
const EXP_MASK: u32 = 0x7f800000;
112+
let bits = unsafe { mem::transmute::<f32, u32>(self) };
113+
bits & EXP_MASK == EXP_MASK
114+
}
115+
116+
#[cold]
117+
#[cfg_attr(feature = "no-panic", inline)]
118+
fn format_nonfinite(self) -> &'static str {
119+
const MANTISSA_MASK: u32 = 0x007fffff;
120+
const SIGN_MASK: u32 = 0x80000000;
121+
let bits = unsafe { mem::transmute::<f32, u32>(self) };
122+
if bits & MANTISSA_MASK != 0 {
123+
NAN
124+
} else if bits & SIGN_MASK != 0 {
125+
NEG_INFINITY
126+
} else {
127+
INFINITY
128+
}
129+
}
130+
131+
#[inline]
84132
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
85133
raw::format32(self, result)
86134
}
87135
}
88136

89137
impl Sealed for f64 {
90138
#[inline]
91-
#[cfg_attr(feature = "no-panic", no_panic)]
139+
fn is_nonfinite(self) -> bool {
140+
const EXP_MASK: u64 = 0x7ff0000000000000;
141+
let bits = unsafe { mem::transmute::<f64, u64>(self) };
142+
bits & EXP_MASK == EXP_MASK
143+
}
144+
145+
#[cold]
146+
#[cfg_attr(feature = "no-panic", inline)]
147+
fn format_nonfinite(self) -> &'static str {
148+
const MANTISSA_MASK: u64 = 0x000fffffffffffff;
149+
const SIGN_MASK: u64 = 0x8000000000000000;
150+
let bits = unsafe { mem::transmute::<f64, u64>(self) };
151+
if bits & MANTISSA_MASK != 0 {
152+
NAN
153+
} else if bits & SIGN_MASK != 0 {
154+
NEG_INFINITY
155+
} else {
156+
INFINITY
157+
}
158+
}
159+
160+
#[inline]
92161
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
93162
raw::format64(self, result)
94163
}

src/d2s.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ use d2s_intrinsics::*;
2727
#[cfg(feature = "small")]
2828
use d2s_small_table::*;
2929

30-
#[cfg(feature = "no-panic")]
31-
use no_panic::no_panic;
32-
3330
pub const DOUBLE_MANTISSA_BITS: u32 = 52;
3431
pub const DOUBLE_EXPONENT_BITS: u32 = 11;
3532

src/f2s.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020

2121
use common::*;
2222

23-
#[cfg(feature = "no-panic")]
24-
use no_panic::no_panic;
25-
2623
pub const FLOAT_MANTISSA_BITS: u32 = 23;
2724
pub const FLOAT_EXPONENT_BITS: u32 = 8;
2825

tests/d2s_test.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn test_random() {
5454
let mut buffer = ryu::Buffer::new();
5555
for _ in 0..1000000 {
5656
let f: f64 = rand::random();
57-
assert_eq!(f, buffer.format(f).parse().unwrap());
57+
assert_eq!(f, buffer.format_finite(f).parse().unwrap());
5858
}
5959
}
6060

@@ -63,7 +63,7 @@ fn test_non_finite() {
6363
for i in 0u64..1 << 23 {
6464
let f = f64::from_bits((((1 << 11) - 1) << 52) + (i << 29));
6565
assert!(!f.is_finite(), "f={}", f);
66-
ryu::Buffer::new().format(f);
66+
ryu::Buffer::new().format_finite(f);
6767
}
6868
}
6969

@@ -73,6 +73,9 @@ fn test_basic() {
7373
check!(-0.0);
7474
check!(1.0);
7575
check!(-1.0);
76+
assert_eq!(pretty(f64::NAN), "NaN");
77+
assert_eq!(pretty(f64::INFINITY), "inf");
78+
assert_eq!(pretty(f64::NEG_INFINITY), "-inf");
7679
}
7780

7881
#[test]

tests/exhaustive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ fn test_exhaustive() {
4040
}
4141
let n = unsafe { ryu::raw::format32(f, &mut bytes[0]) };
4242
assert_eq!(Ok(Ok(f)), str::from_utf8(&bytes[..n]).map(str::parse));
43-
assert_eq!(Ok(f), buffer.format(f).parse());
43+
assert_eq!(Ok(f), buffer.format_finite(f).parse());
4444
}
4545

4646
let increment = (max - min + 1) as usize;

tests/f2s_test.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fn test_random() {
4949
let mut buffer = ryu::Buffer::new();
5050
for _ in 0..1000000 {
5151
let f: f32 = rand::random();
52-
assert_eq!(f, buffer.format(f).parse().unwrap());
52+
assert_eq!(f, buffer.format_finite(f).parse().unwrap());
5353
}
5454
}
5555

@@ -58,7 +58,7 @@ fn test_non_finite() {
5858
for i in 0u32..1 << 23 {
5959
let f = f32::from_bits((((1 << 8) - 1) << 23) + i);
6060
assert!(!f.is_finite(), "f={}", f);
61-
ryu::Buffer::new().format(f);
61+
ryu::Buffer::new().format_finite(f);
6262
}
6363
}
6464

@@ -68,6 +68,9 @@ fn test_basic() {
6868
check!(-0.0);
6969
check!(1.0);
7070
check!(-1.0);
71+
assert_eq!(pretty(f32::NAN), "NaN");
72+
assert_eq!(pretty(f32::INFINITY), "inf");
73+
assert_eq!(pretty(f32::NEG_INFINITY), "-inf");
7174
}
7275

7376
#[test]

0 commit comments

Comments
 (0)