1

I have a problem how to check if the input is really an integer. My simple program below writes quotes if the input is 1 or 2. In all other cases it should print "luj".

My code:

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

int main(int argc, char **argv) {

  int cislo;

  printf("ml' nob:\n");
  scanf("%d", &cislo);

  if (cislo == 1) {
    printf("Qapla'\n");
    printf("noH QapmeH wo' Qaw'lu'chugh yay chavbe'lu' 'ej wo' choqmeH may' "
           "DoHlu'chugh lujbe'lu'.\n");
  } else if (cislo == 2) {
    printf("Qapla'\n");
    printf("Qu' buSHa'chugh SuvwI', batlhHa' vangchugh, qoj matlhHa'chugh, "
           "pagh ghaH SuvwI''e'.\n");
  } else {
    printf("luj\n");
  }
}

When the input is 1.5 my program read its as 1. But it should print "luj". In python I would do something like isinstance(cislo, int). How can I do it in C, please?

This question did not help me: Checking if input is an integer in C

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
vojtam
  • 1,157
  • 9
  • 34
  • Always check `scanf`'s return value. – Steve Summit Oct 15 '21 at 15:12
  • Indeed do check `scanf`'s return value, but that will not directly help in distinguishing 1 from 1.5. – John Bollinger Oct 15 '21 at 15:14
  • If `scanf` fails, you have no idea what value your `cislo` variable will have. Maybe better to say `int cislo = -1;`. – Steve Summit Oct 15 '21 at 15:14
  • 7
    A completely different approach is to read the user's input as a *string*, then call `strtol`, then check `strtol`'s returned value and returned "endp" pointer carefully to see if it was able to convert anything, or everything. This is admittedly somewhat tricky to get right. There have been questions about it in the past. There are some hints in [this answer](https://stackoverflow.com/questions/36074422/why-cant-you-just-check-if-errno-is-equal-to-erange/36075249#36075249). – Steve Summit Oct 15 '21 at 15:16
  • The `strtol()` approach may be a bit tricky to get right, but not more so than it is to get a `scanf`-based approach right. I was about to recommend `strtol()` myself. – John Bollinger Oct 15 '21 at 15:17
  • More information at [this question](https://stackoverflow.com/questions/64260139/how-can-i-validate-scanf-numeric-input/64264714#64264714). It turns out that the question "how can I validate numeric input?" can be surprisingly hard to answer. My personal C library contains a predicate `bool numeric(char *str)` to handle it for me when I need to. – Steve Summit Oct 15 '21 at 15:22
  • @SteveSummit thanks for reply. Can you show me simple code? Thanks – vojtam Oct 15 '21 at 15:43
  • @vojtam Unfortunately there is no simple code for this. – Steve Summit Oct 15 '21 at 16:00

2 Answers2

2

scanf with %d will only read integers - anything that's not an integer won't get read.

Unfortunately, this means you can get partial matches as you've experienced - "1.5" is not an integer, but scanf will read and assign the 1 to cislo and return success.

The way around this is to read the next input as text (not try to read it as an integer or float) and do the conversion separately using a library function like strtol or by doing your own conversion manually. Here's an example using strtol:

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

#define BUFFER_SIZE 11 // up to 10 digits for a 32-bit int plus sign

/**
 * We don't want to use a naked "%s" in our scanf call, since
 * if the user types in more characters than the buffer is sized
 * to hold, we'll write past the end of the buffer.  You can specify
 * the maximum number of characters to read as part of the "%s" 
 * conversion like "%11s", but unlike with printf you can't specify
 * it with a runtime argument - it has to be hardcoded.
 *
 * We're going to create a format string based on the value of BUFFER_SIZE;
 * when we're done, FMT will expand to "%" "11" "s", which will 
 * be interpreted as "%11s".  
 */
#define EXPAND(x) #x
#define STR(x) EXPAND(x)
#define FMT "%" STR(BUFFER_SIZE)  "s" // Make sure we don't read more characters than the buffer can hold

int main( void )
{
  int cislo = 0;

  char buffer[BUFFER_SIZE+1]; // +1 for string terminator
  if ( scanf ( FMT, buffer ) == 1 )
  {
    /**
     * strtol will take a string representation of an integer
     * like "123" and convert it to the corresponding integer
     * value.  chk will point to the first character in the
     * string that *isn't* part of an integer constant.  If that
     * character isn't whitespace or 0 (the string terminator),
     * then the input is not a properly formed integer.
     */
    char *chk;
    int tmp = strtol( buffer, &chk, 10 );
    if ( !isspace( *chk ) && *chk != 0 )
    {
      fprintf( stderr, "%s is not a valid integer input!\n", buffer );
      return -1;
    }
    
    cislo = tmp;
  }

  printf( "cislo = %d\n", cislo );
  return 0;
}

Examples:

$ ./converter 
123
cislo = 123

$ ./converter
abc
abc is not a valid integer input!

$ ./converter
123c
123c is not a valid integer input!

$ ./converter 
12.3
12.3 is not a valid integer input!
John Bode
  • 119,563
  • 19
  • 122
  • 198
1

Note that C is not like Python: You want to input a number with a decimal point into a variable whose type is an integer. An integer type variable can only hold integers, so... This number will automatically become an integer and the number behind the dot will not be counted.

So when you trying to insert the value 1.5 into cislo the value is automatically converted to an int value, to 1.


Solution:

#include <stdio.h>

int main(int argc, char** argv) {
    float cislo;
    
    printf("ml' nob:\n");
    
    if (scanf("%f", &cislo) != 1)
        // Problem (Handle it)

    if (cislo == 1) {
        printf("Qapla'\n");
        printf("noH QapmeH wo' Qaw'lu'chugh yay chavbe'lu' 'ej wo' choqmeH may' DoHlu'chugh lujbe'lu'.\n");
    } else if (cislo == 2) {
        printf("Qapla'\n");
        printf("Qu' buSHa'chugh SuvwI', batlhHa' vangchugh, qoj matlhHa'chugh, pagh ghaH SuvwI''e'.\n");  
    } else {
        printf("luj\n");
    }
}
InSaNiTy
  • 150
  • 1
  • 9
  • 2
    Would this work if the input was `"abc"`? – Bill Lynch Oct 15 '21 at 15:51
  • @BillLynch ```cislo``` will be set to 0.0, And in fact, the else clause will be executed. So yes, it will work. I also see that you edited the question, is my answer still relevant? – InSaNiTy Oct 15 '21 at 18:02