2007年9月4日星期二

How do I use itoa() with GCC?

How do I use itoa() with GCC?

Arrgghh C/C++! It would appear that itoa() isn't ANSI C standard and doesn't work with GCC on Linux (at least the version I'm using). Things like this are frustrating especially if you want your code to work on different platforms (Windows/Linux/Solaris/whatever).

Many people say that you should just use sprintf to write to a character string but that doesn't allow for one of the features of itoa(); the ability to write in a base other than 10. This page contains a series of evolving versions of an itoa implementation. The oldest are near the top and the latest at the bottom. Please make sure that you use the latest version.

Credits

Before we go any further, I would like to say thanks to the people that contributed to the solutions below. This function has been put together by contributions from Stuart Lowe (that's me), Robert Jan Schaper, Ray-Yuan Sheu, Rodrigo de Salvo Braz, Wes Garland and John Maloney.

Development

Below is an early version described by Robert Jan Schaper on Google groups:

char* version 0.1

 

char* itoa(int val, int base){

static char buf[32] = {0};

int i = 30;

for(; val && i ; --i, val /= base)

buf[i] = "0123456789abcdef"[val % base];

return &buf[i+1];

}

This doesn't quite look like the implementation I am used to which is more like itoa(int value, char* buffer, int radix). In the end I have made my own version which uses a std::string instead of a character string.

std::string version 0.1

 

void my_itoa(int value, std::string& buf, int base){

int i = 30;

buf = "";

for(; value && i ; --i, value /= base) buf = "0123456789abcdef"[value % base] + buf;

}

Update: (2005/02/11)
Ray-Yuan Sheu sent me an email with an improved version which does a bit more error checking for things like a base that is out of range and for negative integers.

Update: (2005/04/08)
Rodrigo de Salvo Braz spotted a bug that meant nothing was returned when the input was zero. It now returns "0". This bug has also been spotted by Luc Gallant.

std::string version 0.2

 

/**

* C++ version std::string style "itoa":

*/

std::string itoa(int value, unsigned int base) {

const char digitMap[] = "0123456789abcdef";

std::string buf;



// Guard:

if (base == 0 || base > 16) {

// Error: may add more trace/log output here

return buf;

}



// Take care of negative int:

std::string sign;

int _value = value;



// Check for case when input is zero:

if (_value == 0) return "0";



if (value < 0) {

_value = -value;

sign = "-";

}



// Translating number to string with base:

for (int i = 30; _value && i ; --i) {

buf = digitMap[ _value % base ] + buf;

_value /= base;

}

return sign.append(buf);

}

Update: (2005/05/07)
Wes Garland says that lltostr exists under Solaris and several other unices. It should return a char * of a long long in multiple number bases. There is also ulltostr for unsigned values.

Update: (2005/05/30)
John Maloney has pointed out various problems with the previous implementation. One of the major issues was the amount of heap allocation going on. He suggested that a lot of this be removed to speed up the algorithm. Below are two versions based on his excellent suggestions. The char* version is at least 10 times faster than the code above. The new std::string version is 3 times faster than before. Although the char* version is faster, you should check that you have allocated enough space to hold the output.

std::string version 0.3

 

/**

* C++ version std::string style "itoa":

*/

std::string itoa(int value, int base) {



enum { kMaxDigits = 35 };

std::string buf;

buf.reserve( kMaxDigits ); // Pre-allocate enough space.



// check that the base if valid

if (base <> 16) return buf;



int quotient = value;



// Translating number to string with base:

do {

buf += "0123456789abcdef"[ std::abs( quotient % base ) ];

quotient /= base;

} while ( quotient );



// Append the negative sign for base 10

if ( value < 0 && base == 10) buf += '-';



std::reverse( buf.begin(), buf.end() );

return buf;

}

char* version 0.2

 

/**

* C++ version char* style "itoa":

*/

char* itoa( int value, char* result, int base ) {

// check that the base if valid

if (base <> 16) { *result = 0; return result; }



char* out = result;

int quotient = value;



do {

*out = "0123456789abcdef"[ std::abs( quotient % base ) ];

++out;

quotient /= base;

} while ( quotient );



// Only apply negative sign for base 10

if ( value < 0 && base == 10) *out++ = '-';



std::reverse( result, out );

*out = 0;

return result;

}

Update: (2006/10/15)
Luiz Gonçalves tells me that although not an ANSI standard, itoa comes in many packages and it is written in many textbooks. He suggests a version written in pure ANSI C based on a version from Kernighan & Ritchie's Ansi C. A base error is reported by the return of an empty string but the function does no checks of sizes and no allocations. This version is provided below along with a slightly modified version (architecture specific tweaks), the std::string version and the C++ char* itoa() version.

 

/**

* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C":

*/

void strreverse(char* begin, char* end) {

char aux;

while(end>begin)

aux=*end, *end--=*begin, *begin++=aux;

}

void itoa(int value, char* str, int base) {

static char num[] = "0123456789abcdefghijklmnopqrstuvwxyz";

char* wstr=str;

int sign;



// Validate base

if (base<2>35){ *wstr='\0'; return; }



// Take care of sign

if ((sign=value) < 0) value = -value;



// Conversion. Number is reversed.

do *wstr++ = num[value%base]; while(value/=base);

if(sign<0) *wstr++='-';

*wstr='\0';



// Reverse string

strreverse(str,wstr-1);

}
 

/**

* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C"

* with slight modification to optimize for specific architecture:

*/

void strreverse(char* begin, char* end) {

char aux;

while(end>begin)

aux=*end, *end--=*begin, *begin++=aux;

}

void itoa(int value, char* str, int base) {

static char num[] = "0123456789abcdefghijklmnopqrstuvwxyz";

char* wstr=str;

int sign;

div_t res;



// Validate base

if (base<2>35){ *wstr='\0'; return; }



// Take care of sign

if ((sign=value) < 0) value = -value;



// Conversion. Number is reversed.

do {

res = div(value,base);

*wstr++ = num[res.rem];

}while(value=res.quot);

if(sign<0) *wstr++='-';

*wstr='\0';



// Reverse string

strreverse(str,wstr-1);

}

Latest Versions

Below are the latest versions of the itoa function using either char* or std::string as you prefer. I haven't included the Kernighan & Ritchie based versions in this section because I'm not sure what the copyright status is for those. However, the functions below have been developed by the people mentioned on this page and are available for use.

std::string version 0.3



/**

* C++ version std::string style "itoa":

*/

std::string itoa(int value, int base) {



enum { kMaxDigits = 35 };

std::string buf;

buf.reserve( kMaxDigits ); // Pre-allocate enough space.



// check that the base if valid

if (base <> 16) return buf;



int quotient = value;



// Translating number to string with base:

do {

buf += "0123456789abcdef"[ std::abs( quotient % base ) ];

quotient /= base;

} while ( quotient );



// Append the negative sign for base 10

if ( value < 0 && base == 10) buf += '-';



std::reverse( buf.begin(), buf.end() );

return buf;

}

char* version 0.2



/**

* C++ version char* style "itoa":

*/

char* itoa( int value, char* result, int base ) {

// check that the base if valid

if (base <> 16) { *result = 0; return result; }



char* out = result;

int quotient = value;



do {

*out = "0123456789abcdef"[ std::abs( quotient % base ) ];

++out;

quotient /= base;

} while ( quotient );



// Only apply negative sign for base 10

if ( value < 0 && base == 10) *out++ = '-';



std::reverse( result, out );

*out = 0;

return result;

}

Performance Comparsion

I've done some testing of the three versions of itoa by finding the average time required to perform 2 million conversions in every base from base 2 to base 20. The summary is presented in the following table.

functionspeed
C++ version 0.2 char* style "itoa"
char* itoa(int value, char* result, int base)
baseline
Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C"
void itoa(int value, char* str, int base)
~15% faster
Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" with modification to optimize for specific architecture
void itoa(int value, char* str, int base)
~30% faster
(XP, cygwin, g++)
C++ version 0.3 std::string style "itoa"
std::string itoa(int value, int base)
~3400% slower


If anyone has any improvements or better solutions, please let me know. My email address can be worked out from information on my home page.

没有评论: