11

If user enters floating number for an integer variable I want to print invalid input. is that possible?

 int a;
 scanf("%d",&a); // if user enters 4.35 print invalid input 

I have tried for characters like this

  if(scanf("%d",&a)==1);
  else printf("invalid input");

But how to do for floating numbers. If user enters 4.35 it truncates to 4 but I want invalid input.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
niko
  • 9,285
  • 27
  • 84
  • 131
  • 2
    Maybe take in a float, then check if it stays the same after rounding? – StephenTG Aug 20 '13 at 15:44
  • Linked again: [Scanf won't execute for second time](http://stackoverflow.com/questions/17827603/scanf-wont-execute-for-second-time/17827635#17827635) I think related posts. – Grijesh Chauhan Aug 20 '13 at 16:34

9 Answers9

11

Since the start of a floating point number with any digits before the decimal point looks like an integer, there is no way to detect this with %d alone.

You might consider reading the whole line with fgets() and then analyzing with sscanf():

int a;
int n;
char line[4096];
if (fgets(line, sizeof(line), stdin) != 0 && sscanf(line, "%d%n", &a, &n) == 1)
   ...analyze the character at line[n] for validity...

(And yes, I did mean to compare with 1; the %n conversion specifications are not counted in the return value from sscanf() et al.)

One thing that scanf() does which this code does not do is to skip blank lines before the number is entered. If that matters, you have to code a loop to read up to the (non-empty) line, and then parse the non-empty line. You also need to decide how much trailing junk (if any) on the line is tolerated. Are blanks allowed? Tabs? Alpha characters? Punctuation?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 5
    Isn't this a bit overcomplicated? – thejh Aug 20 '13 at 15:49
  • 3
    It depends on what you want. Using `fgets()` and `sscanf()` is generally better than `scanf()`; it will be a whole heap easier to report on what the user typed this way. So, factor that out of the solution. That leaves `sscanf(line, "%d%n", &a, &n) == 1` and checking that you don't have a decimal point (default `.`, but locale-specific in general), or an `e` or `E` as the next character; anything else means the value entered was an integer. Even the `E` is not strictly proof; you'd need to see `E` and a number. 1.2E is the number 1.2 with an E after it, AFAIK (though I've not tested it today). – Jonathan Leffler Aug 20 '13 at 16:00
  • 1
    Interesting: on an Ubuntu 12.04 derivative, the following code regards the `E` as part of the floating point number; it prints out the space (32) as the stopping point. `#include int main(void) { char data[] = "34.1E and junk"; double d; int n; if (sscanf(data, "%lf%n", &d, &n) != 1) printf("scan failed\n"); else printf("Got: %f (stopped at '%c' %d)\n", d, data[n], data[n]); return 0; }` I'm not completely convinced that's correct behaviour; on Mac OS X, the conversion stops on the `E` instead (correct by my reckoning). (`"34.1E- and junk"` stops at blank on Linux too.) – Jonathan Leffler Aug 20 '13 at 16:38
  • 1
    Actually, prior to C99, its unspecified whether the match for `%n` is counted in the return value -- so a successful match might return 2 on some systems. That's ok as you can easily catch this case by using `sscanf(line, "%d%n", &a, &n) >= 1` instead as a failure to match will always return 0. – Chris Dodd Aug 20 '13 at 17:21
  • @ChrisDodd: interesting — I wasn't aware of that detail (or not any more; it is a while since I worked in an environment which was not reasonably C99 compliant). – Jonathan Leffler Aug 20 '13 at 17:42
  • 1
    @ChrisDodd: ISO 9899:1990 for `%n` in `fscanf()` says: _No input is consumed. The corresponding argument shall be a pointer to integer into which is to be written the number of characters read from the input stream so far by this call to the `fscanf` function. Execution of a `%n` directive does not increment the assignment count returned at the completion of the `fscanf` function._ Thus, only pre-standard versions of `fscanf()` could count `%n` (and `%n` was added by the C89 standard, AFAIK — though there probably was some prior art). Unless you think Schildt made up the standard in his book. – Jonathan Leffler Aug 21 '13 at 08:01
  • 2
    @thejh robust I/O in C is complicated ! – M.M Apr 01 '16 at 04:13
4

You'll have to read it as a double and then check if it is an integer. The best way to check if it is an integer is to use modf, which returns the decimal portion of the double. If there is one you have an error:

double d;
scanf("%lf", &d);

double temp;
if(modf(d, &temp)){
  // Handle error for invalid input
}

int a = (int)temp;

This will allow integers or floating point numbers with only 0s after the decimal point such as 54.00000. If you want to consider that as invalid as well, you are better off reading character by character and verifying that each character is between 0 and 9 (ascii 48 to 57).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Paul
  • 139,544
  • 27
  • 275
  • 264
  • Does not one need to check for range, should `d` be outside `int` range? – chux - Reinstate Monica Aug 20 '13 at 15:59
  • On many 64-bit machines, an `int` with 64 bit range exceeds the precision of a double, thus scanning the incorrect value into 'a'. A `long double` _may_ work though. – chux - Reinstate Monica Aug 20 '13 at 16:04
  • Not many 64-bit machine use a 64-bit `int` size; it leaves you with a quandary about what to do with 32-bit integers (or 16-bit integers). However, if you were dealing with `long long` (or 64-bit `long`), then your point would be entirely valid. – Jonathan Leffler Aug 20 '13 at 16:14
  • @Jonathan Leffler The range of an `int` may _usually_ be within the integer representable range of a `double`. But "usual" is not portable idea. I prefer portable ideas and this solution had a weakness concerning that. I've worked in embedded where `double` and `float` are the same. That would limit this solution even if `int` was still 32-bit. – chux - Reinstate Monica Aug 20 '13 at 16:30
3

This can not be done with out reading pass the int to see what stopped the scan.

Classic idiom

char buf[100];
if (fgets(buf, sizeo(buf), stdin) == NULL) {
  ; // deal with EOF or I/O error
}
int a;
char ch;
if (1 != sscanf(buf, "%d %c", &a, &ch)) {
  ; // Error: extra non-white space text
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • If there's white space after the integer, it was an integer. If the user typed `34 .34` (with a space before the `.`), they typed a valid integer first; there's just trailing junk after it on the line. Of course, that might be what you're after...any trailing junk is an error. It isn't clear from the question. – Jonathan Leffler Aug 20 '13 at 16:03
  • @Jonathan Leffler "34 .34" would fail the above as the extra, IMO, negates the correctness of the input. As you say, OP did not specify about trailing/leading white space nor extra text. – chux - Reinstate Monica Aug 20 '13 at 16:13
2

You can do it using strtol() and strtod() and comparing the end pointers, e.g. this:

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

int main(void) {
    char buffer[100];
    char * endptr_n;
    char * endptr_d;
    long n;
    double d;

    fgets(buffer, 100, stdin);
    n = strtol(buffer, &endptr_n, 10);
    if ( endptr_n == buffer ) {
        fputs("You didn't enter a number.", stderr);
        return EXIT_FAILURE;
    }

    d = strtod(buffer, &endptr_d);

    if ( *endptr_d == '\0' || *endptr_d == '\n' ) {
        if ( endptr_d == endptr_n ) {
            puts("You entered just a plain integer.");
        } else {
            puts("You entered a floating point number - invalid.");
        }
    } else {
        puts("You entered garbage after the number - invalid.");
    }

    return EXIT_SUCCESS;
}

outputs:

paul@local:~/src/c$ ./testint
2
You entered just a plain integer.
paul@local:~/src/c$ ./testint
2.3
You entered a floating point number - invalid.
paul@local:~/src/c$ ./testint
3e4
You entered a floating point number - invalid.
paul@local:~/src/c$ ./testint
4e-5
You entered a floating point number - invalid.
paul@local:~/src/c$ ./testint
423captainpicard
You entered garbage after the number - invalid.
paul@local:~/src/c$

It doesn't use scanf(), but that's a good thing, and it avoids the need to manually check the input following the integer you read.

Obviously, if the only thing on the line is the number, then a lot of this becomes unnecessary, since you can just call strtol() and check *endptr_n immediately, but if there may be other stuff on the line this is how you can do it, e.g. if you want to accept an integer followed by anything non-numeric, but not a floating point followed by the same thing, you can just remove the if ( *endptr_d == '\0' || *endptr_d == '\n' ) logic.

EDIT: updated the code to show the check to *endptr.

Crowman
  • 25,242
  • 5
  • 48
  • 56
1

This one is bit easier:

#include <stdio.h>

int main() 
{
    int a;
    long double b;

    scanf("%f",&b);
    a = (int) b;

    a == b ? printf("%d\n",a) : printf("Invalid input!");

    return 0;
} 

Input: 4
Output:

4

Input: 4.35
Output:

 Invalid input
haccks
  • 104,019
  • 25
  • 176
  • 264
1

Here's an easy way:

#include <stdio.h>

int main(int argc, char **argv) {
    int d;
    printf("Type something: ");

    // make sure you read %d and the next one is '\n'
    if( scanf("%d", &d) == 1 && getchar() == '\n' ) {
        printf("%d\n", d);
    }

    return 0;
}

.

$ a.exe
Type something: 312312.4214

$ a.exe
Type something: 2312312
2312312

$ a.exe
Type something: 4324.

$
1

First of all, there is nothing wrong with scanf. When a user enters a float then they actually type in a number dot number. So, code a scanf to detect that data entry.

main()
  {
       char c1[2];
       int num1;

       int nr_nums;

       nr_nums = scanf("%d%1[.e0123456789]", &num1, &c1);

       if (nr_nums == 1) {printf("\ndata = %d", num1);}
       if (nr_nums == 2) {printf("\nInvalid");}

 }

Modified this code per another possible data entry format of 1. or 3e-1 as suggested by a comment.

This code gets to the basics of your requirement. It accepts Integer data entry and detects when a float is entered.

JackCColeman
  • 3,777
  • 1
  • 15
  • 21
  • This won't work if they enter a floating point number using scientific notation, though, e.g. `1e-5`, and writing `1.` without anything to the right of the decimal point is a perfectly legitimate way of writing floating point constants in C, and someone may input it like that expecting it to be recognized. – Crowman Aug 20 '13 at 23:22
  • @PaulGriffiths, the question was if the user entered n.m not your example. I will think about your requirement and figure out out scanf can be modified accordingly. I.e. different requirement, different answer! – JackCColeman Aug 21 '13 at 05:18
  • @PaulGriffiths, have modified the code to handle the possibilities that you suggested. – JackCColeman Aug 22 '13 at 04:53
0

If you have your number represented as a string (when you have used fgets) you can run a for loop through it and compare each character to '.'.

Kirk
  • 3
  • 3
-1

One other option I can see follows:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{

 char mystring[31];
 scanf("%30s[0-9]\n", mystring);
 int mynumber = atoi(mystring);
 printf("here is your integer: %d", mynumber);
 getchar();
 getchar();
 return 0;











 }
will_learn
  • 39
  • 7
  • This does not work. An entry of, e.g. `12.34` reports that an integer value of 12 has been entered, but OP wants such inputs to be considered invalid. You may find a solution by looking into [`strtol()`](https://port70.net/~nsz/c/c11/n1570.html#7.22.1.4). Also note that there is no need for `#include ` here. – ad absurdum Jan 03 '19 at 04:27
  • This still does not work. Have you tried this code? There are some significant problems here: the `s` is not part of the scanset directive, so the format string should be `"%30[0-9]"`. Trailing whitespace characters should (almost) never be used in `scanf()` format strings; they cause havoc with interactive input and do not do what most people think they do here. As it is, with `"%30s[0-9]\n"`, only the `%30s` is matched. Even with the corrections, input of `12.34` is still incorrectly treated as an input of 12. – ad absurdum Jan 03 '19 at 21:59