How to convert a string to integer in C?
Asked Answered
R

13

341

I am trying to find out if there is an alternative way of converting string to integer in C.

I regularly pattern the following in my code.

char s[] = "45";

int num = atoi(s);

So, is there a better way or another way?

Ravenna answered 11/8, 2011 at 6:30 Comment(2)
programmingsimplified.com/c/source-code/…Phonolite
It works, but it's not the recommended way, because there is no way to handle errors. Never use this in production code unless you can trust the input 100%.Permanganate
A
242

There is strtol which is better IMO. Also I have taken a liking in strtonum, so use it if you have it (but remember it's not portable):

long long
     strtonum(const char *nptr, long long minval, long long maxval,
     const char **errstr);

You might also be interested in strtoumax and strtoimax which are standard functions in C99. For example you could say:

uintmax_t num = strtoumax(s, NULL, 10);
if (num == UINTMAX_MAX && errno == ERANGE)
    /* Could not convert. */

Anyway, stay away from atoi:

The call atoi(str) shall be equivalent to:

(int) strtol(str, (char **)NULL, 10)

except that the handling of errors may differ. If the value cannot be represented, the behavior is undefined.

Ariel answered 11/8, 2011 at 6:32 Comment(9)
what do I need to include for strtonum? I keep getting an implicit declaration warningCatch
@trideceth12 On systems where it's available it should be declared in #<stdlib.h>. However, you could use the standard strtoumax alternative.Ariel
Hmm, can't seem to get a reference to it - I'm using libc 2.15. At first I thought it was because I was using the -ANSI flag, but then even dropping that flag I couldn't get it.Catch
This answer doesn't seem shorter than the questioner's first code.Bleeding
@NoniA. Conciseness is always good, but not at the expense of correctness.Ariel
Not so much wrong as unsafe. atoi() works if input is valid. But what if you do atoi("cat")? strtol() has defined behavior if the value cannot be represented as a long, atoi() does not.Everyone
@Ariel why checking the result strtoumax against UINTMAX_MAX when errno is always set != 0 when unable to convert so just checking errno will tell the you it could not convert.Desorb
Re "which is better IMO" - why is it better?Valuate
The question refers to C, not C++. This answer is useful, but misleadingTomtoma
V
43

Robust C89 strtol-based solution

With:

  • no undefined behavior (as could be had with the atoi family)
  • a stricter definition of integer than strtol (e.g. no leading whitespace nor trailing trash chars)
  • classification of the error case (e.g. to give useful error messages to users)
  • a "testsuite"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

typedef enum {
    STR2INT_SUCCESS,
    STR2INT_OVERFLOW,
    STR2INT_UNDERFLOW,
    STR2INT_INCONVERTIBLE
} str2int_errno;

/* Convert string s to int out.
 *
 * @param[out] out The converted int. Cannot be NULL.
 *
 * @param[in] s Input string to be converted.
 *
 *     The format is the same as strtol,
 *     except that the following are inconvertible:
 *
 *     - empty string
 *     - leading whitespace
 *     - any trailing characters that are not part of the number
 *
 *     Cannot be NULL.
 *
 * @param[in] base Base to interpret string in. Same range as strtol (2 to 36).
 *
 * @return Indicates if the operation succeeded, or why it failed.
 */
str2int_errno str2int(int *out, char *s, int base) {
    char *end;
    if (s[0] == '\0' || isspace(s[0]))
        return STR2INT_INCONVERTIBLE;
    errno = 0;
    long l = strtol(s, &end, base);
    /* Both checks are needed because INT_MAX == LONG_MAX is possible. */
    if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX))
        return STR2INT_OVERFLOW;
    if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN))
        return STR2INT_UNDERFLOW;
    if (*end != '\0')
        return STR2INT_INCONVERTIBLE;
    *out = l;
    return STR2INT_SUCCESS;
}

