ani627
ani627

Reputation: 6067

How to scanf only integer?

I want the code to run until the user enters an integer value.

The code works for char and char arrays.

I have done the following:

#include <stdio.h>

int main()
{
    int n;
    printf("Please enter an integer: ");
    while(scanf("%d",&n) != 1)
    {
        printf("Please enter an integer: ");
        while(getchar() != '\n');
    }
    printf("You entered: %d\n",n);
    return 0;
}

The problem is if the user inputs a float value scanf will accept it.

Please enter an integer: abcd
Please enter an integer: a
Please enter an integer: 5.9
You entered: 5

How can that be avoided?

Upvotes: 33

Views: 228552

Answers (9)

Andreas Wenzel
Andreas Wenzel

Reputation: 25396

Although it is generally not recommended to use scanf for line-based user input, I will first present a solution which does use scanf. Later, I will present a better solution that does not use scanf.

The scanf solution works by checking the remaining characters on the line that were not consumed by scanf. Generally, this should only be the newline character. However, since using the %d specifier with scanf accepts leading whitespace characters, it would be consistent if the program also accepted trailing whitespace characters.

Here is my solution which uses scanf:

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

int main( void )
{
    int n;

    //repeat until input is valid
    for (;;) //infinite loop, equivalent to while(1)
    {
        int c;

        //prompt user for input
        printf( "Please enter an integer: " );

        //attempt to read and convert input
        if ( scanf( "%d", &n ) == 1 )
        {
            //verify that remainder of input only consists of
            //whitespace characters
            while ( ( c = getchar() ) != EOF && c != '\n' )
            {
                if ( !isspace(c) )
                {
                    //we cannot use "break" here, because we want
                    //to break out of the outer loop, not the inner
                    //loop
                    goto invalid_input;
                }
            }

            //input is valid
            break;
        }

    invalid_input:

        //print error message
        printf( "Input is invalid!\n" );

        //discard remainder of line
        do
        {
            c = getchar();

        } while ( c != EOF && c != '\n' );
    }

    printf("You entered: %d\n",n);

    return 0;
}

This program has the following behavior:

Please enter an integer: abc
Input is invalid!
Please enter an integer: 6abc
Input is invalid!
Please enter an integer: 6.7
Input is invalid!
Please enter an integer: 6
You entered: 6

This scanf solution has the following issues:

  1. The program will have undefined behavior if the user enters a number that is not representable as an int (for example a number that is larger than INT_MAX).
  2. If the user enters an empty line, it does not print an error message. This is because the %d specifier of scanf consumes all leading whitespace characters.

These two issues can be solved by using the functions fgets and strtol, instead of the function scanf.

Performing all of these validation checks makes the code quite large, though. Therefore, it would make sense to put all of the code into its own function. Here is an example which uses the function get_int_from_user, which I took from this answer of mine to another question:

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

int get_int_from_user( const char *prompt );

int main( void )
{
    int number;

    number = get_int_from_user( "Please enter an integer: " );

    printf( "Input was valid.\n" );
    printf( "The number is: %d\n", number );

    return 0;
}

