开发者

What is the proper way of implementing a good "itoa()" function?

I was wondering if my implementation of an "itoa开发者_运维百科" function is correct. Maybe you can help me getting it a bit more "correct", I'm pretty sure I'm missing something. (Maybe there is already a library doing the conversion the way I want it to do, but... couldn't find any)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

char * itoa(int i) {
  char * res = malloc(8*sizeof(int));
  sprintf(res, "%d", i);
  return res;
}

int main(int argc, char *argv[]) {
 ...


// Yet, another good itoa implementation
// returns: the length of the number string
int itoa(int value, char *sp, int radix)
{
    char tmp[16];// be careful with the length of the buffer
    char *tp = tmp;
    int i;
    unsigned v;

    int sign = (radix == 10 && value < 0);    
    if (sign)
        v = -value;
    else
        v = (unsigned)value;

    while (v || tp == tmp)
    {
        i = v % radix;
        v /= radix;
        if (i < 10)
          *tp++ = i+'0';
        else
          *tp++ = i + 'a' - 10;
    }

    int len = tp - tmp;

    if (sign) 
    {
        *sp++ = '-';
        len++;
    }

    while (tp > tmp)
        *sp++ = *--tp;

    return len;
}

// Usage Example:
char int_str[15]; // be careful with the length of the buffer
int n = 56789;
int len = itoa(n,int_str,10);


The only actual error is that you don't check the return value of malloc for null.

The name itoa is kind of already taken for a function that's non-standard, but not that uncommon. It doesn't allocate memory, rather it writes to a buffer provided by the caller:

char *itoa(int value, char * str, int base);

If you don't want to rely on your platform having that, I would still advise following the pattern. String-handling functions which return newly allocated memory in C are generally more trouble than they're worth in the long run, because most of the time you end up doing further manipulation, and so you have to free lots of intermediate results. For example, compare:

void delete_temp_files() {
    char filename[20];
    strcpy(filename, "tmp_");
    char *endptr = filename + strlen(filename);
    for (int i = 0; i < 10; ++i) {
        itoa(endptr, i, 10); // itoa doesn't allocate memory
        unlink(filename);
    }
}

vs.

void delete_temp_files() {
    char filename[20];
    strcpy(filename, "tmp_");
    char *endptr = filename + strlen(filename);
    for (int i = 0; i < 10; ++i) {
        char *number = itoa(i, 10); // itoa allocates memory
        strcpy(endptr, number);
        free(number);
        unlink(filename);
    }
}

If you had reason to be especially concerned about performance (for instance if you're implementing a stdlib-style library including itoa), or if you were implementing bases that sprintf doesn't support, then you might consider not calling sprintf. But if you want a base 10 string, then your first instinct was right. There's absolutely nothing "incorrect" about the %d format specifier.

Here's a possible implementation of itoa, for base 10 only:

char *itobase10(char *buf, int value) {
    sprintf(buf, "%d", value);
    return buf;
}

Here's one which incorporates the snprintf-style approach to buffer lengths:

int itobase10n(char *buf, size_t sz, int value) {
    return snprintf(buf, sz, "%d", value);
}


A good int to string or itoa() has these properties;

  • Works for all [INT_MIN...INT_MAX], base [2...36] without buffer overflow.
  • Does not assume int size.
  • Does not require 2's complement.
  • Does not require unsigned to have a greater positive range than int. In other words, does not use unsigned.
  • Allows use of '-' for negative numbers, even when base != 10.

Tailor the error handling as needed. (needs C99 or later):

char* itostr(char *dest, size_t size, int a, int base) {
  // Max text needs occur with itostr(dest, size, INT_MIN, 2)
  char buffer[sizeof a * CHAR_BIT + 1 + 1]; 
  static const char digits[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  if (base < 2 || base > 36) {
    fprintf(stderr, "Invalid base");
    return NULL;
  }

  // Start filling from the end
  char* p = &buffer[sizeof buffer - 1];
  *p = '\0';

  // Work with negative `int`
  int an = a < 0 ? a : -a;  

  do {
    *(--p) = digits[-(an % base)];
    an /= base;
  } while (an);

  if (a < 0) {
    *(--p) = '-';
  }

  size_t size_used = &buffer[sizeof(buffer)] - p;
  if (size_used > size) {
    fprintf(stderr, "Scant buffer %zu > %zu", size_used , size);
    return NULL;
  }
  return memcpy(dest, p, size_used);
}


I think you are allocating perhaps too much memory. malloc(8*sizeof(int)) will give you 32 bytes on most machines, which is probably excessive for a text representation of an int.


I'm not quite sure where you get 8*sizeof(int) as the maximum possible number of characters -- ceil(8 / (log(10) / log(2))) yields a multiplier of 3*. Additionally, under C99 and some older POSIX platforms you can create an accurately-allocating version with sprintf():

char *
itoa(int i) 
{
    int n = snprintf(NULL, 0, "%d", i) + 1;
    char *s = malloc(n);

    if (s != NULL)
        snprintf(s, n, "%d", i);
    return s;
}

HTH


i found an interesting resource dealing with several different issues with the itoa implementation
you might wanna look it up too
itoa() implementations with performance tests


You should use a function in the printf family for this purpose. If you'll be writing the result to stdout or a file, use printf/fprintf. Otherwise, use snprintf with a buffer big enough to hold 3*sizeof(type)+2 bytes or more.


sprintf is quite slow, if performance matters it is probably not the best solution.

if the base argument is a power of 2 the conversion can be done with a shift and masking, and one can avoid reversing the string by recording the digits from the highest positions. For instance, something like this for base=16

int  num_iter = sizeof(int) / 4;

const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

/* skip zeros in the highest positions */
int i = num_iter;
for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    if ( digit > 0 )  break;
}

for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    result[len++] = digits[digit];
}

For decimals there is a nice idea to use a static array big enough to record the numbers in the reversed order, see here


  • Integer-to-ASCII needs to convert data from a standard integer type into an ASCII string.
  • All operations need to be performed using pointer arithmetic, not array indexing.
  • The number you wish to convert is passed in as a signed 32-bit integer.
  • You should be able to support bases 2 to 16 by specifying the integer value of the base you wish to convert to (base).
  • Copy the converted character string to the uint8_t* pointer passed in as a parameter (ptr).
  • The signed 32-bit number will have a maximum string size (Hint: Think base 2).
  • You must place a null terminator at the end of the converted c-string Function should return the length of the converted data (including a negative sign).
  • Example my_itoa(ptr, 1234, 10) should return an ASCII string length of 5 (including the null terminator).
  • This function needs to handle signed data.
  • You may not use any string functions or libraries.

.

uint8_t my_itoa(int32_t data, uint8_t *ptr, uint32_t base){
        uint8_t cnt=0,sgnd=0;
        uint8_t *tmp=calloc(32,sizeof(*tmp));
        if(!tmp){exit(1);}
        else{
            for(int i=0;i<32;i++){
            if(data<0){data=-data;sgnd=1;}
            if(data!=0){
               if(data%base<10){
                *(tmp+i)=(data%base)+48;
                data/=base;
               }
               else{
                *(tmp+i)=(data%base)+55;
                data/=base;
               }
            cnt++;     
            }
           }
        if(sgnd){*(tmp+cnt)=45;++cnt;}
        }
     my_reverse(tmp, cnt);
     my_memcopy(tmp,ptr,cnt);
     return ++cnt;
}
  • ASCII-to-Integer needs to convert data back from an ASCII represented string into an integer type.
  • All operations need to be performed using pointer arithmetic, not array indexing
  • The character string to convert is passed in as a uint8_t * pointer (ptr).
  • The number of digits in your character set is passed in as a uint8_t integer (digits).
  • You should be able to support bases 2 to 16.
  • The converted 32-bit signed integer should be returned.
  • This function needs to handle signed data.
  • You may not use any string functions or libraries.

.

int32_t my_atoi(uint8_t *ptr, uint8_t digits, uint32_t base){
    int32_t sgnd=0, rslt=0;
    for(int i=0; i<digits; i++){
        if(*(ptr)=='-'){*ptr='0';sgnd=1;}
        else if(*(ptr+i)>'9'){rslt+=(*(ptr+i)-'7');}
        else{rslt+=(*(ptr+i)-'0');}
        if(!*(ptr+i+1)){break;}
        rslt*=base;
    }
    if(sgnd){rslt=-rslt;}
    return rslt;
}


I don't know about good, but this is my implementation that I did while learning C

static int  ft_getintlen(int value)
{
    int l;
    int neg;

    l = 1;
    neg = 1;
    if (value < 0)
    {
        value *= -1;
        neg = -1;
    }
    while (value > 9)
    {
        l++;
        value /= 10;
    }
    if (neg == -1)
    {
        return (l + 1);
    }
    return (l);
}

static int  ft_isneg(int n)
{
    if (n < 0)
        return (-1);
    return (1);
}

static char *ft_strcpy(char *dest, const char *src)
{
    unsigned int    i;

    i = 0;
    while (src[i] != '\0')
    {
        dest[i] = src[i];
        i++;
    }
    dest[i] = src[i];
    return (dest);
}

char    *ft_itoa(int n)
{
    size_t  len;
    char    *instr;
    int     neg;

    neg = ft_isneg(n);
    len = ft_getintlen(n);
    instr = (char *)malloc((sizeof(char) * len) + 1);
    if (n == -2147483648)
        return (ft_strcpy(instr, "-2147483648"));
    if (!instr)
        return (NULL);
    if (neg == -1)
        n *= -1;
    instr[len--] = 0;
    if (n == 0)
        instr[len--] = 48;
    while (n)
    {
        instr[len--] = ((n % 10) + 48);
        n /= 10;
    }
    if (neg == -1)
        instr[len] = '-';
    return (instr);
}


This should work:

#include <string.h>
#include <stdlib.h>
#include <math.h>

char * itoa_alloc(int x) {
   int s = x<=0 ? 1 ? 0; // either space for a - or for a 0
   size_t len = (size_t) ceil( log10( abs(x) ) );
   char * str = malloc(len+s + 1);

   sprintf(str, "%i", x);

   return str;
}

If you don't want to have to use the math/floating point functions (and have to link in the math libraries) you should be able to find non-floating point versions of log10 by searching the Web and do:

size_t len = my_log10( abs(x) ) + 1;

That might give you 1 more byte than you needed, but you'd have enough.


There a couple of suggestions I might make. You can use a static buffer and strdup to avoid repeatedly allocating too much memory on subsequent calls. I would also add some error checking.

char *itoa(int i)
{
  static char buffer[12];

  if (snprintf(buffer, sizeof(buffer), "%d", i) < 0)
    return NULL;

  return strdup(buffer);
}

If this will be called in a multithreaded environment, remove "static" from the buffer declaration.


main()
{
  int i=1234;
  char stmp[10];
#if _MSC_VER
  puts(_itoa(i,stmp,10));
#else
  puts((sprintf(stmp,"%d",i),stmp));
#endif
  return 0;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