26

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?

ani627
  • 5,221
  • 8
  • 38
  • 44
  • Is this similar ? http://stackoverflow.com/questions/23610457/how-can-i-distinguish-between-integer-and-character-when-using-scanf – Suvarna Pattayil Oct 27 '14 at 08:42
  • 5
    It's not 'scanning a float' but rather stopping at the first non-digit. It will also do this with input "5xyzNotAFloatNumber". – Jongware Oct 27 '14 at 08:43
  • So it can't be done with `scanf`? So one must always use `fgets`? – ani627 Oct 27 '14 at 08:46
  • 1
    @1336087 it's not a **must,** but you better do. Yes, it **can** be done using `scanf()`, but `scanf()` is horrible and unsafe in the general case. – The Paramagnetic Croissant Oct 27 '14 at 08:47
  • @TheParamagneticCroissant: Thanks for your input. :) I know how this can be done using `fgets` and `strtol`, I would like to know who this can be done using `scanf()`(if possible). – ani627 Oct 27 '14 at 08:55
  • 1
    Please refer to http://stackoverflow.com/a/36704392/5547353 – Yonggoo Noh Apr 18 '16 at 21:35

8 Answers8

41
  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));
Kevin Klute
  • 440
  • 1
  • 4
  • 21
13

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;
}
David Ranieri
  • 37,819
  • 6
  • 48
  • 88
  • This doesn't print anything until the user pushes enter, right? What am I missing? – Tanner Apr 23 '20 at 14:55
  • @Tanner it runs until the user enters an integer value, as required in the question. – David Ranieri Apr 23 '20 at 15:17
  • 1
    Okay, great. I am working on a similar problem, but which requires a prompt at the beginning and wanted to make sure I understood the logic here :) It works great! – Tanner Apr 23 '20 at 15:22
8

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
}
John Bode
  • 113,266
  • 18
  • 112
  • 190
5

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.

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
vicsana1
  • 359
  • 1
  • 4
  • 1
    `scanf("%d\n",&n)` only reads the whole line (and then some) if a `int` was entered. If no `int` was entered, input remains in `stdin`. Using `"\n"` not only consumes `'\n'`, but it scans for any number of white-space until a non-whitespace is entered. That `char` is then put back into `stdin`. So `scanf()` will not immediately return when `'\n'` is entered. – chux - Reinstate Monica Oct 27 '14 at 14:33
  • See [`scanf()` and trailing white space in format string](https://stackoverflow.com/questions/15740024/) — don't do it if you value your user's sanity. – Jonathan Leffler Jun 16 '19 at 09:33
4

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.

mafso
  • 5,264
  • 2
  • 16
  • 39
3

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.

chux - Reinstate Monica
  • 127,356
  • 13
  • 118
  • 231
  • in each pass through the loop, set NextChar = '\0'; However, , '\n', in certain OSs, is a multi char item, so define as 'int NextChar = 0 Then it will re-prompt when no char entered – user3629249 Oct 28 '14 at 03:25
  • @user3629249 Thanks for the idea - d o not think it will work. When no `char` is entered (example: only `'\n'`), `scanf()` will not return until some non-white-space is entered. The `"%d"` causes `scanf()` to consume white-space until EOF or non-white-space. So setting `NextChar` to this or that will not make a difference. – chux - Reinstate Monica Oct 28 '14 at 03:36
  • @chux How would you modify this if it was to accept only positive integers? The modification of the first if `if (count >= 1 && n > 0 && NextChar == '\n') ` doesnt seem to work. Stdout doesnt react if we give for example `-125` but we have to enter input again to show a message. – BugShotGG Mar 02 '15 at 10:43
  • @Geo Papas `if (count >= 1 && NextChar == '\n')` is used to detect if a number was entered without additional `char`. It is within the _body_ of this `if()` that the additional testing should be added to further qualify `n`: `if (count >= 1 && NextChar == '\n') if (!(n > 0)) continue; break;` – chux - Reinstate Monica Mar 02 '15 at 15:34
3

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.

hcs
  • 1,464
  • 9
  • 14
0

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;
}
Andreas Foteas
  • 343
  • 2
  • 10