0

I want to validate the input is int.

Ideally, I want it to prompt to the user if the validation failed.

I tried this:

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

int main(void)
{
    int i;
    printf("enter a number: ");
    scanf("%i", &i);
    (isdigit(i)) ? printf("%i is a digit\n", i) : printf("%i is NOT a digit\n", i);
}

it does not do what i want.

here is the result:

> enter a number: 1
> 1 is NOT a digit

but magically:

> enter a number: 54
> 54 is a digit

why? How can I do the validation in the right way?

== UPDATE ==

I tried some suggestion to not using isdigit but checking the scanf result, it works, as follow:

int i, checker;
printf("enter a number: ");
checker = scanf("%i", &i);
(checker) ? printf("%d Is digit\n", i) : printf("%d Is NOT digit\n", i);

here is the result

enter a number: 12
12 Is digit
enter a number: asdf
220151845 Is NOT digit

but it failed the while loop validation,

int i, checker;
do
{
    printf("enter a number: ");
    checker = scanf("%i", &i);
} while (!checker);
printf("number : %i\n", i);

especially when it is not a digit, then it become infinity looping the printf but not prompt to user at all.

Thanks.

  • 2
    Don't use `isdigit()` — it is for checking whether a character is a digit or not. You've read a number. You must check the return value from `scanf()` — if it is not 1, you've got a problem. Depending on your requirements, the fact that there may be all sorts of stuff on the line after the number may or may not be a problem. I'm assuming when you say "validate the input is an integer", you want to allow for multiple-digit numbers, and negative numbers, and since you used `%i` rather than `%d`, you're fine with octal or hexadecimal values being entered too (leading 0 or 0x respectively). – Jonathan Leffler Apr 26 '20 at 02:40
  • 1
    isdigit() will only be true if i is between 48 and 57, because isdigit() expects char to be in argument not an integer value. isdigit(54) is equivalent to isdigit('6'). – h3t1 Apr 26 '20 at 02:51
  • @JonathanLeffler I tried `checker = scanf("%i", &i);` and `(checker) ? printf("%i is a digit\n", i) : printf("%i is NOT a digit\n", i);`, it works. but when i put it in the `while loop`, it become and infinite loop when it is NOT digit.. any idea? –  Apr 26 '20 at 02:51
  • You didn't write the same check that I wrote — I wrote that you must check that the value is 1, which means you must check for equality with 1, because it can return 0 or EOF too. When you encounter EOF, `if (checker)` evaluates to 'true'. You need to test `if (checker == 1) printf("OK\n"); else if (checker == 0) printf("Not OK\n"); else { printf("EOF\n"); break; }` (except you'll use your more verbose messages instead of my very terse ones). – Jonathan Leffler Apr 26 '20 at 02:55

4 Answers4

2

Don't use isdigit() — it is for checking whether a character is a digit or not. You've read a number. You must check the return value from scanf() — if it is not 1, you've got a problem. Depending on your requirements, the fact that there may be all sorts of stuff on the line after the number may or may not be a problem. I'm assuming when you say "validate the input is an integer", you want to allow for multiple-digit numbers, and negative numbers, and since you used %i rather than %d, you're fine with octal values (leading 0) or hexadecimal values (leading 0x or 0X) being entered too.

Note that if you have:

int checker = scanf("%i", &i);

then the result could be 1, 0, or EOF. If the result is 1, then you got an integer after possible leading white space, including possibly multiple newlines. There could be all sorts of 'garbage' after the number and before the next newline. If the result is 0, then after skipping possible white space, including possibly multiple newlines, the first character that wasn't white space also wasn't part of an integer (or it might have been a sign not immediately followed by a digit). If the result is EOF, then end-of-file was detected after possibly reading white space, possibly including multiple newlines, but before anything other than white space was read.

To continue sensibly, you need to check that the value returned was 1. Even then, there could be problems if the value is out of the valid range for the int type.

The full requirement isn't completely clear yet. However, I'm going to assume that the user is required to enter a number on the first line of input, with possibly a sign (- or +), and possibly in octal (leading 0) or hexadecimal (leading 0x or 0X), and with at most white space after the number and before the newline. And that the value must be in the range INT_MIN .. INT_MAX? (The behaviour of scanf() on overflow is undefined — just to add to your woes.)

The correct tools to use for this are fgets() or POSIX getline() to read the line, and strtol() to convert to a number, and isspace() to validate the tail end of the line.

Note that using strtol() properly is quite tricky.

In the code below, note the cast to unsigned char in the call to isspace(). That ensures that a valid value is passed to the function, even if the plain char type is signed and the character entered by the user has the high bit set so the value would convert to a negative integer. The valid inputs for any of the ispqrst() or topqrst() functions are EOF or the range of unsigned char.

C11 §7.4 Character handling <ctype.h> ¶1

… In all cases the argument is an int, the value of which shall be representable as an unsigned char or shall equal the value of the macro EOF. If the argument has any other value, the behavior is undefined.

The GNU C library tends to protect the careless, but you should not rely on being nannied by your standard C library.

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

