to_str/
numeric.rs

1use crate::ToStr;
2
3use core::{ptr};
4use core::str::from_utf8_unchecked;
5
6//num % 100 * 2 + 1 at most will be 200, therefore DIGITS contains this much.
7static DEC_DIGITS: &[u8; 200] = b"0001020304050607080910111213141516171819\
8                                  2021222324252627282930313233343536373839\
9                                  4041424344454647484950515253545556575859\
10                                  6061626364656667686970717273747576777879\
11                                  8081828384858687888990919293949596979899";
12static 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'];
13const PTR_PREFIX: [u8; 2] = [b'0', b'x'];
14
15const fn size_of_val<T>(_: &T) -> usize {
16    core::mem::size_of::<T>()
17}
18
19unsafe fn write_u8_to_buf(mut num: u8, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
20    let digits_ptr = DEC_DIGITS.as_ptr();
21
22    if num >= 100 {
23        let index = (num as isize % 100) << 1;
24        num /= 100;
25
26        cursor -= 3;
27        ptr::write(buffer_ptr.offset(cursor), *digits_ptr + num);
28        ptr::copy_nonoverlapping(digits_ptr.offset(index), buffer_ptr.offset(cursor + 1), 2);
29    } else if num <= 9 {
30        cursor -= 1;
31        ptr::write(buffer_ptr.offset(cursor), *digits_ptr + num);
32    } else {
33        let index = num as isize * 2;
34
35        cursor -= 2;
36        ptr::copy_nonoverlapping(digits_ptr.offset(index), buffer_ptr.offset(cursor), 2);
37    }
38
39    cursor
40}
41
42unsafe fn write_u64_to_buf(mut num: u64, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
43    let digits_ptr = DEC_DIGITS.as_ptr();
44
45    while num >= 10000 {
46        let rem = (num % 10000) as isize;
47        num /= 10000;
48
49        let index1 = (rem / 100) << 1;
50        let index2 = (rem % 100) << 1;
51        cursor -= 4;
52        ptr::copy_nonoverlapping(digits_ptr.offset(index1), buffer_ptr.offset(cursor), 2);
53        ptr::copy_nonoverlapping(digits_ptr.offset(index2), buffer_ptr.offset(cursor + 2), 2);
54    }
55
56    if num >= 100 {
57        let index = (num as isize % 100) << 1;
58        num /= 100;
59
60        cursor -= 2;
61        ptr::copy_nonoverlapping(digits_ptr.offset(index), buffer_ptr.offset(cursor), 2);
62    }
63
64    if num < 10 {
65        cursor -= 1;
66        ptr::write(buffer_ptr.offset(cursor), *digits_ptr + num as u8);
67    } else {
68        let index = num as isize * 2;
69
70        cursor -= 2;
71        ptr::copy_nonoverlapping(digits_ptr.offset(index), buffer_ptr.offset(cursor), 2);
72    }
73
74    cursor
75}
76
77//Taken from https://github.com/dtolnay/itoa for a better x128 divisions
78#[inline]
79pub fn udivmod_1e19(num: &mut u128) -> u64 {
80    const DIV: u64 = 10_000_000_000_000_000_000;
81
82    let high = (*num >> 64) as u64;
83    if high == 0 {
84        let low = *num as u64;
85        *num = (low / DIV) as u128;
86        return low % DIV;
87    }
88
89    let sr = 65 - high.leading_zeros();
90
91    let mut q: u128 = *num << (128 - sr);
92    let mut r: u128 = *num >> sr;
93    let mut carry: u64 = 0;
94
95    let mut i = 0;
96    while i < sr {
97        i += 1;
98
99        r = (r << 1) | (q >> 127);
100        q = (q << 1) | carry as u128;
101
102        let s = (DIV as u128).wrapping_sub(r).wrapping_sub(1) as i128 >> 127;
103        carry = (s & 1) as u64;
104        r -= (DIV as u128) & s as u128;
105    }
106
107    *num = (q << 1) | carry as u128;
108    r as u64
109}
110
111unsafe fn write_u128_to_buf(mut num: u128, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
112    const U64_TEXT_SIZE: isize = u64::TEXT_SIZE as isize;
113    const U64_TEXT_MAX_WRITTEN: isize = u64::TEXT_SIZE as isize - 1;
114    let digits_ptr = DEC_DIGITS.as_ptr();
115
116    let mut offset = cursor - u64::TEXT_SIZE as isize;
117    let mut written = U64_TEXT_SIZE - write_u64_to_buf(udivmod_1e19(&mut num), buffer_ptr.offset(offset), U64_TEXT_SIZE);
118
119    cursor -= written;
120
121    if num != 0 {
122        ptr::write_bytes(buffer_ptr.offset(cursor), *digits_ptr, (U64_TEXT_MAX_WRITTEN - written) as usize);
123
124        offset = cursor - u64::TEXT_SIZE as isize;
125        written = U64_TEXT_SIZE - write_u64_to_buf(udivmod_1e19(&mut num), buffer_ptr.offset(offset), U64_TEXT_SIZE);
126
127        cursor -= written;
128
129        if num != 0 {
130            ptr::write_bytes(buffer_ptr.offset(cursor), *digits_ptr, (U64_TEXT_MAX_WRITTEN - written) as usize);
131
132            // There is at most one digit left
133            // because u128::max / 10^19 / 10^19 is 3.
134            cursor -= 1;
135            *buffer_ptr.offset(cursor) = (num as u8) + b'0';
136        }
137    }
138
139    cursor
140}
141
142unsafe fn write_hex_to_buf(mut num: usize, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
143    const BASE: usize = 4;
144    const BASE_DIGIT: usize = (1 << BASE) - 1;
145    let digits_ptr = HEX_DIGITS.as_ptr();
146
147    loop {
148        let digit = num & BASE_DIGIT;
149        cursor -= 1;
150        ptr::write(buffer_ptr.offset(cursor), *digits_ptr.add(digit));
151        num >>= BASE;
152
153        if num == 0 {
154            break;
155        }
156    }
157
158    cursor
159}
160
161#[inline(always)]
162unsafe fn write_ptr_to_buf(num: usize, buffer_ptr: *mut u8, mut cursor: isize) -> isize {
163    const PTR_PREFIX_SIZE: usize = size_of_val(&PTR_PREFIX);
164    cursor = write_hex_to_buf(num, buffer_ptr, cursor);
165    cursor -= PTR_PREFIX_SIZE as isize;
166
167    ptr::copy_nonoverlapping(PTR_PREFIX.as_ptr(), buffer_ptr.offset(cursor), PTR_PREFIX_SIZE);
168    cursor
169}
170
171macro_rules! impl_unsigned {
172    ($t:ty: $max:expr; $conv:ident as $cv_t:ident) => {
173        unsafe impl ToStr for $t {
174            const TEXT_SIZE: usize = $max;
175
176            #[inline]
177            fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
178                debug_assert!(buffer.len() >= Self::TEXT_SIZE);
179
180                unsafe {
181                    let offset = $conv(*self as $cv_t, buffer.as_mut_ptr(), buffer.len() as isize) as usize;
182                    from_utf8_unchecked(&buffer[offset..])
183                }
184            }
185        }
186    }
187}
188
189impl_unsigned!(u8: 3; write_u8_to_buf as u8);
190impl_unsigned!(u16: 5; write_u64_to_buf as u64);
191impl_unsigned!(u32: 10; write_u64_to_buf as u64);
192impl_unsigned!(u64: 20; write_u64_to_buf as u64);
193#[cfg(target_pointer_width = "16")]
194impl_unsigned!(usize: 5; write_u64_to_buf as u64);
195#[cfg(target_pointer_width = "32")]
196impl_unsigned!(usize: 10; write_u64_to_buf as u64);
197#[cfg(target_pointer_width = "64")]
198impl_unsigned!(usize: 20; write_u64_to_buf as u64);
199impl_unsigned!(u128: 39; write_u128_to_buf as u128);
200
201macro_rules! impl_signed {
202    ($t:ty as $st:ty where $conv:ident as $cv_t:ty) => {
203        unsafe impl ToStr for $t {
204            const TEXT_SIZE: usize = <$st>::TEXT_SIZE + 1;
205
206            #[inline]
207            fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
208                if self.is_negative() {
209                    debug_assert!(buffer.len() >= Self::TEXT_SIZE);
210
211                    let abs = (0 as $st).wrapping_sub(*self as $st);
212                    unsafe {
213                        let offset = $conv(abs as $cv_t, buffer.as_mut_ptr(), buffer.len() as isize) - 1;
214                        ptr::write(buffer.as_mut_ptr().offset(offset), b'-');
215                        from_utf8_unchecked(&mut buffer[offset as usize..])
216                    }
217
218                } else {
219                    ToStr::to_str(&(*self as $st), buffer)
220                }
221            }
222        }
223    }
224}
225
226impl_signed!(i8 as u8 where write_u8_to_buf as u8);
227impl_signed!(i16 as u16 where write_u64_to_buf as u64);
228impl_signed!(i32 as u32 where write_u64_to_buf as u64);
229impl_signed!(i64 as u64 where write_u64_to_buf as u64);
230#[cfg(target_pointer_width = "16")]
231impl_signed!(isize as u16 where write_u64_to_buf as u64);
232#[cfg(target_pointer_width = "32")]
233impl_signed!(isize as u32 where write_u64_to_buf as u64);
234#[cfg(target_pointer_width = "64")]
235impl_signed!(isize as u64 where write_u64_to_buf as u64);
236impl_signed!(i128 as u128 where write_u128_to_buf as u128);
237
238unsafe impl<T> ToStr for *const T {
239    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
240
241    #[inline]
242    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
243        debug_assert!(buffer.len() >= Self::TEXT_SIZE);
244
245        unsafe {
246            let offset = write_ptr_to_buf(*self as usize, buffer.as_mut_ptr(), buffer.len() as isize) as usize;
247            from_utf8_unchecked(&buffer[offset..])
248        }
249    }
250}
251
252unsafe impl<T> ToStr for *mut T {
253    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
254
255    #[inline(always)]
256    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
257        (*self as *const T).to_str(buffer)
258    }
259}
260
261unsafe impl<T> ToStr for core::sync::atomic::AtomicPtr<T> {
262    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
263
264    #[inline(always)]
265    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
266        self.load(core::sync::atomic::Ordering::Acquire).to_str(buffer)
267    }
268}
269
270unsafe impl<T> ToStr for ptr::NonNull<T> {
271    const TEXT_SIZE: usize = usize::TEXT_SIZE + 2;
272
273    #[inline(always)]
274    fn to_str<'a>(&self, buffer: &'a mut [u8]) -> &'a str {
275        self.as_ptr().to_str(buffer)
276    }
277}