简体   繁体   中英

How to check an edge case in taking command line argument in C and evaluating to int or double?

So I have an assignment to figure out whether a number on the command line is either an integer or a double.

I have it mostly figured it out by doing:

sscanf(argv[x], "%lf", &d)

Where "d" is a double. I then cast it to an int and then subtract "d" with itself to check to see if it is 0.0 as such.

d - (int)d == 0.0

My problem is if the command line arguments contains doubles that can be technically classified as ints.

I need to classify 3.0 as a double whereas my solution considers it an int. For example initializing the program.

a.out 3.0

I need it to print out

"3.0 is a double"

However right now it becomes

"3 is an int." 

What would be a way to check for this? I did look around for similar problems which led me to the current solution but just this one edge case I do not know how to account for.

Thank you.

For example, a way like this:

#include <stdio.h>

int main(int argc, char *argv[]){
    if(argc != 2){
        puts("Need an argument!");
        return -1;
    }

    int int_v, read_len = 0;
    double double_v;
    printf("'%s' is ", argv[1]);
    //==1 : It was able to read normally.
    //!argv[1][read_len] : It used all the argument strings.
    if(sscanf(argv[1], "%d%n", &int_v, &read_len) == 1 && !argv[1][read_len])
        puts("an int.");
    else if(sscanf(argv[1], "%lf%n", &double_v, &read_len) == 1 && !argv[1][read_len])
        puts("a double.");
    else
        puts("isn't the expected input.");
}

To test if a string will covert to a int and/or double (completely, without integer overflow, without undefined behavior ), call strtol()/strtod() . @Tom Karzes

The trouble with a sscanf() approach is that the result is undefined behavior (UB) on overflow. To properly detect, use strtol()/strtod() .

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

bool is_int(const char *src) {
  char *endptr;
  // Clear, so it may be tested after strtol().
  errno = 0;
  // Using 0 here allows 0x1234, octal 0123 and decimal 1234.
  // or use 10 to allow only decimal text.
  long num = strtol(src, &endptr, 0 /* or 10 */);

#if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
  if (num < INT_MIN || num > INT_MAX) {
    errno = ERANGE;
  }
#endif

  return !errno && endptr > src && *endptr == '\0';
}

bool is_double(const char *src) {
  char *endptr;
  // Clear, so it may be tested after strtod().
  strtod(src, &endptr);

  // In this case, detecting over/underflow IMO is not a concern.
  return endptr > src && *endptr == '\0';
}

It is not entirely clear what the specific expectations are for your program, but it has at least something to do with the form of the input, since "3.0" must be classified as a double. If the form is all it should care about, then you should not try to convert the argument strings to numbers at all, for then you will run into trouble with unrepresentable values. In that case, you should analyze the character sequence of the argument to see whether it matches the pattern of an integer, and if not, whether it matches the pattern of a floating-point number.

For example:

int main(int argc, char *argv[]) {
    for (int arg_num = 1; arg_num < argc; arg_num++) {
        char *arg = argv[arg_num]; 
        int i = (arg[0] == '-' || arg[0] == '+') ? 1 : 0;  // skip any leading sign

        // scan through all the decimal digits
        while(isdigit(arg[i])) {
            ++i;
        }

        printf("Argument %d is %s.\n", arg_num, arg[i] ? "floating-point" : "integer");
    }
}

That makes several assumptions, chief among them:

  • the question is strictly about form, so that the properties of your system's built-in data types (such as int and double ) are not relevant.

  • each argument will have the form of either an integer or a floating-point number, so that eliminating "integer" as a possibility leaves "floating-point" as the only alternative. If "neither" is a possibility that must also be accommodated, then you'll also need to compare the inputs that do not have integer form to a pattern for floating-point numbers, too.

  • only decimal (or smaller radix) integers need be accommodated -- not, for example, hexadecimal inputs.

Under those assumptions, particularly the first, it is not just unnecessary but counterproductive to attempt to convert the arguments to one of the built-in numeric data types, because you would then come to the wrong conclusion about arguments that, say, are not within the bounds of representable values for those types.

For example, consider how the program should classify "9000000000". It has the form of an integer, but supposing that your system's int type has 31 value bits, that type cannot accommodate a value as large as the one the string represents.

int main (int argc,char *argv[])
{
    if(argc==2)
    {
        int i;
        double d;
        d=atof(argv[1]);
        i=atoi(argv[1]);
        if(d!=i)
            printf("%s is a double.",argv[1]);
        else if(d==i)
            printf("%s is an int.",argv[1]);

    }
    else
        printf("Invalid input\n");
    return 0;
}

You must add #include <stdlib.h>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM