to_str/
numeric.rs

1use crate::ToStr;
2
3use core::{num, ptr};
4
5//num % 100 * 2 + 1 at most will be 200, therefore DIGITS contains this much.
6static DEC_DIGITS: &[u8; 200] = b"0001020304050607080910111213141516171819\
7                                  2021222324252627282930313233343536373839\
8                                  4041424344454647484950515253545556575859\
9                                  6061626364656667686970717273747576777879\
10                                  8081828384858687888990919293949596979899";
11static HEX_DIGITS: [u8; 16] = [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f'];
12const PTR_PREFIX: [u8; 2] = [b'0', b'x'];
13
14const fn size_of_val<T>(_: &T) -> usize {
15    core::mem::size_of::<T>()
16}
17
18macro_rules! write_digit {
19    ($buffer_ptr:ident[$cursor:ident] = $digit:expr) => {
20        $cursor -= 1;
21        unsafe {
22            *$buffer_ptr.offset($cursor) = ($digit as u8) + b'0';
23        }
24    }
25}
26
27macro_rules! write_two_digits {
28    ($buffer_ptr:ident[$cursor:ident] = $digits_ptr:ident[$digits_offset:expr]) => {
29
30        $cursor -= 1;
31        unsafe {
32            *$buffer_ptr.offset($cursor) = *$digits_ptr.offset($digits_offset + 1);
33        }
34
35        $cursor -= 1;
36        unsafe {
37            *$buffer_ptr.offset($cursor) = *$digits_ptr.offset($digits_offset);
38        }
39    };
40}
41
42pub(crate) const unsafe fn write_u8_to_buf(mut num: u8, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
43    let digits_ptr = DEC_DIGITS.as_ptr();
44
45    if num >= 100 {
46        let index = (num as isize % 100) << 1;
47        num /= 100;
48
49        write_two_digits!(buffer_ptr[cursor] = digits_ptr[index]);
50        write_digit!(buffer_ptr[cursor] = num);
51    } else if num <= 9 {
52        write_digit!(buffer_ptr[cursor] = num);
53    } else {
54        let index = num as isize * 2;
55
56        write_two_digits!(buffer_ptr[cursor] = digits_ptr[index]);
57    }
58
59    cursor
60}
61
62pub(crate) const unsafe fn write_u64_to_buf(mut num: u64, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
63    let digits_ptr = DEC_DIGITS.as_ptr();
64
65    while num >= 10000 {
66        let rem = (num % 10000) as isize;
67        num /= 10000;
68
69        let index1 = (rem / 100) << 1;
70        let index2 = (rem % 100) << 1;
71        write_two_digits!(buffer_ptr[cursor] = digits_ptr[index2]);
72        write_two_digits!(buffer_ptr[cursor] = digits_ptr[index1]);
73    }
74
75    if num >= 100 {
76        let index = (num as isize % 100) << 1;
77        num /= 100;
78
79        write_two_digits!(buffer_ptr[cursor] = digits_ptr[index]);
80    }
81
82    if num < 10 {
83        write_digit!(buffer_ptr[cursor] = num);
84    } else {
85        let index = num as isize * 2;
86
87        write_two_digits!(buffer_ptr[cursor] = digits_ptr[index]);
88    }
89
90    cursor
91}
92
93//Taken from https://github.com/dtolnay/itoa for a better x128 divisions
94//
95//Ref: https://github.com/dtolnay/itoa/blob/3091ce69da35e9c8a8ff29702ea3310af30684e4/src/udiv128.rs#L1
96#[inline(always)]
97const fn udivmod_1e19(num: &mut u128) -> u64 {
98    const DIV: u64 = 10_000_000_000_000_000_000;
99
100    #[inline(always)]
101    const fn u128_mulhi(x: u128, y: u128) -> u128 {
102        let x_lo = x as u64;
103        let x_hi = (x >> 64) as u64;
104        let y_lo = y as u64;
105        let y_hi = (y >> 64) as u64;
106
107        // handle possibility of overflow
108        let carry = (x_lo as u128 * y_lo as u128) >> 64;
109        let m = x_lo as u128 * y_hi as u128 + carry;
110        let high1 = m >> 64;
111
112        let m_lo = m as u64;
113        let high2 = (x_hi as u128 * y_lo as u128 + m_lo as u128) >> 64;
114
115        x_hi as u128 * y_hi as u128 + high1 + high2
116    }
117
118    let quot = if *num < 1 << 83 {
119        ((*num >> 19) as u64 / (DIV >> 19)) as u128
120    } else {
121        u128_mulhi(*num, 156927543384667019095894735580191660403) >> 62
122    };
123
124    let rem = (*num - quot * DIV as u128) as u64;
125    *num = quot;
126
127    rem
128}
129
130#[inline]
131pub(crate) const unsafe fn write_u128_to_buf(mut num: u128, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
132    const U64_TEXT_MAX_WRITTEN: isize = u64::TEXT_SIZE as isize - 1;
133
134    if num <= u64::MAX as u128 {
135        //shortcut to directly calling u64 routine once
136        unsafe {
137            return write_u64_to_buf(num as u64, buffer_ptr, cursor)
138        }
139    }
140
141    let first64 = udivmod_1e19(&mut num);
142    if num <= u64::MAX as u128 {
143        //Fill ahead of time to smooth
144        unsafe {
145            ptr::write_bytes(buffer_ptr.offset(cursor - U64_TEXT_MAX_WRITTEN), b'0', U64_TEXT_MAX_WRITTEN as _);
146        }
147
148        unsafe {
149            write_u64_to_buf(first64, buffer_ptr, cursor);
150        }
151        //finish directly with u64 write since it fits
152        unsafe {
153            write_u64_to_buf(num as u64, buffer_ptr, cursor - U64_TEXT_MAX_WRITTEN)
154        }
155    } else {
156        let second64 = udivmod_1e19(&mut num);
157
158        //Fill ahead of time to smooth
159        unsafe {
160            ptr::write_bytes(buffer_ptr.offset(cursor - U64_TEXT_MAX_WRITTEN * 2), b'0', (U64_TEXT_MAX_WRITTEN * 2) as _);
161        }
162
163        unsafe {
164            write_u64_to_buf(first64, buffer_ptr, cursor);
165        }
166
167        cursor -= U64_TEXT_MAX_WRITTEN;
168        let written_cursor = unsafe {
169            write_u64_to_buf(second64, buffer_ptr, cursor)
170        };
171
172        if num != 0 {
173            cursor -= U64_TEXT_MAX_WRITTEN;
174            // There is at most one digit left
175            // because u128::max / 10^19 / 10^19 is 3.
176            write_digit!(buffer_ptr[cursor] = num);
177            cursor
178        } else {
179            written_cursor
180        }
181    }
182}
183
184const unsafe fn write_hex_to_buf(mut num: usize, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
185    const BASE: usize = 4;
186    const BASE_DIGIT: usize = (1 << BASE) - 1;
187    let digits_ptr = HEX_DIGITS.as_ptr();
188
189    loop {
190        let digit = num & BASE_DIGIT;
191        cursor -= 1;
192        unsafe {
193            ptr::write(buffer_ptr.offset(cursor), *digits_ptr.add(digit));
194        }
195        num >>= BASE;
196
197        if num == 0 {
198            break;
199        }
200    }
201
202    cursor
203}
204
205#[inline(always)]
206pub(crate) const unsafe fn write_ptr_to_buf(num: usize, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
207    const PTR_PREFIX_SIZE: usize = size_of_val(&PTR_PREFIX);
208    cursor = unsafe {
209        write_hex_to_buf(num, buffer_ptr, cursor)
210    };
211    cursor -= PTR_PREFIX_SIZE as isize;
212
213    unsafe {
214        ptr::copy_nonoverlapping(PTR_PREFIX.as_ptr(), buffer_ptr.offset(cursor), PTR_PREFIX_SIZE);
215    }
216
217    cursor
218}
219
220macro_rules! impl_unsigned {
221    ($t:ident: $max:expr; $conv:ident($($cv_t:tt)*)) => {
222        #[inline]
223        pub(crate) const fn $t(num: $t, buffer: &'_ mut [core::mem::MaybeUninit<u8>]) -> &'_ str {
224            debug_assert!(buffer.len() >= <$t as crate::ToStr>::TEXT_SIZE);
225            unsafe {
226                let offset = super::$conv(num $($cv_t)*, buffer.as_mut_ptr() as *mut u8, buffer.len() as isize);
227                let slice = core::slice::from_raw_parts(buffer.as_ptr().offset(offset) as *const u8, buffer.len() - offset as usize);
228                core::str::from_utf8_unchecked(slice)
229            }
230        }
231
232        unsafe impl crate::ToStr for $t {
233            const TEXT_SIZE: usize = $max;
234
235            #[inline(always)]
236            fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
237                let buffer = unsafe {
238                    core::mem::transmute::<&'a mut [u8], &'a mut [core::mem::MaybeUninit<u8>]>(buffer)
239                };
240                $t(*self, buffer)
241            }
242        }
243    }
244}
245
246pub mod unsigned {
247    impl_unsigned!(u8: 3; write_u8_to_buf(as u8));
248    impl_unsigned!(u16: 5; write_u64_to_buf(as u64));
249    impl_unsigned!(u32: 10; write_u64_to_buf(as u64));
250    impl_unsigned!(u64: 20; write_u64_to_buf(as u64));
251    impl_unsigned!(u128: 39; write_u128_to_buf(as u128));
252
253    pub(crate) const fn usize(num: usize, buffer: &'_ mut [core::mem::MaybeUninit<u8>]) -> &'_ str {
254        debug_assert!(buffer.len() >= <usize as crate::ToStr>::TEXT_SIZE);
255        unsafe {
256            let offset = super::write_u64_to_buf(num as _, buffer.as_mut_ptr() as *mut u8, buffer.len() as isize);
257            let slice = core::slice::from_raw_parts(buffer.as_ptr().offset(offset) as *const u8, buffer.len() - offset as usize);
258            core::str::from_utf8_unchecked(slice)
259        }
260    }
261}
262
263unsafe impl ToStr for usize {
264    #[cfg(target_pointer_width = "16")]
265    const TEXT_SIZE: usize = <u16 as ToStr>::TEXT_SIZE;
266    #[cfg(target_pointer_width = "32")]
267    const TEXT_SIZE: usize = <u32 as ToStr>::TEXT_SIZE;
268    #[cfg(target_pointer_width = "64")]
269    const TEXT_SIZE: usize = <u64 as ToStr>::TEXT_SIZE;
270
271    #[inline]
272    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
273        let buffer = unsafe {
274            core::mem::transmute::<&'a mut [u8], &'a mut [core::mem::MaybeUninit<u8>]>(buffer)
275        };
276        unsigned::usize(*self, buffer)
277    }
278}
279
280macro_rules! impl_signed {
281    ($t:ident as $st:ident where $conv:ident as $cv_t:ty) => {
282        #[inline]
283        pub(crate) const fn $t(num: $t, buffer: &'_ mut [core::mem::MaybeUninit<u8>]) -> &'_ str {
284            if num.is_negative() {
285                debug_assert!(buffer.len() >= <$t as crate::ToStr>::TEXT_SIZE);
286
287                let abs = (0 as $st).wrapping_sub(num as $st);
288                unsafe {
289                    let offset = super::$conv(abs as $cv_t, buffer.as_mut_ptr() as *mut u8, buffer.len() as isize) - 1;
290                    core::ptr::write(buffer.as_mut_ptr().offset(offset), core::mem::MaybeUninit::new(b'-'));
291                    let slice = core::slice::from_raw_parts(buffer.as_ptr().offset(offset) as *const u8, buffer.len() - offset as usize);
292                    core::str::from_utf8_unchecked(slice)
293                }
294
295            } else {
296                crate::numeric::unsigned::$st(num as $st, buffer)
297            }
298        }
299
300        unsafe impl crate::ToStr for $t {
301            const TEXT_SIZE: usize = <$st>::TEXT_SIZE + 1;
302
303            #[inline(always)]
304            fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
305                let buffer = unsafe {
306                    core::mem::transmute::<&'a mut [u8], &'a mut [core::mem::MaybeUninit<u8>]>(buffer)
307                };
308                $t(*self, buffer)
309            }
310        }
311    }
312}
313
314pub mod signed {
315    impl_signed!(i8 as u8 where write_u8_to_buf as u8);
316    impl_signed!(i16 as u16 where write_u64_to_buf as u64);
317    impl_signed!(i32 as u32 where write_u64_to_buf as u64);
318    impl_signed!(i64 as u64 where write_u64_to_buf as u64);
319    impl_signed!(i128 as u128 where write_u128_to_buf as u128);
320
321    #[inline]
322    pub(crate) const fn isize(num: isize, buffer: &'_ mut [core::mem::MaybeUninit<u8>]) -> &'_ str {
323        if num.is_negative() {
324            debug_assert!(buffer.len() >= <isize as crate::ToStr>::TEXT_SIZE);
325
326            #[cfg(target_pointer_width = "16")]
327            let abs = 0i16.wrapping_sub(num as i16);
328            #[cfg(target_pointer_width = "32")]
329            let abs = 0i32.wrapping_sub(num as i32);
330            #[cfg(target_pointer_width = "64")]
331            let abs = 0i64.wrapping_sub(num as i64);
332
333            unsafe {
334                let offset = super::write_u64_to_buf(abs as _, buffer.as_mut_ptr() as *mut u8, buffer.len() as isize) - 1;
335                core::ptr::write(buffer.as_mut_ptr().offset(offset), core::mem::MaybeUninit::new(b'-'));
336                let slice = core::slice::from_raw_parts(buffer.as_ptr().offset(offset) as *const u8, buffer.len() - offset as usize);
337                core::str::from_utf8_unchecked(slice)
338            }
339        } else {
340            super::unsigned::usize(num as _, buffer)
341        }
342    }
343}
344
345unsafe impl ToStr for isize {
346    #[cfg(target_pointer_width = "16")]
347    const TEXT_SIZE: usize = <i16 as ToStr>::TEXT_SIZE;
348    #[cfg(target_pointer_width = "32")]
349    const TEXT_SIZE: usize = <i32 as ToStr>::TEXT_SIZE;
350    #[cfg(target_pointer_width = "64")]
351    const TEXT_SIZE: usize = <i64 as ToStr>::TEXT_SIZE;
352
353    #[inline(always)]
354    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
355
356        let buffer = unsafe {
357            core::mem::transmute::<&'a mut [u8], &'a mut [core::mem::MaybeUninit<u8>]>(buffer)
358        };
359        signed::isize(*self, buffer)
360    }
361}
362
363unsafe impl<T> ToStr for *const T {
364    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
365
366    #[inline]
367    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
368        debug_assert!(buffer.len() >= Self::TEXT_SIZE);
369
370        unsafe {
371            let offset = write_ptr_to_buf(*self as usize, buffer.as_mut_ptr(), buffer.len() as isize) as usize;
372            core::str::from_utf8_unchecked(&buffer[offset..])
373        }
374    }
375}
376
377unsafe impl<T> ToStr for *mut T {
378    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
379
380    #[inline(always)]
381    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
382        (*self as *const T).to_str(buffer)
383    }
384}
385
386unsafe impl<T> ToStr for core::sync::atomic::AtomicPtr<T> {
387    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
388
389    #[inline(always)]
390    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
391        self.load(core::sync::atomic::Ordering::Acquire).to_str(buffer)
392    }
393}
394
395unsafe impl<T> ToStr for ptr::NonNull<T> {
396    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
397
398    #[inline(always)]
399    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
400        self.as_ptr().to_str(buffer)
401    }
402}
403
404macro_rules! impl_non_zero_repr {
405    ($($t:ty: $repr:ty);* $(;)?) => {
406        $(
407        unsafe impl ToStr for $t {
408            const TEXT_SIZE: usize = {
409                assert!(core::mem::size_of::<$t>() == core::mem::size_of::<$repr>(), "NonZero type doesn't match Repr type");
410                <$repr as ToStr>::TEXT_SIZE
411            };
412
413            #[inline(always)]
414            fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
415                ToStr::to_str(&(*self).get(), buffer)
416            }
417        }
418        )*
419    }
420}
421
422impl_non_zero_repr!(
423    num::NonZeroU8: u8;
424    num::NonZeroU16: u16;
425    num::NonZeroU32: u32;
426    num::NonZeroU64: u64;
427    num::NonZeroU128: u128;
428    num::NonZeroUsize: usize;
429
430    num::NonZeroI8: i8;
431    num::NonZeroI16: i16;
432    num::NonZeroI32: i32;
433    num::NonZeroI64: i64;
434    num::NonZeroI128: i128;
435    num::NonZeroIsize: isize;
436);