compact_str/repr/
num.rs

1//! Implementations for efficiently converting a number into a [`Repr`]
2//!
3//! Adapted from the implementation in the `std` library at
4//! <https://github.com/rust-lang/rust/blob/b8214dc6c6fc20d0a660fb5700dca9ebf51ebe89/src/libcore/fmt/num.rs#L188-L266>
5
6use core::{
7    mem,
8    num,
9    ptr,
10};
11
12use super::traits::IntoRepr;
13use super::Repr;
14use crate::{
15    ToCompactStringError,
16    UnwrapWithMsg,
17};
18
19const DEC_DIGITS_LUT: &[u8] = b"\
20      0001020304050607080910111213141516171819\
21      2021222324252627282930313233343536373839\
22      4041424344454647484950515253545556575859\
23      6061626364656667686970717273747576777879\
24      8081828384858687888990919293949596979899";
25
26/// Defines the implementation of [`IntoRepr`] for integer types
27macro_rules! impl_IntoRepr {
28    ($t:ident, $conv_ty:ident) => {
29        impl IntoRepr for $t {
30            fn into_repr(self) -> Result<Repr, ToCompactStringError> {
31                // Determine the number of digits in this value
32                //
33                // Note: this considers the `-` symbol
34                let num_digits = NumChars::num_chars(self);
35                let mut repr = Repr::with_capacity(num_digits).unwrap_with_msg();
36
37                #[allow(unused_comparisons)]
38                let is_nonnegative = self >= 0;
39                let mut n = if is_nonnegative {
40                    self as $conv_ty
41                } else {
42                    // convert the negative num to positive by summing 1 to it's 2 complement
43                    (!(self as $conv_ty)).wrapping_add(1)
44                };
45                let mut curr = num_digits as isize;
46
47                // our string will end up being num_digits long
48                unsafe { repr.set_len(num_digits) };
49                // get mutable pointer to our buffer
50                let buf_ptr = unsafe { repr.as_mut_buf().as_mut_ptr() };
51
52                let lut_ptr = DEC_DIGITS_LUT.as_ptr();
53
54                unsafe {
55                    // need at least 16 bits for the 4-characters-at-a-time to work.
56                    if mem::size_of::<$t>() >= 2 {
57                        // eagerly decode 4 characters at a time
58                        while n >= 10000 {
59                            let rem = (n % 10000) as isize;
60                            n /= 10000;
61
62                            let d1 = (rem / 100) << 1;
63                            let d2 = (rem % 100) << 1;
64                            curr -= 4;
65                            ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
66                            ptr::copy_nonoverlapping(
67                                lut_ptr.offset(d2),
68                                buf_ptr.offset(curr + 2),
69                                2,
70                            );
71                        }
72                    }
73
74                    // if we reach here numbers are <= 9999, so at most 4 chars long
75                    let mut n = n as isize; // possibly reduce 64bit math
76
77                    // decode 2 more chars, if > 2 chars
78                    if n >= 100 {
79                        let d1 = (n % 100) << 1;
80                        n /= 100;
81                        curr -= 2;
82                        ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
83                    }
84
85                    // decode last 1 or 2 chars
86                    if n < 10 {
87                        curr -= 1;
88                        *buf_ptr.offset(curr) = (n as u8) + b'0';
89                    } else {
90                        let d1 = n << 1;
91                        curr -= 2;
92                        ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
93                    }
94
95                    if !is_nonnegative {
96                        curr -= 1;
97                        *buf_ptr.offset(curr) = b'-';
98                    }
99                }
100
101                // we should have moved all the way down our buffer
102                debug_assert_eq!(curr, 0);
103
104                Ok(repr)
105            }
106        }
107    };
108}
109
110impl_IntoRepr!(u8, u32);
111impl_IntoRepr!(i8, u32);
112impl_IntoRepr!(u16, u32);
113impl_IntoRepr!(i16, u32);
114impl_IntoRepr!(u32, u32);
115impl_IntoRepr!(i32, u32);
116impl_IntoRepr!(u64, u64);
117impl_IntoRepr!(i64, u64);
118
119#[cfg(target_pointer_width = "32")]
120impl_IntoRepr!(usize, u32);
121#[cfg(target_pointer_width = "32")]
122impl_IntoRepr!(isize, u32);
123
124#[cfg(target_pointer_width = "64")]
125impl_IntoRepr!(usize, u64);
126#[cfg(target_pointer_width = "64")]
127impl_IntoRepr!(isize, u64);
128
129/// For 128-bit integer types we use the [`itoa`] crate because writing into a buffer, and then
130/// copying the amount of characters we've written, is faster than determining the number of
131/// characters and then writing.
132impl IntoRepr for u128 {
133    #[inline]
134    fn into_repr(self) -> Result<Repr, ToCompactStringError> {
135        let mut buffer = itoa::Buffer::new();
136        Ok(Repr::new(buffer.format(self))?)
137    }
138}
139
140impl IntoRepr for i128 {
141    #[inline]
142    fn into_repr(self) -> Result<Repr, ToCompactStringError> {
143        let mut buffer = itoa::Buffer::new();
144        Ok(Repr::new(buffer.format(self))?)
145    }
146}
147
148/// Defines the implementation of [`IntoRepr`] for NonZero integer types
149macro_rules! impl_NonZero_IntoRepr {
150    ($t:path) => {
151        impl IntoRepr for $t {
152            #[inline]
153            fn into_repr(self) -> Result<Repr, ToCompactStringError> {
154                self.get().into_repr()
155            }
156        }
157    };
158}
159
160impl_NonZero_IntoRepr!(num::NonZeroU8);
161impl_NonZero_IntoRepr!(num::NonZeroI8);
162impl_NonZero_IntoRepr!(num::NonZeroU16);
163impl_NonZero_IntoRepr!(num::NonZeroI16);
164impl_NonZero_IntoRepr!(num::NonZeroU32);
165impl_NonZero_IntoRepr!(num::NonZeroI32);
166impl_NonZero_IntoRepr!(num::NonZeroU64);
167impl_NonZero_IntoRepr!(num::NonZeroI64);
168impl_NonZero_IntoRepr!(num::NonZeroUsize);
169impl_NonZero_IntoRepr!(num::NonZeroIsize);
170impl_NonZero_IntoRepr!(num::NonZeroU128);
171impl_NonZero_IntoRepr!(num::NonZeroI128);
172
173/// All of these `num_chars(...)` methods are kind of crazy, but they are necessary.
174///
175/// An alternate way to calculate the number of digits in a value is to do:
176/// ```
177/// let val = 42;
178/// let num_digits = ((val as f32).log10().floor()) as usize + 1;
179/// assert_eq!(num_digits, 2);
180/// ```
181/// But there are two problems with this approach:
182/// 1. floating point math is slow
183/// 2. results are dependent on floating point precision, which is too inaccurate for larger values
184///
185/// For example, consider this relatively large value...
186///
187/// ```
188/// let val = 9999995;
189/// let num_digits = ((val as f32).log10().floor()) as usize + 1;
190///
191/// // this is wrong! There are only 7 digits in this number!
192/// assert_eq!(num_digits, 8);
193/// ```
194///
195/// you can use `f64` to get better precision, e.g.
196///
197/// ```
198/// let val = 9999995;
199/// let num_digits = ((val as f64).log10().floor()) as usize + 1;
200///
201/// // the precision is enough to get the correct value
202/// assert_eq!(num_digits, 7);
203/// ```
204///
205/// ...but still not precise enough!
206///
207/// ```
208/// let val: u64 = 9999999999999999999;
209/// let num_digits = ((val as f64).log10().floor()) as usize + 1;
210///
211/// // this is wrong! the number is only 19 digits but the formula returns 20
212/// assert_eq!(num_digits, 20);
213/// ```
214trait NumChars {
215    fn num_chars(val: Self) -> usize;
216}
217
218impl NumChars for u8 {
219    #[inline(always)]
220    fn num_chars(val: u8) -> usize {
221        match val {
222            u8::MIN..=9 => 1,
223            10..=99 => 2,
224            100..=u8::MAX => 3,
225        }
226    }
227}
228
229impl NumChars for i8 {
230    #[inline(always)]
231    fn num_chars(val: i8) -> usize {
232        match val {
233            i8::MIN..=-100 => 4,
234            -99..=-10 => 3,
235            -9..=-1 => 2,
236            0..=9 => 1,
237            10..=99 => 2,
238            100..=i8::MAX => 3,
239        }
240    }
241}
242
243impl NumChars for u16 {
244    #[inline(always)]
245    fn num_chars(val: u16) -> usize {
246        match val {
247            u16::MIN..=9 => 1,
248            10..=99 => 2,
249            100..=999 => 3,
250            1000..=9999 => 4,
251            10000..=u16::MAX => 5,
252        }
253    }
254}
255
256impl NumChars for i16 {
257    #[inline(always)]
258    fn num_chars(val: i16) -> usize {
259        match val {
260            i16::MIN..=-10000 => 6,
261            -9999..=-1000 => 5,
262            -999..=-100 => 4,
263            -99..=-10 => 3,
264            -9..=-1 => 2,
265            0..=9 => 1,
266            10..=99 => 2,
267            100..=999 => 3,
268            1000..=9999 => 4,
269            10000..=i16::MAX => 5,
270        }
271    }
272}
273
274impl NumChars for u32 {
275    #[inline(always)]
276    fn num_chars(val: u32) -> usize {
277        match val {
278            u32::MIN..=9 => 1,
279            10..=99 => 2,
280            100..=999 => 3,
281            1000..=9999 => 4,
282            10000..=99999 => 5,
283            100000..=999999 => 6,
284            1000000..=9999999 => 7,
285            10000000..=99999999 => 8,
286            100000000..=999999999 => 9,
287            1000000000..=u32::MAX => 10,
288        }
289    }
290}
291
292impl NumChars for i32 {
293    #[inline(always)]
294    fn num_chars(val: i32) -> usize {
295        match val {
296            i32::MIN..=-1000000000 => 11,
297            -999999999..=-100000000 => 10,
298            -99999999..=-10000000 => 9,
299            -9999999..=-1000000 => 8,
300            -999999..=-100000 => 7,
301            -99999..=-10000 => 6,
302            -9999..=-1000 => 5,
303            -999..=-100 => 4,
304            -99..=-10 => 3,
305            -9..=-1 => 2,
306            0..=9 => 1,
307            10..=99 => 2,
308            100..=999 => 3,
309            1000..=9999 => 4,
310            10000..=99999 => 5,
311            100000..=999999 => 6,
312            1000000..=9999999 => 7,
313            10000000..=99999999 => 8,
314            100000000..=999999999 => 9,
315            1000000000..=i32::MAX => 10,
316        }
317    }
318}
319
320impl NumChars for u64 {
321    #[inline(always)]
322    fn num_chars(val: u64) -> usize {
323        match val {
324            u64::MIN..=9 => 1,
325            10..=99 => 2,
326            100..=999 => 3,
327            1000..=9999 => 4,
328            10000..=99999 => 5,
329            100000..=999999 => 6,
330            1000000..=9999999 => 7,
331            10000000..=99999999 => 8,
332            100000000..=999999999 => 9,
333            1000000000..=9999999999 => 10,
334            10000000000..=99999999999 => 11,
335            100000000000..=999999999999 => 12,
336            1000000000000..=9999999999999 => 13,
337            10000000000000..=99999999999999 => 14,
338            100000000000000..=999999999999999 => 15,
339            1000000000000000..=9999999999999999 => 16,
340            10000000000000000..=99999999999999999 => 17,
341            100000000000000000..=999999999999999999 => 18,
342            1000000000000000000..=9999999999999999999 => 19,
343            10000000000000000000..=u64::MAX => 20,
344        }
345    }
346}
347
348impl NumChars for i64 {
349    #[inline(always)]
350    fn num_chars(val: i64) -> usize {
351        match val {
352            i64::MIN..=-1000000000000000000 => 20,
353            -999999999999999999..=-100000000000000000 => 19,
354            -99999999999999999..=-10000000000000000 => 18,
355            -9999999999999999..=-1000000000000000 => 17,
356            -999999999999999..=-100000000000000 => 16,
357            -99999999999999..=-10000000000000 => 15,
358            -9999999999999..=-1000000000000 => 14,
359            -999999999999..=-100000000000 => 13,
360            -99999999999..=-10000000000 => 12,
361            -9999999999..=-1000000000 => 11,
362            -999999999..=-100000000 => 10,
363            -99999999..=-10000000 => 9,
364            -9999999..=-1000000 => 8,
365            -999999..=-100000 => 7,
366            -99999..=-10000 => 6,
367            -9999..=-1000 => 5,
368            -999..=-100 => 4,
369            -99..=-10 => 3,
370            -9..=-1 => 2,
371            0..=9 => 1,
372            10..=99 => 2,
373            100..=999 => 3,
374            1000..=9999 => 4,
375            10000..=99999 => 5,
376            100000..=999999 => 6,
377            1000000..=9999999 => 7,
378            10000000..=99999999 => 8,
379            100000000..=999999999 => 9,
380            1000000000..=9999999999 => 10,
381            10000000000..=99999999999 => 11,
382            100000000000..=999999999999 => 12,
383            1000000000000..=9999999999999 => 13,
384            10000000000000..=99999999999999 => 14,
385            100000000000000..=999999999999999 => 15,
386            1000000000000000..=9999999999999999 => 16,
387            10000000000000000..=99999999999999999 => 17,
388            100000000000000000..=999999999999999999 => 18,
389            1000000000000000000..=i64::MAX => 19,
390        }
391    }
392}
393
394impl NumChars for usize {
395    fn num_chars(val: usize) -> usize {
396        #[cfg(target_pointer_width = "32")]
397        {
398            u32::num_chars(val as u32)
399        }
400
401        #[cfg(target_pointer_width = "64")]
402        {
403            u64::num_chars(val as u64)
404        }
405    }
406}
407
408impl NumChars for isize {
409    fn num_chars(val: isize) -> usize {
410        #[cfg(target_pointer_width = "32")]
411        {
412            i32::num_chars(val as i32)
413        }
414
415        #[cfg(target_pointer_width = "64")]
416        {
417            i64::num_chars(val as i64)
418        }
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use alloc::string::ToString;
425
426    use super::IntoRepr;
427
428    #[test]
429    fn test_from_u8_sanity() {
430        let vals = [u8::MIN, u8::MIN + 1, 0, 42, u8::MAX - 1, u8::MAX];
431
432        for x in &vals {
433            let repr = u8::into_repr(*x).unwrap();
434            assert_eq!(repr.as_str(), x.to_string());
435        }
436    }
437
438    #[test]
439    fn test_from_i8_sanity() {
440        let vals = [i8::MIN, i8::MIN + 1, 0, 42, i8::MAX - 1, i8::MAX];
441
442        for x in &vals {
443            let repr = i8::into_repr(*x).unwrap();
444            assert_eq!(repr.as_str(), x.to_string());
445        }
446    }
447
448    #[test]
449    fn test_from_u16_sanity() {
450        let vals = [u16::MIN, u16::MIN + 1, 0, 42, u16::MAX - 1, u16::MAX];
451
452        for x in &vals {
453            let repr = u16::into_repr(*x).unwrap();
454            assert_eq!(repr.as_str(), x.to_string());
455        }
456    }
457
458    #[test]
459    fn test_from_i16_sanity() {
460        let vals = [i16::MIN, i16::MIN + 1, 0, 42, i16::MAX - 1, i16::MAX];
461
462        for x in &vals {
463            let repr = i16::into_repr(*x).unwrap();
464            assert_eq!(repr.as_str(), x.to_string());
465        }
466    }
467
468    #[test]
469    fn test_from_u32_sanity() {
470        let vals = [u32::MIN, u32::MIN + 1, 0, 42, u32::MAX - 1, u32::MAX];
471
472        for x in &vals {
473            let repr = u32::into_repr(*x).unwrap();
474            assert_eq!(repr.as_str(), x.to_string());
475        }
476    }
477
478    #[test]
479    fn test_from_i32_sanity() {
480        let vals = [i32::MIN, i32::MIN + 1, 0, 42, i32::MAX - 1, i32::MAX];
481
482        for x in &vals {
483            let repr = i32::into_repr(*x).unwrap();
484            assert_eq!(repr.as_str(), x.to_string());
485        }
486    }
487
488    #[test]
489    fn test_from_u64_sanity() {
490        let vals = [u64::MIN, u64::MIN + 1, 0, 42, u64::MAX - 1, u64::MAX];
491
492        for x in &vals {
493            let repr = u64::into_repr(*x).unwrap();
494            assert_eq!(repr.as_str(), x.to_string());
495        }
496    }
497
498    #[test]
499    fn test_from_i64_sanity() {
500        let vals = [i64::MIN, i64::MIN + 1, 0, 42, i64::MAX - 1, i64::MAX];
501
502        for x in &vals {
503            let repr = i64::into_repr(*x).unwrap();
504            assert_eq!(repr.as_str(), x.to_string());
505        }
506    }
507
508    #[test]
509    fn test_from_usize_sanity() {
510        let vals = [
511            usize::MIN,
512            usize::MIN + 1,
513            0,
514            42,
515            usize::MAX - 1,
516            usize::MAX,
517        ];
518
519        for x in &vals {
520            let repr = usize::into_repr(*x).unwrap();
521            assert_eq!(repr.as_str(), x.to_string());
522        }
523    }
524
525    #[test]
526    fn test_from_isize_sanity() {
527        let vals = [
528            isize::MIN,
529            isize::MIN + 1,
530            0,
531            42,
532            isize::MAX - 1,
533            isize::MAX,
534        ];
535
536        for x in &vals {
537            let repr = isize::into_repr(*x).unwrap();
538            assert_eq!(repr.as_str(), x.to_string());
539        }
540    }
541
542    #[test]
543    fn test_from_u128_sanity() {
544        let vals = [u128::MIN, u128::MIN + 1, 0, 42, u128::MAX - 1, u128::MAX];
545
546        for x in &vals {
547            let repr = u128::into_repr(*x).unwrap();
548            assert_eq!(repr.as_str(), x.to_string());
549        }
550    }
551
552    #[test]
553    fn test_from_i128_sanity() {
554        let vals = [i128::MIN, i128::MIN + 1, 0, 42, i128::MAX - 1, i128::MAX];
555
556        for x in &vals {
557            let repr = i128::into_repr(*x).unwrap();
558            assert_eq!(repr.as_str(), x.to_string());
559        }
560    }
561}