Search code examples
rust

How to convert an usize value to corresponding ascii bytes


I'd like to convert a usize value into the corresponding chars ascii bytes.

For example, for the value 50usize, I'd want to obtain [53, 48].

fn usize_to_ascii_bytes(input: usize) -> Vec<u8> {
    ...
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn usize_to_ascii_bytes_test() {
        assert_eq!(vec![53], usize_to_ascii_bytes(5));
        assert_eq!(vec![53, 48], usize_to_ascii_bytes(50));
    }
}

Context: I'm doing the Redis CodeCrafters course, and for a GET request, I need to write the length of the value to the output stream; value being a vec of u8. I know I could convert that value's .len() to a string then obtain u8 in ascii, but I'm looking for the most effective way of doing this, minimizing conversions and copies, in the most idiomatic way in Rust.


Solution

  • You can convert each digit one by one, starting from the last one, and then reverse the Vec:

    fn usize_to_ascii_bytes(mut input: usize) -> Vec<u8> {
        let mut vec = Vec::new();
        loop {
            vec.push(b'0' + (input % 10) as u8);
            input /= 10;
            if input == 0 {
                break;
            }
        }
        vec.reverse();
        vec
    }
    

    Playground

    Although I think a simple implementation should be just as fast since you have to allocate a Vec anyways:

    fn usize_to_ascii_bytes_simple(input: usize) -> Vec<u8> {
        input.to_string().into()
    }
    

    I benchmarked the two functions and the string one turned out to be consistently slightly faster for the inputs I tested, possibly because Rust's usize to string conversion uses a better algorithm than one digit at a time.

    use criterion::{black_box, criterion_group, criterion_main, Criterion};
    
    criterion_main!(benches);
    criterion_group!(benches, bench_main);
    
    fn usize_to_ascii_bytes(mut input: usize) -> Vec<u8> {
        let mut vec = Vec::new();
        loop {
            vec.push(b'0' + (input % 10) as u8);
            input /= 10;
            if input == 0 {
                break;
            }
        }
        vec.reverse();
        vec
    }
    
    fn usize_to_ascii_bytes_simple(input: usize) -> Vec<u8> {
        input.to_string().into()
    }
    
    fn bench_main(c: &mut Criterion) {
        let inputs = [0, 123, 456789, 101112131415];
    
        let mut group = c.benchmark_group("usize_to_ascii");
    
        group.bench_function("loop", |b| {
            b.iter(|| {
                for &input in &inputs {
                    black_box(usize_to_ascii_bytes(input));
                }
            })
        });
    
        group.bench_function("string", |b| {
            b.iter(|| {
                for &input in &inputs {
                    black_box(usize_to_ascii_bytes_simple(input));
                }
            })
        });
    }
    
    usize_to_ascii/loop     time:   [315.93 ns 321.52 ns 327.95 ns]
    usize_to_ascii/string   time:   [284.08 ns 288.74 ns 294.24 ns]