Search code examples
ccommand-line-interfacecs50

how to check if a string is a number with a built-in Library?


I tried using the isdigit function but it only works for chars. I know I can write a function that'll check every char in a string but is there an already built-in way in c

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

int main(int argc, string argv[])
{
    if (argc != 2)
    {
        printf("Usage: ./caesar key");
        return 1;
    }
    else if (isdigit(argv[1]) != 1)
    {
        printf("Usage: ./caesar key");
        return 1;
    }
}

Solution

  • You cannot use isdigit() to way you do in the question: its argument must be a single character (all values of type unsigned char) or the special negative value EOF. Passing a string has undefined behavior.

    Your question has many possible answers, depending on what you mean by if a string is a number:

    • To check if a string pointed to by a pointer s is composed of digits only using strspn defined in <string.h>:

      if (*s && s[strspn(s, "0123456789")] == '\0') {
          // s is not empty and contains only digits
      }
      
    • same test using sscanf defined in `<stdio.h>:

      int n = 0;
      sscanf(s, "%*[0-9]%n, &n);
      if (n > 0 && s[n] == '\0') {
          // s is not empty and contains only digits
      }
      
    • same test using isdigit defined in `<ctype.h> in a loop:

      const char *p = s;
      while (isdigit((unsigned char)*p))
          p++;
      if (p > s && *p == '\0') {
          // s is not empty and contains only digits
      }
      
    • same test, using strtol defined in <stdlib.h>. Performs the conversion and accepts optional leading whitespace and an optional sign, may set errno in case of overflow (errno is defined in <errno.h>):

      char *p;
      long value;
      errno = 0;
      value = strtol(s, &p, 10);
      if (p != s && *p == '\0') {
          // s contains the decimal representation of an integer
          if (errno) {
              // value is too large for type `long`
          }
      }
      

    Here is an alternative using no library function correctly:

    #include <cs50.h>
    #include <limits.h>
    #include <stdio.h>
    
    enum key_result {
       KEY_OK,          // string is a valid integer
       KEY_TOO_LARGE,   // value is outside the range of type `int`
       KEY_EMPTY,       // string is empty
       KEY_NOT_NUMBER,  // string does not contain only digits
    };
    
    /* parse the argument as a key, store value into `*dest` */
    enum key_result get_key(const char *s, int *dest) {
        enum key_result res;
        int value;
    
        // string must not be empty
        if (*s == '\0')
            return KEY_EMPTY;
    
        value = 0;
        res = KEY_OK;
        // consume digits
        while (*s >= '0' && *s <= '9') {
            int digit = *s++ - '0';
            if ((value > INT_MAX / 10)
            ||  (value == INT_MAX / 10 && digit > INT_MAX % 10)) {
                value = INT_MAX;
                res = KEY_TOO_LARGE;
            } else {
                value = value * 10 + digit;
            }
        }
        if (*s != '\0')
            return KEY_NOT_NUMBER;
    
        *dest = value;
        return res;
    }
    
    int main(int argc, char *argv[]) {
        int key = 0;
    
        if (argc != 2) {
            fprintf(stderr, "Usage: ./caesar key\n");
            return 2;
        }
        switch (get_key(argv[1], &key)) {
          case KEY_OK:
            printf("caesar: key is %d\n", key);
            break;
          case KEY_TOO_LARGE:
            fprintf(stderr, "caesar: key value is too large: '%s'\n", argv[1]);
            return 1;
          default:
            fprintf(stderr, "caesar: key must be a number\n");
            return 1;
        }
        //...
    }