Bullet Proof Integer Input Using strtol()

A very common question from newcomers to C and C++ programming is how to validate input. They use the standard library functions and objects like scanf() or cin which work well if the input is well-formed and within range, but fail if the input is invalid. This can be a real problem when the input is coming from a user at a keyboard and the user types something unexpected!

Consider the code below. It uses the standard library function strtol() which has several important features for bullet-proof conversion of text strings to numeric values.

The prototype is:

long strtol(const char *s, char **end_ptr, int base);

Here are its features:

  1. The first argument is a pointer to the text string to be converted to a numeric value. Use of the const keyword indicates that the function will not modify the contents of this string.
  2. The second argument is a pointer to a pointer to char. This allows the function to modify the pointer to char. If you do not wish to use this feature, you may pass NULL as the second parameter but you loose the availability of some of the error checking features.
  3. The third parameter is the number base, which must be between 2 and 36. This is very useful because it allows you to accept input in other formats besides decimal. Specifically if works like this:
    If the base argument is 10 the function expects to find a text representation of a number in ordinary decimal format.

    If the base argument is 0 the function examines the input string the same way the C compiler does when processing your source code. That is if the first two non-blank characters of the string are "0x" or "0X", it treats the rest of the string as hexadecimal and accepts the characters 'a' through 'f' and 'A' through 'F' as the corresponding hex digits. If the first non-blank character of the string is '0' and it is not followed by 'x' or 'X' but by more digits, the rest of the string is treated as octal.

    If the base argument is 2, strtol() will process a string of '0' and '1' characters as represent a binary string, which is another common question.

    If you specifically want hex or octal input, you can use a base argument of 16 or 8.

Here is how the function operates:

  1. It ignores leading white space in the string (spaces, tabs, newlines, etc.) until it reaches the first non-blank character.
  2. If the first non-blank character represents the start of a valid numeric string ('+', '-', or a digit) it processes all following characters up until the point where the next value it sees can't be part of the long integer value it is building.
    At this point, if the end_ptr parameter was not NULL, it sets the char pointer to point to the first character which isn't part of the number.

  3. If the string represents a number which is outside the range of a signed long (larger than LONG_MAX or smaller than LONG_MIN), the global variable errno will be set to ERANGE and either LONG_MAX or LONG_MIN will be returned, depending on the sign of the number.
  4. If the string doesn't represent a number and cannot be converted at all, 0 is returned and end_ptr is set equal to the pointer to the input string.
All of this means that if you pass a real end_ptr parameter to strtol() you can be absolutely sure of the results that you get.

Testing end_ptr for equality to the original string tells you for sure whether you got a value of 0 because the user entered 0, or because the entered string is not numerical.

Testing errno if you get back LONG_MAX or LONG_MIN tells you whether the number exceeds the range of a signed long int, or whether that exact value was entered.

If the value you are inputting is intended to be placed in an integer type which might be smaller than a long (int, short, or signed char), then you merely test the result against the limits for that type. The tests do nothing if the smaller type happens to have the same size and representation on a particular compiler.

A sample program is below. Note that this sample program does not check to see that there are no non-numeric characters after the valid numeric characters that can be converted into a value. In some cases, this is the type of response that you want. In other cases you might want to do additional checking.


/* file usestrtol.c
*
*   22-Aug-2005
*   © 1999-2005 by Jack Klein
*   All rights reserved
*
*   License granted for free non-commercial use
*
*   Description:
*
*   this program demonstrates the use of the standard
*   library function strtol() prototpyed in <stdlib.h>
*   to obtain and validate a signed integer value from the
*   standard input stream
*
*   C++ notes:
*
*   On newer, ANSI/ISO Standard Conforming C++ compilers,
*   the function strtol() is prototpyed in <cstdlib> and
*   is in the std namespace
*
*   The strtol() function operates with an array of constant
*   characters as its source which could be read with the
*   cin.getline() member function instead of the C library
*   fgets() function
*
*   It can even be called with the pointer returned by the
*   c_str() member function for a std::string
*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

int main(void)
{
    char buff [25];
    char *end_ptr;
    long long_var;
    int int_var;
    for ( ; ; )
    {
        printf("\nEnter an int, return only to quit: ");
        fflush(stdout);
        end_ptr = fgets(buff, sizeof buff, stdin);

        if ((end_ptr == NULL) || (buff [0] == '\n'))
        {
            break;
        }

        errno = 0;

        long_var = strtol(buff, &end_ptr, 0);

        if (ERANGE == errno)
        {
            printf("Number out of range...\n"
                   "Too %s for signed long\n",
                   LONG_MAX == long_var ? "big" : "small");
        }
        else if (long_var > INT_MAX)
        {
            printf("%ld too large!\n", long_var);
        }
        else if (long_var < INT_MIN)
        {
            printf("%ld too small!\n", long_var);
        }
        else if (end_ptr == buff)
        {
            printf("Not valid numeric input\n");
        }

        /* here is where you might want to add code to  */
        /* test for extra non-numeric characters at the */
        /* end of the input string, if you want that to */
        /* be an error                                  */
        /*                                              */
        /* if you want to generate this error, remove   */
        /* the two preprocessor lines that start with   */
        /* #if 0 and #endif                             */

        #if 0
        else if (('\n' != *end_ptr) && ('\0' != *end_ptr))
        {
             printf("Extra characters on input line\n");
        }
        #endif
				
        else
        {
            int_var = (int)long_var;
            printf("The number %d is OK!\n", int_var);
        }
    }
    return 0;
}

[Top]
[C And C++ Main Page]
[Home]

©1999-2005 By Jack Klein. All Rights Reserved.
All trademarks are acknowledged to belong to their respective owners.