int get_int_from_user( const char *prompt )
{
    //loop forever until user enters a valid number
    for (;;)
    {
        char buffer[1024], *p;
        long l;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "Line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "Unrecoverable error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

            } while ( c != '\n' );

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        l = strtol( buffer, &p, 10 );
        if ( p == buffer )
        {
            printf( "Error converting string to number!\n" );
            continue;
        }

        //make sure that number is representable as an "int"
        if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
        {
            printf( "Number out of range error!\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6sdfj23jlj" gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return l;

    continue_outer_loop:
        continue;
    }
}

This program has the following behavior:

Please enter an integer: abc
Error converting string to number!
Please enter an integer: 6abc
Unexpected input encountered!
Please enter an integer: 6.7
Unexpected input encountered!
Please enter an integer: 6000000000
Number out of range error!
Please enter an integer: 6
Input was valid.
The number is: 6

Upvotes: 2

Andreas Foteas
Andreas Foteas

Reputation: 452

maybe not an optimal solution.. but uses only scanf and strtol

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

typedef enum {false, true} bool;

bool isIntString(char* input ) {
    int i;
    for (i = 0; i < strlen(input); i++) {
        if(input[i] < '0' || input[i] > '9') {
            return false;
        }
    }
    return true;
}

int main()
{
    char s[10];
    char *end;
    bool correct = false;
    int result;


    printf("Type an integer value: ");
    do {
        scanf("%s",s);
        if ( isIntString(s)) {
            correct = true;
            result = strtol(s, &end, 10);
            break;
        } else {
            printf("you typed %s\n",s);
            printf("\ntry again: ");
        }
    } while (!correct);


    printf("Well done!! %d\n",result);

    return 0;
}

Upvotes: 0

David Ranieri
David Ranieri

Reputation: 41045

Use fgets and strtol,

A pointer to the first character following the integer representation in s is stored in the object pointed by p, if *p is different to \n then you have a bad input.

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

int main(void) 
{
    char *p, s[100];
    long n;

    while (fgets(s, sizeof(s), stdin)) {
        n = strtol(s, &p, 10);
        if (p == s || *p != '\n') {
            printf("Please enter an integer: ");
        } else break;
    }
    printf("You entered: %ld\n", n);
    return 0;
}

Upvotes: 16

vicsana1
vicsana1

Reputation: 369

Try using the following pattern in scanf. It will read until the end of the line:

scanf("%d\n", &n)

You won't need the getchar() inside the loop since scanf will read the whole line. The floats won't match the scanf pattern and the prompt will ask for an integer again.

Upvotes: 6

  1. You take scanf().
  2. You throw it in the bin.
  3. You use fgets() to get an entire line.
  4. You use strtol() to parse the line as an integer, checking if it consumed the entire line.
char *end;
char buf[LINE_MAX];

do {
     if (!fgets(buf, sizeof buf, stdin))
        break;

     // remove \n
     buf[strlen(buf) - 1] = 0;

     int n = strtol(buf, &end, 10);
} while (end != buf + strlen(buf));

Upvotes: 47

chux
chux

Reputation: 154243

Using fgets() is better.

To solve only using scanf() for input, scan for an int and the following char.

int ReadUntilEOL(void) {
  char ch;
  int count;
  while ((count = scanf("%c", &ch)) == 1 && ch != '\n')
    ; // Consume char until \n or EOF or IO error
  return count;
}

#include<stdio.h>
int main(void) {
  int n;

  for (;;) {
    printf("Please enter an integer: ");
    char NextChar = '\n';
    int count = scanf("%d%c", &n, &NextChar);
    if (count >= 1 && NextChar == '\n') 
      break;
    if (ReadUntilEOL() == EOF) 
      return 1;  // No valid input ever found
  }
  printf("You entered: %d\n", n);
  return 0;
}

This approach does not re-prompt if user only enters white-space such as only Enter.

Upvotes: 3

hcs
hcs

Reputation: 1544

A possible solution is to think about it backwards: Accept a float as input and reject the input if the float is not an integer:

int n;
float f;
printf("Please enter an integer: ");
while(scanf("%f",&f)!=1 || (int)f != f)
{
    ...
}
n = f;

Though this does allow the user to enter something like 12.0, or 12e0, etc.

Upvotes: 4

John Bode
John Bode

Reputation: 123578

If you're set on using scanf, you can do something like the following:

int val;
char follow;  
int read = scanf( "%d%c", &val, &follow );

if ( read == 2 )
{
  if ( isspace( follow ) )
  {
    // input is an integer followed by whitespace, accept
  }
  else
  {
    // input is an integer followed by non-whitespace, reject
  }
}
else if ( read == 1 )
{
  // input is an integer followed by EOF, accept
}
else
{
  // input is not an integer, reject
}

Upvotes: 9

mafso
mafso

Reputation: 5553

I know how this can be done using fgets and strtol, I would like to know how this can be done using scanf() (if possible).

As the other answers say, scanf isn't really suitable for this, fgets and strtol is an alternative (though fgets has the drawback that it's hard to detect a 0-byte in the input and impossible to tell what has been input after a 0-byte, if any).

For sake of completeness (and assuming valid input is an integer followed by a newline):

while(scanf("%d%1[\n]", &n, (char [2]){ 0 }) < 2)

Alternatively, use %n before and after %*1[\n] with assignment-suppression. Note, however (from the Debian manpage):

This is not a conversion, although it can be suppressed with the * assignment-suppression character. The C standard says: "Execution of a %n directive does not increment the assignment count returned at the completion of execution" but the Corrigendum seems to contradict this. Probably it is wise not to make any assumptions on the effect of %n conversions on the return value.

Upvotes: 4

Related Questions