Skip to main content

ryu/buffer/
mod.rs

1use core::{mem::MaybeUninit, slice, str};
2
3#[cfg(feature = "no-panic")]
4use no_panic::no_panic;
5
6use crate::raw;
7
8const NAN: &str = "NaN";
9const INFINITY: &str = "inf";
10const NEG_INFINITY: &str = "-inf";
11
12/// Safe API for formatting floating point numbers to text.
13///
14/// ## Example
15///
16/// ```
17/// let mut buffer = ryu::Buffer::new();
18/// let printed = buffer.format_finite(1.234);
19/// assert_eq!(printed, "1.234");
20/// ```
21pub struct Buffer {
22    bytes: [MaybeUninit<u8>; 24],
23}
24
25impl Buffer {
26    /// This is a cheap operation; you don't need to worry about reusing buffers
27    /// for efficiency.
28    #[inline]
29    #[cfg_attr(feature = "no-panic", no_panic)]
30    pub fn new() -> Self {
31        let bytes = [MaybeUninit::<u8>::uninit(); 24];
32        Buffer {
33            bytes,
34        }
35    }
36
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
58    /// Print a floating point number into this buffer and return a reference to
59    /// its string representation within the buffer.
60    ///
61    /// # Special cases
62    ///
63    /// This function **does not** check for NaN or infinity. If the input
64    /// number is not a finite float, the printed representation will be some
65    /// correctly formatted but unspecified numerical value.
66    ///
67    /// Please check [`is_finite`] yourself before calling this function, or
68    /// check [`is_nan`] and [`is_infinite`] and handle those cases yourself.
69    ///
70    /// [`is_finite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_finite
71    /// [`is_nan`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_nan
72    /// [`is_infinite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite
73    #[inline]
74    #[cfg_attr(feature = "no-panic", no_panic)]
75    pub fn format_finite<F: Float>(&mut self, f: F) -> &str {
76        unsafe {
77            let n = f.write_to_ryu_buffer(self.bytes.as_mut_ptr() as *mut u8);
78            debug_assert!(n <= self.bytes.len());
79            let slice = slice::from_raw_parts(self.bytes.as_ptr() as *const u8, n);
80            str::from_utf8_unchecked(slice)
81        }
82    }
83}
84
85impl Copy for Buffer {}
86
87impl Clone for Buffer {
88    #[inline]
89    #[allow(clippy::non_canonical_clone_impl)] // false positive https://github.com/rust-lang/rust-clippy/issues/11072
90    fn clone(&self) -> Self {
91        Buffer::new()
92    }
93}
94
95impl Default for Buffer {
96    #[inline]
97    #[cfg_attr(feature = "no-panic", no_panic)]
98    fn default() -> Self {
99        Buffer::new()
100    }
101}
102
103/// A floating point number, f32 or f64, that can be written into a
104/// [`ryu::Buffer`][Buffer].
105///
106/// This trait is sealed and cannot be implemented for types outside of the
107/// `ryu` crate.
108pub trait Float: Sealed {}
109impl Float for f32 {}
110impl Float for f64 {}
111
112pub trait Sealed: Copy {
113    fn is_nonfinite(self) -> bool;
114    fn format_nonfinite(self) -> &'static str;
115    unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize;
116}
117
118impl Sealed for f32 {
119    #[inline]
120    fn is_nonfinite(self) -> bool {
121        const EXP_MASK: u32 = 0x7f800000;
122        let bits = self.to_bits();
123        bits & EXP_MASK == EXP_MASK
124    }
125
126    #[cold]
127    #[cfg_attr(feature = "no-panic", inline)]
128    fn format_nonfinite(self) -> &'static str {
129        const MANTISSA_MASK: u32 = 0x007fffff;
130        const SIGN_MASK: u32 = 0x80000000;
131        let bits = self.to_bits();
132        if bits & MANTISSA_MASK != 0 {
133            NAN
134        } else if bits & SIGN_MASK != 0 {
135            NEG_INFINITY
136        } else {
137            INFINITY
138        }
139    }
140
141    #[inline]
142    unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
143        unsafe { raw::format32(self, result) }
144    }
145}
146
147impl Sealed for f64 {
148    #[inline]
149    fn is_nonfinite(self) -> bool {
150        const EXP_MASK: u64 = 0x7ff0000000000000;
151        let bits = self.to_bits();
152        bits & EXP_MASK == EXP_MASK
153    }
154
155    #[cold]
156    #[cfg_attr(feature = "no-panic", inline)]
157    fn format_nonfinite(self) -> &'static str {
158        const MANTISSA_MASK: u64 = 0x000fffffffffffff;
159        const SIGN_MASK: u64 = 0x8000000000000000;
160        let bits = self.to_bits();
161        if bits & MANTISSA_MASK != 0 {
162            NAN
163        } else if bits & SIGN_MASK != 0 {
164            NEG_INFINITY
165        } else {
166            INFINITY
167        }
168    }
169
170    #[inline]
171    unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
172        unsafe { raw::format64(self, result) }
173    }
174}