int main(void) {
    int i;
    /* Lazy to calculate this size properly. */
    char s[256];

    /* Simple case. */
    assert(str2int(&i, "11", 10) == STR2INT_SUCCESS);
    assert(i == 11);

    /* Negative number . */
    assert(str2int(&i, "-11", 10) == STR2INT_SUCCESS);
    assert(i == -11);

    /* Different base. */
    assert(str2int(&i, "11", 16) == STR2INT_SUCCESS);
    assert(i == 17);

    /* 0 */
    assert(str2int(&i, "0", 10) == STR2INT_SUCCESS);
    assert(i == 0);

    /* INT_MAX. */
    sprintf(s, "%d", INT_MAX);
    assert(str2int(&i, s, 10) == STR2INT_SUCCESS);
    assert(i == INT_MAX);

    /* INT_MIN. */
    sprintf(s, "%d", INT_MIN);
    assert(str2int(&i, s, 10) == STR2INT_SUCCESS);
    assert(i == INT_MIN);

    /* Leading and trailing space. */
    assert(str2int(&i, " 1", 10) == STR2INT_INCONVERTIBLE);
    assert(str2int(&i, "1 ", 10) == STR2INT_INCONVERTIBLE);

    /* Trash characters. */
    assert(str2int(&i, "a10", 10) == STR2INT_INCONVERTIBLE);
    assert(str2int(&i, "10a", 10) == STR2INT_INCONVERTIBLE);

    /* int overflow.
     *
     * `if` needed to avoid undefined behaviour
     * on `INT_MAX + 1` if INT_MAX == LONG_MAX.
     */
    if (INT_MAX < LONG_MAX) {
        sprintf(s, "%ld", (long int)INT_MAX + 1L);
        assert(str2int(&i, s, 10) == STR2INT_OVERFLOW);
    }

    /* int underflow */
    if (LONG_MIN < INT_MIN) {
        sprintf(s, "%ld", (long int)INT_MIN - 1L);
        assert(str2int(&i, s, 10) == STR2INT_UNDERFLOW);
    }

    /* long overflow */
    sprintf(s, "%ld0", LONG_MAX);
    assert(str2int(&i, s, 10) == STR2INT_OVERFLOW);

    /* long underflow */
    sprintf(s, "%ld0", LONG_MIN);
    assert(str2int(&i, s, 10) == STR2INT_UNDERFLOW);

    return EXIT_SUCCESS;
}

GitHub upstream.

Based on: https://mcmap.net/q/48603/-how-to-parse-a-string-to-an-int-in-c

