Skip to main content

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