int main(void)
{
    char line[4096];    /* Make it bigger if you like */

    printf("Enter a number: ");
    if (fgets(line, sizeof(line), stdin) == 0)
    {
        fprintf(stderr, "Unexpected EOF\n");
        exit(EXIT_FAILURE);
    }
    errno = 0;
    char *eon;    /* End of number */
    long result = strtol(line, &eon, 0);
    if (eon == line)
    {
        fprintf(stderr, "What you entered is not a number: %s\n", line);
        exit(EXIT_FAILURE);
    }
    if (sizeof(int) == sizeof(long))
    {
        if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE)
        {
            fprintf(stderr, "What you entered is not a number in the range %ld..%+ld: %s\n",
                    LONG_MIN, LONG_MAX, line);
            exit(EXIT_FAILURE);
        }
    }
    else
    {
        if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE)
        {
            fprintf(stderr, "What you entered is not a number in the range %ld..%+ld,\n"
                    "let alone in the range %d..%+d: %s\n",
                    LONG_MIN, LONG_MAX, INT_MIN, INT_MAX, line);
            exit(EXIT_FAILURE);
        }
    }
    char c;
    while ((c = *eon++) != '\0')
    {
        if (!isspace((unsigned char)c))
        {
            fprintf(stderr, "There is trailing information (%c) after the number: %s\n",
                    c, line);
            exit(EXIT_FAILURE);
        }
    }
    if (result < INT_MIN || result > INT_MAX)
    {
        fprintf(stderr, "What you entered is outside the range %d..%+d: %s\n",
                INT_MIN, INT_MAX, line);
        exit(EXIT_FAILURE);
    }
    int i = result;   /* No truncation given prior tests */
    printf("%d is a valid int\n", i);
    return(EXIT_SUCCESS);
}

That seems to work correctly for a fairly large collection of weird numeric and non-numeric inputs. The code handles both 32-bit systems where sizeof(long) == sizeof(int) and LP64 64-bit systems where sizeof(long) > sizeof(int) — you probably don't need that. You can legitimately decide not to detect all the separate conditions but to aggregate some of the errors into a smaller number of error messages.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Any particular reason for `if (result == LONG_MIN || result == LONG_MAX) && errno == ERANGE)` versus a simple `if (errno == ERANGE)`? – chux - Reinstate Monica Apr 26 '20 at 04:24
  • Caution, mainly. The standard doesn't say that `errno` can't be set to `ERANGE` even if the values are within range. See the linked question about handling `strtol()` correctly (which quotes the C11 standard). Granted, it is not likely that the code in `strtol()` would set `errno` without also returning either LONG_MIN or LONG_MAX, but safety suggests following the letter of the standard. – Jonathan Leffler Apr 26 '20 at 04:29
  • There's also an assumption that the input is in a single-byte code set such as 8859-1, not in UTF-8 or UTF-16. Also, that you're not attempting to use one of the other sets of numerals, such as one of the [Arabic sets of numerals](https://stackoverflow.com/a/1676590/15168) (there is one variant for Persian and Urdu, and another for other Arabic countries), and …. Validation of input, even as simple as "is it an integer", is not trivial in full generality. And reading a line is making some assumptions, too. – Jonathan Leffler Apr 26 '20 at 04:32
  • Disagree about that (ref § 7.5 3 & § 7.22.1.4 8) - maybe another day to post that question. – chux - Reinstate Monica Apr 26 '20 at 04:36
  • As I said — caution is the motive. I'm not going to take the short-cut. You may if you wish. – Jonathan Leffler Apr 26 '20 at 04:37
1

isdigit() can only test one char if it's digit or not.

in order to test a multi-digit number, you must read this number char by char;

here is an example showing that:

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

int main(void)
{
    int i=0;
    char s[100];
    printf("enter a number: ");
    scanf("%99s", &s);
    while(s[i]!='\0'){
        if(!isdigit(s[i]))break;
        i++;
    }
    if(s[i]=='\0') printf("%s is a number\n",s);
    else printf("%s is NOT a number\n",s);
}
h3t1
  • 1,126
  • 2
  • 18
  • 29
0

isdigit is a function to check a single char only. It is not designed to check whether a string is a valid integer or not.

The argument "%i" reads input line and convert it to an integer, so the data stored in i is an integer number, not the ASCII code that isdigit expected.

If you want to validate a single character, you can use "%c" instead of "%i" #include #include

    int main(){
        char c;
        int n = scanf("%c",&c);
        if(isdigit(c)){
            printf("%c is digit\n",c);
        }else{
            printf("%c is not digit\n",c);
        }
        return 0;
    }

If you want to validate a line, you can check this: [Check if input is integer type in C] (Check if input is integer type in C)

In short, use "%i%c" instead of "%i", check the result of "%c" to validate if input is purely in digits.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
tgarm
  • 473
  • 3
  • 8
0

About a while loop with scanf not working, read this from the scanf Linux man page:

The format string consists of a sequence of directives which describe how to process the sequence of input characters. If processing of a directive fails, no further input is read, and scanf() returns. A "failure" can be either of the following: input failure, meaning that input characters were unavailable, or matching failure, meaning that the input was inappropriate (see below).

Especially note "If processing of a directive fails, no further input is read"

So if your scanf fails to read an integer you need to do something to read and discard the bad input.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131