numtoa/
lib.rs

1//! The standard library provides a convenient method of converting numbers into strings, but these strings are
2//! heap-allocated. If you have an application which needs to convert large volumes of numbers into strings, but don't
3//! want to pay the price of heap allocation, this crate provides an efficient `no_std`-compatible method of heaplessly converting numbers
4//! into their string representations, storing the representation within a reusable byte array.
5//!
6//! In addition to supporting the standard base 10 conversion, this implementation allows you to select the base of
7//! your choice. Therefore, if you want a binary representation, set the base to 2. If you want hexadecimal, set the
8//! base to 16.
9//!
10//! # Convenience Example
11//!
12//! ```
13//! use numtoa::NumToA;
14//!
15//! let mut buf = [0u8; 20];
16//! let mut string = String::new();
17//!
18//! for number in (1..10) {
19//!     string.push_str(number.numtoa_str(10, &mut buf));
20//!     string.push('\n');
21//! }
22//!
23//! println!("{}", string);
24//! ```
25//!
26//! ## Base 10 Example
27//! ```
28//! use numtoa::NumToA;
29//! use std::io::{self, Write};
30//!
31//! let stdout = io::stdout();
32//! let mut stdout = stdout.lock();
33//! let mut buffer = [0u8; 20];
34//!
35//! let number: u32 = 162392;
36//! let mut start_indice = number.numtoa(10, &mut buffer);
37//! let _ = stdout.write(&buffer[start_indice..]);
38//! let _ = stdout.write(b"\n");
39//! assert_eq!(&buffer[start_indice..], b"162392");
40//!
41//! let other_number: i32 = -6235;
42//! start_indice = other_number.numtoa(10, &mut buffer);
43//! let _ = stdout.write(&buffer[start_indice..]);
44//! let _ = stdout.write(b"\n");
45//! assert_eq!(&buffer[start_indice..], b"-6235");
46//!
47//! let other_number: i8 = -128;
48//! start_indice = other_number.numtoa(10, &mut buffer);
49//! let _ = stdout.write(&buffer[start_indice..]);
50//! let _ = stdout.write(b"\n");
51//! assert_eq!(&buffer[start_indice..], b"-128");
52//!
53//! let other_number: i8 = 53;
54//! start_indice = other_number.numtoa(10, &mut buffer);
55//! let _ = stdout.write(&buffer[start_indice..]);
56//! let _ = stdout.write(b"\n");
57//! assert_eq!(&buffer[start_indice..], b"53");
58//!
59//! let other_number: i16 = -256;
60//! start_indice = other_number.numtoa(10, &mut buffer);
61//! let _ = stdout.write(&buffer[start_indice..]);
62//! let _ = stdout.write(b"\n");
63//! assert_eq!(&buffer[start_indice..], b"-256");
64//!
65//! let other_number: i16 = -32768;
66//! start_indice = other_number.numtoa(10, &mut buffer);
67//! let _ = stdout.write(&buffer[start_indice..]);
68//! let _ = stdout.write(b"\n");
69//! assert_eq!(&buffer[start_indice..], b"-32768");
70//!
71//! let large_num: u64 = 35320842;
72//! start_indice = large_num.numtoa(10, &mut buffer);
73//! let _ = stdout.write(&buffer[start_indice..]);
74//! let _ = stdout.write(b"\n");
75//! assert_eq!(&buffer[start_indice..], b"35320842");
76//!
77//! let max_u64: u64 = 18446744073709551615;
78//! start_indice = max_u64.numtoa(10, &mut buffer);
79//! let _ = stdout.write(&buffer[start_indice..]);
80//! let _ = stdout.write(b"\n");
81//! assert_eq!(&buffer[start_indice..], b"18446744073709551615");
82//! ```
83
84#![no_std]
85use core::mem::size_of;
86
87#[cfg(feature = "std")]
88extern crate std;
89#[cfg(feature = "std")]
90use std::str;
91
92/// Converts a number into a string representation, storing the conversion into a mutable byte slice.
93pub trait NumToA<T> {
94    /// Given a base for encoding and a mutable byte slice, write the number into the byte slice and return the
95    /// indice where the inner string begins. The inner string can be extracted by slicing the byte slice from
96    /// that indice.
97    ///
98    /// # Panics
99    /// If the supplied buffer is smaller than the number of bytes needed to write the integer, this will panic.
100    /// On debug builds, this function will perform a check on base 10 conversions to ensure that the input array
101    /// is large enough to hold the largest possible value in digits.
102    ///
103    /// # Example
104    /// ```
105    /// use numtoa::NumToA;
106    /// use std::io::{self, Write};
107    ///
108    /// let stdout = io::stdout();
109    /// let stdout = &mut io::stdout();
110    ///
111    /// let mut buffer = [0u8; 20];
112    /// let number = 15325;
113    /// let start_indice = number.numtoa(10, &mut buffer);
114    /// let _ = stdout.write(&buffer[start_indice..]);
115    /// assert_eq!(&buffer[start_indice..], b"15325");
116    /// ```
117    fn numtoa(self, base: T, string: &mut [u8]) -> usize;
118
119    #[cfg(feature = "std")]
120    /// Convenience method for quickly getting a string from the input's array buffer.
121    fn numtoa_str(self, base: T, buf: &mut [u8; 20]) -> &str;
122}
123
124// A lookup table to prevent the need for conditional branching
125// The value of the remainder of each step will be used as the index
126const LOOKUP: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
127
128// A lookup table optimized for decimal lookups. Each two indices represents one possible number.
129const DEC_LOOKUP: &[u8; 200] = b"0001020304050607080910111213141516171819\
130                                 2021222324252627282930313233343536373839\
131                                 4041424344454647484950515253545556575859\
132                                 6061626364656667686970717273747576777879\
133                                 8081828384858687888990919293949596979899";
134
135macro_rules! base_10 {
136    ($number:ident, $index:ident, $string:ident) => {
137        // Decode four characters at the same time
138        while $number > 9999 {
139            let rem = ($number % 10000) as u16;
140            let (frst, scnd) = ((rem / 100) * 2, (rem % 100) * 2);
141            $string[$index-3..$index-1].copy_from_slice(&DEC_LOOKUP[frst as usize..frst as usize+2]);
142            $string[$index-1..$index+1].copy_from_slice(&DEC_LOOKUP[scnd as usize..scnd as usize+2]);
143            $index = $index.wrapping_sub(4);
144            $number /= 10000;
145        }
146
147        if $number > 999 {
148            let (frst, scnd) = (($number / 100) * 2, ($number % 100) * 2);
149            $string[$index-3..$index-1].copy_from_slice(&DEC_LOOKUP[frst as usize..frst as usize+2]);
150            $string[$index-1..$index+1].copy_from_slice(&DEC_LOOKUP[scnd as usize..scnd as usize+2]);
151            $index = $index.wrapping_sub(4);
152        } else if $number > 99 {
153            let section = ($number as u16 / 10) * 2;
154            $string[$index-2..$index].copy_from_slice(&DEC_LOOKUP[section as usize..section as usize+2]);
155            $string[$index] = LOOKUP[($number % 10) as usize];
156            $index = $index.wrapping_sub(3);
157        } else if $number > 9 {
158            $number *= 2;
159            $string[$index-1..$index+1].copy_from_slice(&DEC_LOOKUP[$number as usize..$number as usize+2]);
160            $index = $index.wrapping_sub(2);
161        } else {
162            $string[$index] = LOOKUP[$number as usize];
163            $index = $index.wrapping_sub(1);
164        }
165    }
166}
167
168macro_rules! impl_unsized_numtoa_for {
169    ($t:ty) => {
170        impl NumToA<$t> for $t {
171            fn numtoa(mut self, base: $t, string: &mut [u8]) -> usize {
172                // Check if the buffer is large enough and panic on debug builds if it isn't
173                if cfg!(debug_assertions) {
174                    if base == 10 {
175                        match size_of::<$t>() {
176                            2 => debug_assert!(string.len() >= 5,  "u16 base 10 conversions require at least 5 bytes"),
177                            4 => debug_assert!(string.len() >= 10, "u32 base 10 conversions require at least 10 bytes"),
178                            8 => debug_assert!(string.len() >= 20, "u64 base 10 conversions require at least 20 bytes"),
179                            _ => unreachable!()
180                        }
181                    }
182                }
183
184                let mut index = string.len() - 1;
185                if self == 0 {
186                    string[index] = b'0';
187                    return index;
188                }
189
190                if base == 10 {
191                    // Convert using optimized base 10 algorithm
192                    base_10!(self, index, string);
193                } else {
194                    while self != 0 {
195                        let rem = self % base;
196                        string[index] = LOOKUP[rem as usize];
197                        index = index.wrapping_sub(1);
198                        self /= base;
199                    }
200                }
201
202                index.wrapping_add(1)
203            }
204
205            #[cfg(feature = "std")]
206            fn numtoa_str(self, base: $t, buf: &mut [u8; 20]) -> &str {
207                let s = self.numtoa(base, buf);
208                unsafe { str::from_utf8_unchecked(&buf[s..]) }
209            }
210        }
211    }
212}
213
214macro_rules! impl_sized_numtoa_for {
215    ($t:ty) => {
216        impl NumToA<$t> for $t {
217            fn numtoa(mut self, base: $t, string: &mut [u8]) -> usize {
218                if cfg!(debug_assertions) {
219                    if base == 10 {
220                        match size_of::<$t>() {
221                            2 => debug_assert!(string.len() >= 6,  "i16 base 10 conversions require at least 6 bytes"),
222                            4 => debug_assert!(string.len() >= 11, "i32 base 10 conversions require at least 11 bytes"),
223                            8 => debug_assert!(string.len() >= 20, "i64 base 10 conversions require at least 20 bytes"),
224                            _ => unreachable!()
225                        }
226                    }
227                }
228
229                let mut index = string.len() - 1;
230                let mut is_negative = false;
231
232                if self < 0 {
233                    is_negative = true;
234                    self = match self.checked_abs() {
235                        Some(value) => value,
236                        None        => {
237                            let value = <$t>::max_value();
238                            string[index] = LOOKUP[((value % base + 1) % base) as usize];
239                            index -= 1;
240                            value / base + ((value % base == base - 1) as $t)
241                        }
242                    };
243                } else if self == 0 {
244                    string[index] = b'0';
245                    return index;
246                }
247
248                if base == 10 {
249                    // Convert using optimized base 10 algorithm
250                    base_10!(self, index, string);
251                } else {
252                    while self != 0 {
253                        let rem = self % base;
254                        string[index] = LOOKUP[rem as usize];
255                        index = index.wrapping_sub(1);
256                        self /= base;
257                    }
258                }
259
260                if is_negative {
261                    string[index] = b'-';
262                    index = index.wrapping_sub(1);
263                }
264
265                index.wrapping_add(1)
266            }
267
268            #[cfg(feature = "std")]
269            fn numtoa_str(self, base: $t, buf: &mut [u8; 20]) -> &str {
270                let s = self.numtoa(base, buf);
271                unsafe { str::from_utf8_unchecked(&buf[s..]) }
272            }
273        }
274    }
275}
276
277impl_sized_numtoa_for!(i16);
278impl_sized_numtoa_for!(i32);
279impl_sized_numtoa_for!(i64);
280impl_sized_numtoa_for!(isize);
281impl_unsized_numtoa_for!(u16);
282impl_unsized_numtoa_for!(u32);
283impl_unsized_numtoa_for!(u64);
284impl_unsized_numtoa_for!(usize);
285
286impl NumToA<i8> for i8 {
287    fn numtoa(mut self, base: i8, string: &mut [u8]) -> usize {
288        if cfg!(debug_assertions) {
289            if base == 10 {
290                debug_assert!(string.len() >= 4, "i8 conversions need at least 4 bytes");
291            }
292        }
293
294        let mut index = string.len() - 1;
295        let mut is_negative = false;
296
297        if self < 0 {
298            is_negative = true;
299            self = match self.checked_abs() {
300                Some(value) => value,
301                None        => {
302                    let value = <i8>::max_value();
303                    string[index] = LOOKUP[((value % base + 1) % base) as usize];
304                    index -= 1;
305                    value / base + ((value % base == base - 1) as i8)
306                }
307            };
308        } else if self == 0 {
309            string[index] = b'0';
310            return index;
311        }
312
313        if base == 10 {
314            if self > 99 {
315                let section = (self / 10) * 2;
316                string[index-2..index].copy_from_slice(&DEC_LOOKUP[section as usize..section as usize+2]);
317                string[index] = LOOKUP[(self % 10) as usize];
318                index = index.wrapping_sub(3);
319            } else if self > 9 {
320                self *= 2;
321                string[index-1..index+1].copy_from_slice(&DEC_LOOKUP[self as usize..self as usize+2]);
322                index = index.wrapping_sub(2);
323            } else {
324                string[index] = LOOKUP[self as usize];
325                index = index.wrapping_sub(1);
326            }
327        } else {
328            while self != 0 {
329                let rem = self % base;
330                string[index] = LOOKUP[rem as usize];
331                index = index.wrapping_sub(1);
332                self /= base;
333            }
334        }
335
336        if is_negative {
337            string[index] = b'-';
338            index = index.wrapping_sub(1);
339        }
340
341        index.wrapping_add(1)
342    }
343
344    #[cfg(feature = "std")]
345    fn numtoa_str(self, base: Self, buf: &mut [u8; 20]) -> &str {
346        let s = self.numtoa(base, buf);
347        unsafe { str::from_utf8_unchecked(&buf[s..]) }
348    }
349}
350
351impl NumToA<u8> for u8 {
352    fn numtoa(mut self, base: u8, string: &mut [u8]) -> usize {
353        if cfg!(debug_assertions) {
354            if base == 10 {
355                debug_assert!(string.len() >= 3, "u8 conversions need at least 3 bytes");
356            }
357        }
358
359        let mut index = string.len() - 1;
360        if self == 0 {
361            string[index] = b'0';
362            return index;
363        }
364
365        if base == 10 {
366            if self > 99 {
367                let section = (self / 10) * 2;
368                string[index-2..index].copy_from_slice(&DEC_LOOKUP[section as usize..section as usize+2]);
369                string[index] = LOOKUP[(self % 10) as usize];
370                index = index.wrapping_sub(3);
371            } else if self > 9 {
372                self *= 2;
373                string[index-1..index+1].copy_from_slice(&DEC_LOOKUP[self as usize..self as usize+2]);
374                index = index.wrapping_sub(2);
375            } else {
376                string[index] = LOOKUP[self as usize];
377                index = index.wrapping_sub(1);
378            }
379        } else {
380            while self != 0 {
381                let rem = self % base;
382                string[index] = LOOKUP[rem as usize];
383                index = index.wrapping_sub(1);
384                self /= base;
385            }
386        }
387
388        index.wrapping_add(1)
389    }
390
391    #[cfg(feature = "std")]
392    fn numtoa_str(self, base: Self, buf: &mut [u8; 20]) -> &str {
393        let s = self.numtoa(base, buf);
394        unsafe { str::from_utf8_unchecked(&buf[s..]) }
395    }
396}
397
398#[test]
399fn str_convenience() {
400    let mut buffer = [0u8; 20];
401    assert_eq!("256123", 256123.numtoa_str(10, &mut buffer));
402}
403
404#[test]
405#[should_panic]
406fn base10_u8_array_too_small() {
407    let mut buffer = [0u8; 2];
408    let _ = 0u8.numtoa(10, &mut buffer);
409}
410
411#[test]
412fn base10_u8_array_just_right() {
413    let mut buffer = [0u8; 3];
414    let _ = 0u8.numtoa(10, &mut buffer);
415}
416
417#[test]
418#[should_panic]
419fn base10_i8_array_too_small() {
420    let mut buffer = [0u8; 3];
421    let _ = 0i8.numtoa(10, &mut buffer);
422}
423
424#[test]
425fn base10_i8_array_just_right() {
426    let mut buffer = [0u8; 4];
427    let i = (-127i8).numtoa(10, &mut buffer);
428    assert_eq!(&buffer[i..], b"-127");
429}
430
431#[test]
432#[should_panic]
433fn base10_i16_array_too_small() {
434    let mut buffer = [0u8; 5];
435    let _ = 0i16.numtoa(10, &mut buffer);
436}
437
438#[test]
439fn base10_i16_array_just_right() {
440    let mut buffer = [0u8; 6];
441    let i = (-12768i16).numtoa(10, &mut buffer);
442    assert_eq!(&buffer[i..], b"-12768");
443}
444
445#[test]
446#[should_panic]
447fn base10_u16_array_too_small() {
448    let mut buffer = [0u8; 4];
449    let _ = 0u16.numtoa(10, &mut buffer);
450}
451
452#[test]
453fn base10_u16_array_just_right() {
454    let mut buffer = [0u8; 5];
455    let _ = 0u16.numtoa(10, &mut buffer);
456}
457
458#[test]
459#[should_panic]
460fn base10_i32_array_too_small() {
461    let mut buffer = [0u8; 10];
462    let _ = 0i32.numtoa(10, &mut buffer);
463}
464
465#[test]
466fn base10_i32_array_just_right() {
467    let mut buffer = [0u8; 11];
468    let _ = 0i32.numtoa(10, &mut buffer);
469}
470
471#[test]
472#[should_panic]
473fn base10_u32_array_too_small() {
474    let mut buffer = [0u8; 9];
475    let _ = 0u32.numtoa(10, &mut buffer);
476}
477
478#[test]
479fn base10_u32_array_just_right() {
480    let mut buffer = [0u8; 10];
481    let _ = 0u32.numtoa(10, &mut buffer);
482}
483
484#[test]
485#[should_panic]
486fn base10_i64_array_too_small() {
487    let mut buffer = [0u8; 19];
488    let _ = 0i64.numtoa(10, &mut buffer);
489}
490
491#[test]
492fn base10_i64_array_just_right() {
493    let mut buffer = [0u8; 20];
494    let _ = 0i64.numtoa(10, &mut buffer);
495}
496
497#[test]
498#[should_panic]
499fn base10_u64_array_too_small() {
500    let mut buffer = [0u8; 19];
501    let _ = 0u64.numtoa(10, &mut buffer);
502}
503
504#[test]
505fn base10_u64_array_just_right() {
506    let mut buffer = [0u8; 20];
507    let _ = 0u64.numtoa(10, &mut buffer);
508}
509
510#[test]
511fn base8_min_signed_number() {
512    let mut buffer = [0u8; 30];
513    let i = (-128i8).numtoa(8, &mut buffer);
514    assert_eq!(&buffer[i..], b"-200");
515
516    let i = (-32768i16).numtoa(8, &mut buffer);
517    assert_eq!(&buffer[i..], b"-100000");
518
519    let i = (-2147483648i32).numtoa(8, &mut buffer);
520    assert_eq!(&buffer[i..], b"-20000000000");
521
522    let i = (-9223372036854775808i64).numtoa(8, &mut buffer);
523    assert_eq!(&buffer[i..], b"-1000000000000000000000");
524}
525
526#[test]
527fn base16_min_signed_number() {
528    let mut buffer = [0u8; 20];
529    let i = (-128i8).numtoa(16, &mut buffer);
530    assert_eq!(&buffer[i..], b"-80");
531
532    let i = (-32768i16).numtoa(16, &mut buffer);
533    assert_eq!(&buffer[i..], b"-8000");
534
535    let i = (-2147483648i32).numtoa(16, &mut buffer);
536    assert_eq!(&buffer[i..], b"-80000000");
537
538    let i = (-9223372036854775808i64).numtoa(16, &mut buffer);
539    assert_eq!(&buffer[i..], b"-8000000000000000");
540}