Vieva answered 16/10, 2012 at 21:46 Comment(7)
Nice robust str2int(). Pedantic: use isspace((unsigned char) s[0]).Dearborn
@chux thanks! Can you explain a bit more why the (unsigned char) cast could make a difference?Vieva
IAR C compiler warns that l > INT_MAX and l < INT_MIN are pointless integer comparison since either result is always false. What happens if I change them to l >= INT_MAX and l <= INT_MIN to clear the warnings? On ARM C, long and int are 32-bit signed Basic data types in ARM C and C++Ourselves
@Ourselves changing code to l >= INT_MAX incurs incorrect functionality: Example returning STR2INT_OVERFLOW with input "32767" and 16-bit int. Use a conditional compile. Example.Dearborn
if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW; would be better as if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;} to allow calling code to use errno on int out-of-range. Same for if (l < INT_MIN....Dearborn
I find not setting *out on error returns less useful than setting it like strtol() returns. Yet that is a design critique, not a problem with this implementation.Dearborn
@chux thanks for the suggestion. I think I'll keep the current one for simplicity, but maybe you are right that errno would be a more consistent interface with the rest of the stdlib error handling.Vieva
C
32

Don't use functions from ato... group. These are broken and virtually useless. A moderately better solution would be to use sscanf, although it is not perfect either.

To convert string to integer, functions from strto... group should be used. In your specific case it would be strtol function.

Chowder answered 11/8, 2011 at 6:34 Comment(3)
sscanf actually has undefined behavior if it tries to convert a number outside the range of its type (for example, sscanf("999999999999999999999", "%d", &n)).Watchdog
@Keith Thompson: That's exactly what I mean. atoi provides no meaningful success/failure feedback and has undefined behavior on overflow. sscanf provides success/failure feedback of sorts (the return value, which is what makes it "moderately better"), but still has undefined behavior on overflow. Only strtol is a viable solution.Chowder
Agreed; I just wanted to emphasize the potentially fatal problem with sscanf. (Though I confess I sometimes use atoi, usually for programs that I don't expect to survive more than 10 minute before I delete the source.)Watchdog
L
10

You can code atoi() for fun:

int my_getnbr(char *str)
{
  int result;
  int puiss;

  result = 0;
  puiss = 1;
  while (('-' == (*str)) || ((*str) == '+'))
  {
      if (*str == '-')
        puiss = puiss * -1;
      str++;
  }
  while ((*str >= '0') && (*str <= '9'))
  {
      result = (result * 10) + ((*str) - '0');
      str++;
  }
  return (result * puiss);
}

You can also make it recursive, which can fold in 3 lines.

Lathery answered 11/8, 2011 at 8:20 Comment(4)
Thanks so much .. But could you tell me how the below code works? code ((*str) - '0') codeRavenna
a character has an ascii value. If you are uner linux type: man ascii in the shell or if not go to:table-ascii.com. You will see that the character '0' = 68 (i think) for a int. So to get the number of '9' (it's '0' + 9) so you get 9 = '9' - '0'. You get it?Lathery
1) The code allows "----1" 2) Has undefined behavior with int overflow when the result should be INT_MIN. Consider my_getnbr("-2147483648")Dearborn
Thanks for precision, it was just for showing a little example. As it's said it for fun and learning. You should definetly use standart lib for this kind of tasks. Faster and safer!Lathery
G
6
int atoi(const char* str){
    int num = 0;
    int i = 0;
    bool isNegetive = false;
    if(str[i] == '-'){
        isNegetive = true;
        i++;
    }
    while (str[i] && (str[i] >= '0' && str[i] <= '9')){
        num = num * 10 + (str[i] - '0');
        i++;
    }
    if(isNegetive) num = -1 * num;
    return num;
}
Gratitude answered 13/2, 2020 at 5:6 Comment(1)
Has undefined behaviour on int overflow.Ryanryann
C
5

As already mentioned, the atoi family of functions should never be used in any C program, since they don't have any error handling.

The the strtol family of functions is 100% equivalent, but with extended functionality: it has error handling and it also supports other bases than decimal, such as hex or binary. Therefore the correct answer is: use strtol (family).

If you for some reason insist on rolling out this function yourself manually, you should try to do something similar to strtol in case there are other symbols present other than the optional sign and digits. It's quite common that we want to convert numbers that are part of larger string, for example.

A naive version with error handling support might look like the example below. This code is for decimal base 10 numbers only, but otherwise behaves like strtol with an optional pointer set to point at the first invalid symbol encountered (if any). Also note that this code doesn't handle overflows.

#include <ctype.h>

long my_strtol (char* restrict src, char** endptr)
{
  long result=0;
  long sign=1;

  if(endptr != NULL) 
  {
    /* if input is ok and endptr is provided, 
       it will point at the beginning of the string */
    *endptr = src;
  }

  if(*src=='-')
  {
    sign = -1;
    src++;
  }

  for(; *src!='\0'; src++)
  {
    if(!isdigit(*src)) // error handling
    {
      if(endptr != NULL)
      {
        *endptr = src;
      }
      break;
    }
    result = result*10 + *src - '0';
  }

  return result * sign;
}

To handle overflows, one can for example add code counting the characters and check that they never go past 10, assuming 32 bit long which can be max 2147483647, 10 digits.

Cooker answered 13/12, 2021 at 14:43 Comment(1)
(Arriving very late to the party but there's so much bad code posted here. If you roll this out manually, at least don't make something worse than atoi.)Cooker
R
3

Just wanted to share a solution for unsigned long aswell.

unsigned long ToUInt(char* str)
{
    unsigned long mult = 1;
    unsigned long re = 0;
    int len = strlen(str);
    for(int i = len -1 ; i >= 0 ; i--)
    {
        re = re + ((int)str[i] -48)*mult;
        mult = mult*10;
    }
    return re;
}
Russell answered 18/10, 2016 at 19:26 Comment(5)
Doesn't handle overflow. Also, the parameter should be const char *.Jez
Plus, what's that 48 mean? Are you assuming that's the value of '0' where the code will run? Please don't inflict such broad assumptions on the world!Appellee
@TobySpeight Yes I assume 48 represent '0' in the ascii table.Russell
Not all the world is ASCII - just use '0' like you should.Appellee
it is recommended to use the strtoul function instead.Stark
E
0

Ok, I had the same problem.I came up with this solution.It worked for me the best.I did try atoi() but didn't work well for me.So here is my solution:

void splitInput(int arr[], int sizeArr, char num[])
{
    for(int i = 0; i < sizeArr; i++)
        // We are subtracting 48 because the numbers in ASCII starts at 48.
        arr[i] = (int)num[i] - 48;
}
Egomania answered 5/9, 2015 at 15:36 Comment(1)
This converts to an array of digits, not to an integer, so it doesn't answer the question. Also there is never a reason to type out some magic number 48 in source. Type '0'.Cooker
B
-3

This function will help you

int strtoint_n(char* str, int n)
{
    int sign = 1;
    int place = 1;
    int ret = 0;

    int i;
    for (i = n-1; i >= 0; i--, place *= 10)
    {
        int c = str[i];
        switch (c)
        {
            case '-':
                if (i == 0) sign = -1;
                else return -1;
                break;
            default:
                if (c >= '0' && c <= '9')   ret += (c - '0') * place;
                else return -1;
        }
    }

    return sign * ret;
}

int strtoint(char* str)
{
    char* temp = str;
    int n = 0;
    while (*temp != '\0')
    {
        n++;
        temp++;
    }
    return strtoint_n(str, n);
}

Ref: http://amscata.blogspot.com/2013/09/strnumstr-version-2.html

Binge answered 12/9, 2013 at 11:31 Comment(2)
Why do this though? One of the biggest problems with atoi and friends is that if there's overflow, it's undefined behavior. Your function does not check for this. strtol and friends do.Sisley
Yup. Since C is not Python I hope that the people who use C language are aware of these kind of overflow errors. Everything has it's own limits.Binge
G
-3

You can always roll your own!

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

int my_atoi(const char* snum)
{
    int idx, strIdx = 0, accum = 0, numIsNeg = 0;
    const unsigned int NUMLEN = (int)strlen(snum);

    /* Check if negative number and flag it. */
    if(snum[0] == 0x2d)
        numIsNeg = 1;

    for(idx = NUMLEN - 1; idx >= 0; idx--)
    {
        /* Only process numbers from 0 through 9. */
        if(snum[strIdx] >= 0x30 && snum[strIdx] <= 0x39)
            accum += (snum[strIdx] - 0x30) * pow(10, idx);

        strIdx++;
    }

    /* Check flag to see if originally passed -ve number and convert result if so. */
    if(!numIsNeg)
        return accum;
    else
        return accum * -1;
}

int main()
{
    /* Tests... */
    printf("Returned number is: %d\n", my_atoi("34574"));
    printf("Returned number is: %d\n", my_atoi("-23"));

    return 0;
}

This will do what you want without clutter.

Gyro answered 3/1, 2015 at 0:42 Comment(11)
But... why? This doesn't check for overflow and simply ignores garbage values. There's no reason not to use the strto... family of functions. They are portable and significantly better.Sisley
Strange to use 0x2d, 0x30 instead of '-', '0'. Does not allow '+' sign. Why (int) cast in (int)strlen(snum)? UB if input is "". UB when result is INT_MIN due to int overflow with accum += (snum[strIdx] - 0x30) * pow(10, idx);Dearborn
@chux - This code is demonstration code. There are easy fixes to what you described as potential issues.Gyro
@Gyro What you describe as "demonstration code" will be used by others who have no clue about all the details. Only the negative score and the comments on this answer protect them now. In my opinion, "demonstration code" must have much higher quality.Jez
@RolandIllig Rather than being all critical, wouldn't it be more helpful to others to actually put up your own solution?Gyro
@Gyro There's no point in providing my own solution when the accepted answer to this question already covers everything. Still, really bad answers should get negative scores.Jez
@RolandIllig Of course. So you offer zero constructive feedback and completely ignore the fact that there is an "Add Another Answer" button right at the bottom of the page that would allow you to add your own answer still? I don't mind criticism, just be sure to actually offer a better solution if you are going to be critical.Gyro
So let's get constructive: just fix the things mentioned by chux, then I will rethink of taking back my downvote and comments.Jez
@RolandIllig - No thanks, you can keep the downvote going is you wish. I don't particularly agree with chux's recommendations, and here is why. Using hex codes makes intentions clear, I am sure you have run into 'O' (letter) vs. '0' (number) for some editors causing confusion, this way prevents that. The reason for the cast on the strlen() return value is to prevent warnings and to again make intentions clear. Lastly, what overflow? We are dealing with a very limited range of values here: 0-9, '-'. Maybe offer a scenario where there is overflow, please?Gyro
my_atoi("12345678901234567890")Jez
I would hope the user would know that it is Alpha-to-Int, not Alpha-to-Long-Long! That is why it is called 'my_atoi' and not 'my_atoll', I mean that is clear, right? You really are looking for stuff to be wrong aren't you?Gyro
B
-3
//I think this way we could go :
int my_atoi(const char* snum)
{
 int nInt(0);
 int index(0);
 while(snum[index])
 {
    if(!nInt)
        nInt= ( (int) snum[index]) - 48;
    else
    {
        nInt = (nInt *= 10) + ((int) snum[index] - 48);
    }
    index++;
 }
 return(nInt);
}

int main()
{
    printf("Returned number is: %d\n", my_atoi("676987"));
    return 0;
}
Broderick answered 6/11, 2015 at 13:5 Comment(2)
Code does not compile in C. Why nInt = (nInt *= 10) + ((int) snum[index] - 48); vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt) not needed.Dearborn
Voting to delete since it's a C++ answer and off-topic.Cooker
A
-5

In C++, you can use a such function:

template <typename T>
T to(const std::string & s)
{
    std::istringstream stm(s);
    T result;
    stm >> result;

    if(stm.tellg() != s.size())
        throw error;

    return result;
}

This can help you to convert any string to any type such as float, int, double...

Arrowworm answered 11/8, 2011 at 6:41 Comment(3)
There's already a similar question covering C++, where the problems with this approach are explained.Gleeful
Delete voting since this answer is off-topic.Cooker
OP specifically asked for C...Pentobarbital
B
-8

Yes, you can store the integer directly:

int num = 45;

If you must parse a string, atoi or strol is going to win the "shortest amount of code" contest.

Baur answered 11/8, 2011 at 6:32 Comment(2)
If you want to do it safely, strtol() actually requires a fair amount of code. It can return LONG_MIN or LONG_MAX either if that's the actual converted value or if there's an underflow or overflow, and it can return 0 either if that's the actual value or if there was no number to convert. You need to set errno = 0 before the call, and check the endptr.Watchdog
The solutions given to parse, are no viable solutions.Bevin

© 2022 - 2024 — McMap. All rights reserved.