Test
This is a log entry about playing around the question. There are robust ways of converting integers to string already implemented out there, for example FastFormat. Good for them. Let's see how I played on my playground.
I measured the speed of sprintf()
, snprintf()
and my own implementation. I tried to make most of the decisions compile-time. I didn't care to write time measurement into my program, instead I'm using GNU's time
utility with three different versions of my program separated using macros.
#include <iostream>
#include <cstdio>
#include <limits>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#ifndef TEST_CASE
#define TEST_CASE 1
#endif
#ifndef TESTCOUNT
#define TESTCOUNT 100*1000*1000
#endif
#ifndef INTTYPE
#define INTTYPE uint64_t
#define INTFMT PRIu64
#endif
template<typename Integral>
char *myUIntToStr(Integral i, char *endPtr)
{
char *&cursor(endPtr);
if(!i)
{
*--cursor='0';
return cursor;
}
for(;i;i/=10)
{
*--cursor = char(i%10+'0');
}
return cursor;
}
int main()
{
const size_t BUFSIZE = std::numeric_limits<INTTYPE>::digits10 + 2;
char buffer[BUFSIZE];
buffer[BUFSIZE-1] = 0;
#if TEST_CASE == 1
for(INTTYPE i = 0; i<TESTCOUNT; ++i)
{
sprintf(buffer,"%" PRIu64,i);
}
#endif
#if TEST_CASE == 2
for(INTTYPE i = 0; i<TESTCOUNT; ++i)
{
snprintf(buffer,BUFSIZE,"%" PRIu64,i);
}
#endif
#if TEST_CASE == 3
char * const endPtr = buffer + BUFSIZE - 1;
char * output;
for(INTTYPE i = 0; i<TESTCOUNT; ++i)
{
output = myUIntToStr(i,endPtr);
}
#endif
#if TEST_CASE == 4
std::stringstream ss;
for(INTTYPE i = 0; i<TESTCOUNT; ++i)
{
ss << i;
ss.str(""); // clearing
}
#endif
return 0;
}
In many cases the arithmetic correctness of a program or interoperability of a binary format can be achieved by using fixed size integers like uint64_t
instead of the fluffy definition of short
, int
, long
, etc.. These fixed size integers are defined in the inttypes.h
header file of the C99 standard. I don't know any standard options for this purpose under C++03, so I chose to use this second best solution. I'm using this decision for a while and the time is finally on my side: The C++11 standard includes this header. The defining of __STDC_FORMAT_MACROS
before including this header results in the creation of printf()
formatters for those types (without the %
sign).
In theory this is a portable source code in practice (sic), in practice I haven't tried. This thing is only a proof of a concept.
The original idea for the conversion implementation came from abelenky at a stackoverflow.com question which was about the alternatives to itoa()
. I added that the buffer is allocated outside the function on stack (something like for free) and a pointer to its end passed into the function.
Environment
- OS: Debian (6.0.4 Squeezee) GNU/Linux (2.6.32-5-amd64)
- G++ 4.4.5
- CPU: Intel(R) Core(TM)2 CPU 6400 @ 2.13GHz
Results
$ g++ -W -Wall -pedantic -Wextra -O2 -DTEST_CASE=1 sprintf_speed.cpp -o sprintf_speed
$ time ./sprintf_speed
real 0m16.776s
user 0m16.757s
sys 0m0.012s
$ g++ -W -Wall -pedantic -Wextra -O2 -DTEST_CASE=2 sprintf_speed.cpp -o sprintf_speed
$ time ./sprintf_speed
real 0m16.951s
user 0m16.953s
sys 0m0.000s
$ g++ -W -Wall -pedantic -Wextra -O2 -DTEST_CASE=3 sprintf_speed.cpp -o sprintf_speed
$ time ./sprintf_speed
real 0m4.295s
user 0m4.296s
sys 0m0.000s
$ g++ -W -Wall -pedantic -Wextra -O2 -DTEST_CASE=4 sprintf_speed.cpp -o sprintf_speed
$ time ./sprintf_speed
real 0m15.443s
user 0m15.429s
sys 0m0.012s
The difference between the sprintf()
and snprintf()
version was around 2 to 4 percent consistently in favor of sprintf()
. It's unfair to compare these guys to my solution because they also have to parse their given formatter parameter. I believe that my solution is somewhat optimal. The fourth case with std::stringstream
is there for reference, that is the clean C++ style portable way for doing the conversion. Surprisingly fast.