I had some spare time and wanted to play around a little with Rust, so, I created the following comparison on the Playground in case anyone is interested: Rust Playground.
I think it is notable, that even using the format! macro on the u32 -> String works pretty well timing-wise (avg 560ns or so). It seems that the solution using modulo/division, even with the reverse at the end, performs slightly better on average, but, it isn't a clear win. The version where the conversion from u32 -> String isn't included in the timing for creating it from the string digits, performs the best. Interestingly, the version of using modulo/division is clearly out-performed if pre-allocation isn't done, so, that is definitely important to it being efficient.
EDIT: I dug around a bit because I was wondering if it was possible to have format! format to a stream of bytes as an iterator rather than allocating a buffer and what that might look like. I couldn't see how, with the API for fmt as it is to accomplish that without a lot of work. In this case, I don't think it would make things faster, but, I wonder if giving the 'fmt' family of macros the ability to format to an iterator in a demand-pull fashion could be of use (not necessarily for this particular problem though)?
use std::time::{Instant};
pub trait Digits {
fn digits( self ) -> Box<[u8]>;
fn digits_bymodulo( self, prealloc : bool ) -> Vec<u32> ;
}
impl Digits for u32 {
fn digits( self ) -> Box<[u8]> {
let y = format!("{:#}", self);
let mut v = y.into_bytes().into_boxed_slice();
v.iter_mut().for_each( |b| *b -= 48 );
v
}
fn digits_bymodulo( self, prealloc : bool ) -> Vec<u32> {
let mut input = self;
let mut buf = if prealloc {
let n = (input as f32).log10() as usize + 1;
Vec::with_capacity(n)
} else {
Vec::new()
};
while input != 0 {
buf.push(input % 10);
input /= 10;
}
buf.reverse();
buf
}
}
impl Digits for String {
fn digits( self ) -> Box<[u8]> {
let mut v = self.into_bytes().into_boxed_slice();
v.iter_mut().for_each( |b| *b -= 48 );
v
}
fn digits_bymodulo( self, prealloc : bool ) -> Vec<u32> {
let _x = prealloc;
unimplemented!();
}
}
fn main() -> () {
let x: u32 = 1234567;
let ( mut min, mut max, mut total ) = ( 0u32, 0u32, 0u32 );
println!( "variant min max avg" );
for _ in 0..1000 {
let s = String::from("1234567");
let t1 = Instant::now();
let _v = s.digits();
let t1 = t1.elapsed().subsec_nanos();
min = if min == 0 || t1 < min { t1 } else { min };
max = if t1 > max { t1 } else { max };
total += t1;
}
println!( "string {:6} {:6} {:6}", min, max, total / 1000u32 );
let ( mut min, mut max, mut total ) = ( 0u32, 0u32, 0u32 );
for _ in 0..1000 {
let t1 = Instant::now();
let _v = x.digits();
let t1 = t1.elapsed().subsec_nanos();
min = if min == 0 || t1 < min { t1 } else { min };
max = if t1 > max { t1 } else { max };
total += t1;
}
println!( "digits {:6} {:6} {:6}", min, max, total / 1000u32 );
let ( mut min, mut max, mut total ) = ( 0u32, 0u32, 0u32 );
for _ in 0..1000 {
let t1 = Instant::now();
let _v = x.digits_bymodulo(true);
let t1 = t1.elapsed().subsec_nanos();
min = if min == 0 || t1 < min { t1 } else { min };
max = if t1 > max { t1 } else { max };
total += t1;
}
println!( "%-prealloc {:6} {:6} {:6}", min, max, total / 1000u32 );
let ( mut min, mut max, mut total ) = ( 0u32, 0u32, 0u32 );
for _ in 0..1000 {
let t1 = Instant::now();
let _v = x.digits_bymodulo(false);
let t1 = t1.elapsed().subsec_nanos();
min = if min == 0 || t1 < min { t1 } else { min };
max = if t1 > max { t1 } else { max };
total += t1;
}
println!( "%-notprealloc {:6} {:6} {:6}", min, max, total / 1000u32 );
}