const_ryu/buffer/
mod.rs

1use crate::raw;
2use core::mem::MaybeUninit;
3use core::{slice, str};
4#[cfg(feature = "no-panic")]
5use no_panic::no_panic;
6
7const NAN: &str = "NaN";
8const INFINITY: &str = "inf";
9const NEG_INFINITY: &str = "-inf";
10
11/// Safe API for formatting floating point numbers to text.
12///
13/// ## Example
14///
15/// ```
16/// let mut buffer = const_ryu::Buffer::new();
17/// let printed = buffer.format_finite(1.234);
18/// assert_eq!(printed, "1.234");
19/// ```
20pub struct Buffer {
21    bytes: [MaybeUninit<u8>; 24],
22}
23
24impl Buffer {
25    /// This is a cheap operation; you don't need to worry about reusing buffers
26    /// for efficiency.
27    #[inline]
28    #[cfg_attr(feature = "no-panic", no_panic)]
29    pub const fn new() -> Self {
30        let bytes = [MaybeUninit::<u8>::uninit(); 24];
31        Buffer { bytes }
32    }
33
34    /// Print a floating point number into this buffer and return a reference to
35    /// its string representation within the buffer.
36    ///
37    /// # Special cases
38    ///
39    /// This function formats NaN as the string "NaN", positive infinity as
40    /// "inf", and negative infinity as "-inf" to match std::fmt.
41    ///
42    /// If your input is known to be finite, you may get better performance by
43    /// calling the `format_finite` method instead of `format` to avoid the
44    /// checks for special cases.
45    #[cfg_attr(feature = "no-panic", inline)]
46    #[cfg_attr(feature = "no-panic", no_panic)]
47    pub fn format<F: Float>(&mut self, f: F) -> &str {
48        if f.is_nonfinite() {
49            f.format_nonfinite()
50        } else {
51            self.format_finite(f)
52        }
53    }
54
55    /// Print a floating point number into this buffer and return a reference to
56    /// its string representation within the buffer.
57    ///
58    /// # Special cases
59    ///
60    /// This function **does not** check for NaN or infinity. If the input
61    /// number is not a finite float, the printed representation will be some
62    /// correctly formatted but unspecified numerical value.
63    ///
64    /// Please check [`is_finite`] yourself before calling this function, or
65    /// check [`is_nan`] and [`is_infinite`] and handle those cases yourself.
66    ///
67    /// [`is_finite`]: f64::is_finite
68    /// [`is_nan`]: f64::is_nan
69    /// [`is_infinite`]: f64::is_infinite
70    #[inline]
71    #[cfg_attr(feature = "no-panic", no_panic)]
72    pub fn format_finite<F: Float>(&mut self, f: F) -> &str {
73        #[expect(clippy::ptr_as_ptr)]
74        unsafe {
75            let n = f.write_to_ryu_buffer(self.bytes.as_mut_ptr() as *mut u8);
76            if true {
    if !(n <= self.bytes.len()) {
        ::core::panicking::panic("assertion failed: n <= self.bytes.len()")
    };
};debug_assert!(n <= self.bytes.len());
77            let slice = slice::from_raw_parts(self.bytes.as_ptr() as *const u8, n);
78            str::from_utf8_unchecked(slice)
79        }
80    }
81}
82
83impl Copy for Buffer {}
84
85#[allow(clippy::non_canonical_clone_impl)]
86impl Clone for Buffer {
87    #[inline]
88    fn clone(&self) -> Self {
89        Buffer::new()
90    }
91}
92
93impl Default for Buffer {
94    #[inline]
95    #[cfg_attr(feature = "no-panic", no_panic)]
96    fn default() -> Self {
97        Buffer::new()
98    }
99}
100
101/// A floating point number, f32 or f64, that can be written into a
102/// [`const_ryu::Buffer`][Buffer].
103///
104/// This trait is sealed and cannot be implemented for types outside of the
105/// `ryu` crate.
106pub trait Float: Sealed {}
107impl Float for f32 {}
108impl Float for f64 {}
109
110pub trait Sealed: Copy {
111    fn is_nonfinite(self) -> bool;
112    fn format_nonfinite(self) -> &'static str;
113    unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize;
114}
115
116#[derive(#[automatically_derived]
impl<T: ::core::fmt::Debug> ::core::fmt::Debug for ConstFloat<T> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "ConstFloat",
            &&self.0)
    }
}Debug, #[automatically_derived]
impl<T: ::core::clone::Clone> ::core::clone::Clone for ConstFloat<T> {
    #[inline]
    fn clone(&self) -> ConstFloat<T> {
        ConstFloat(::core::clone::Clone::clone(&self.0))
    }
}Clone, #[automatically_derived]
impl<T: ::core::marker::Copy> ::core::marker::Copy for ConstFloat<T> { }Copy)]
117struct ConstFloat<T>(T);
118
119impl ConstFloat<f32> {
120    const fn to_bits(self) -> u32 {
121        self.0.to_bits()
122    }
123}
124impl ConstFloat<f64> {
125    const fn to_bits(self) -> u64 {
126        self.0.to_bits()
127    }
128}
129
130macro_rules! Float {
131    ($(
132        impl $Trait:ident for $Ty:ty {$(
133            $(#$attr:tt)*
134            $f1:ident $f2:ident $($f3:ident)? ($this:ident $(, $rest_arg:ident: $RestArgType:ty)* $(,)?) -> $ReturnType:ty
135            $f_body:block
136        )*}
137    )*) => {$(
138        impl ConstFloat<$Ty> {$(
139            #[inline] // Is this good?
140            const $f1 $f2 $($f3)? ($this $(, $rest_arg: $RestArgType)*) -> $ReturnType
141            $f_body
142        )*}
143        impl $Trait for $Ty {$(
144            $(#$attr)* // Is this good?
145            $f1 $f2 $($f3)? ($this $(, $rest_arg: $RestArgType)*) -> $ReturnType {
146                Float!(@call_fn (ConstFloat::<$Ty>($this)) ($f2 $($f3)?) ($($rest_arg),*))
147            }
148        )*}
149    )*};
150    (@call_fn ($this:expr) ($f:ident) $args:tt) => {
151        $this.$f $args
152    };
153    (@call_fn ($this:expr) (fn $f:ident) $args:tt) => {
154        $this.$f $args
155    };
156}
157
158impl ConstFloat<f64> {
    #[inline]
    const fn is_nonfinite(self) -> bool {
        const EXP_MASK: u64 = 0x7ff0000000000000;
        let bits = self.to_bits();
        bits & EXP_MASK == EXP_MASK
    }
    #[inline]
    const fn format_nonfinite(self) -> &'static str {
        const MANTISSA_MASK: u64 = 0x000fffffffffffff;
        const SIGN_MASK: u64 = 0x8000000000000000;
        let bits = self.to_bits();
        if bits & MANTISSA_MASK != 0 {
            NAN
        } else if bits & SIGN_MASK != 0 { NEG_INFINITY } else { INFINITY }
    }
    #[inline]
    const unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
        raw::format64(self.0, result)
    }
}
impl Sealed for f64 {
    #[inline]
    fn is_nonfinite(self) -> bool { ConstFloat::<f64>(self).is_nonfinite() }
    #[cold]
    fn format_nonfinite(self) -> &'static str {
        ConstFloat::<f64>(self).format_nonfinite()
    }
    #[inline]
    unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
        ConstFloat::<f64>(self).write_to_ryu_buffer(result)
    }
}Float! {
159impl Sealed for f32 {
160    #[inline]
161    fn is_nonfinite(self) -> bool {
162        const EXP_MASK: u32 = 0x7f800000;
163        let bits = self.to_bits();
164        bits & EXP_MASK == EXP_MASK
165    }
166
167    #[cold]
168    #[cfg_attr(feature = "no-panic", inline)]
169    fn format_nonfinite(self) -> &'static str {
170        const MANTISSA_MASK: u32 = 0x007fffff;
171        const SIGN_MASK: u32 = 0x80000000;
172        let bits = self.to_bits();
173        if bits & MANTISSA_MASK != 0 {
174            NAN
175        } else if bits & SIGN_MASK != 0 {
176            NEG_INFINITY
177        } else {
178            INFINITY
179        }
180    }
181
182    #[inline]
183    unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
184        raw::format32(self.0, result)
185    }
186}
187
188impl Sealed for f64 {
189    #[inline]
190    fn is_nonfinite(self) -> bool {
191        const EXP_MASK: u64 = 0x7ff0000000000000;
192        let bits = self.to_bits();
193        bits & EXP_MASK == EXP_MASK
194    }
195
196    #[cold]
197    #[cfg_attr(feature = "no-panic", inline)]
198    fn format_nonfinite(self) -> &'static str {
199        const MANTISSA_MASK: u64 = 0x000fffffffffffff;
200        const SIGN_MASK: u64 = 0x8000000000000000;
201        let bits = self.to_bits();
202        if bits & MANTISSA_MASK != 0 {
203            NAN
204        } else if bits & SIGN_MASK != 0 {
205            NEG_INFINITY
206        } else {
207            INFINITY
208        }
209    }
210
211    #[inline]
212    unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
213        raw::format64(self.0, result)
214    }
215}
216}
217
218/// Const & safe API for formatting floating point numbers to text.
219///
220/// ## Example
221///
222/// ```
223/// # const _: () = {macro_rules! assert_eq {($a:expr,$b:expr)=>{{mod __b{pub const VAL:&[u8]=$b.as_bytes();}assert!(matches!($a.as_bytes(), __b::VAL))}}}
224/// let mut buffer = const_ryu::Buffer::new();
225///
226/// let printed = const_ryu::Format(&mut buffer, 1.234f32).call_once();
227/// assert_eq!(printed, "1.234");
228///
229/// let printed = const_ryu::Format(&mut buffer, 1.234f64).call_once();
230/// assert_eq!(printed, "1.234");
231///
232/// let printed = const_ryu::Format(&mut buffer, f64::NAN).call_once();
233/// assert_eq!(printed, "NaN");
234/// # };
235/// ```
236pub struct Format<'a, T>(pub &'a mut Buffer, pub T);
237
238/// Const & safe API for formatting finite floating point numbers to text.
239///
240/// ## Example
241///
242/// ```
243/// # const _: () = {macro_rules! assert_eq {($a:expr,$b:expr)=>{{mod __b{pub const VAL:&[u8]=$b.as_bytes();}assert!(matches!($a.as_bytes(), __b::VAL))}}}
244/// let mut buffer = const_ryu::Buffer::new();
245///
246/// let printed = const_ryu::FormatFinite(&mut buffer, 1.234f32).call_once();
247/// assert_eq!(printed, "1.234");
248///
249/// let printed = const_ryu::FormatFinite(&mut buffer, 1.234f64).call_once();
250/// assert_eq!(printed, "1.234");
251/// # };
252/// ```
253pub struct FormatFinite<'a, T>(pub &'a mut Buffer, pub T);
254
255macro_rules! impl_fns {
256    (
257        type $T:ident = each_of!$each_of:tt;
258
259        $($rest:tt)*
260    ) => {
261        impl_fns! { @impl $T $each_of {$($rest)*} }
262    };
263    (@impl $T:ident [$($Ty:ty),* $(,)?] $impl_body:tt) => {$(
264        const _: () = {
265            type $T = $Ty;
266            const _: () = $impl_body;
267        };
268    )*};
269}
270
271const _: () =
    {
        type F = f64;
        const _: () =
            {
                impl<'a> Format<'a, F> {
                    pub const fn call_once(self) -> &'a str {
                        let Self(this, f) = self;
                        let f = ConstFloat(f);
                        if f.is_nonfinite() {
                            f.format_nonfinite()
                        } else { FormatFinite(this, f.0).call_once() }
                    }
                }
                impl<'a> FormatFinite<'a, F> {
                    pub const fn call_once(self) -> &'a str {
                        let Self(this, f) = self;
                        let f = ConstFloat(f);

                        #[expect(clippy::ptr_as_ptr)]
                        unsafe {
                            let n =
                                f.write_to_ryu_buffer(this.bytes.as_mut_ptr() as *mut u8);
                            if true {
                                if !(n <= this.bytes.len()) {
                                    ::core::panicking::panic("assertion failed: n <= this.bytes.len()")
                                };
                            };
                            let slice =
                                slice::from_raw_parts(this.bytes.as_ptr() as *const u8, n);
                            str::from_utf8_unchecked(slice)
                        }
                    }
                }
            };
    };impl_fns!(
272    type F = each_of![f32, f64];
273
274    impl<'a> Format<'a, F> {
275        pub const fn call_once(self) -> &'a str {
276            let Self(this, f) = self;
277            let f = ConstFloat(f);
278
279            if f.is_nonfinite() {
280                f.format_nonfinite()
281            } else {
282                FormatFinite(this, f.0).call_once()
283            }
284        }
285    }
286
287    impl<'a> FormatFinite<'a, F> {
288        pub const fn call_once(self) -> &'a str {
289            let Self(this, f) = self;
290            let f = ConstFloat(f);
291
292            #[expect(clippy::ptr_as_ptr)]
293            unsafe {
294                let n = f.write_to_ryu_buffer(this.bytes.as_mut_ptr() as *mut u8);
295                debug_assert!(n <= this.bytes.len());
296                let slice = slice::from_raw_parts(this.bytes.as_ptr() as *const u8, n);
297                str::from_utf8_unchecked(slice)
298            }
299        }
300    }
301);