1
\$\begingroup\$

I would like to know if this is a good way to make sure the user enters a valid number.

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

void clear_stream(FILE *in)
{
   int ch;
   clearerr(in);

   do
      ch = getc(in);
   while (ch != '\n' && ch != EOF);

   clearerr(in);
}

float retreive_input()
{
    float input = 0;
    fflush(stdout);
    while (scanf_s("%f", &input) != 1)
    {
        clear_stream(stdin);
        printf("Invalid number. Please try again: ");
        fflush(stdout);
    }

    return input;
}

int main()
{
    float input = 0;
    printf("ENTER A NUMBER: ");
    input = retreive_input();
    printf("YOUR INPUT: %f", input);

    getchar();
    return 0;
}
\$\endgroup\$
  • \$\begingroup\$ You need to define what 'valid input' is. For example, are: "4.22Rubbish" and "4.22.33.44.55" valid inputs? They're both recognised as 4.22 by your existing code. Is this correct? \$\endgroup\$ – forsvarir Sep 1 '16 at 21:11
  • \$\begingroup\$ @forsvarir I never thought of that, no those aren't valid I just really wouldn't know how to handle those kinds of input. Would you have any suggestions for how to approach those inputs to catch the errors? Thank you! \$\endgroup\$ – chris360 Sep 1 '16 at 21:23
  • \$\begingroup\$ @chris360 The only reasonable way I know of approaching those errors is to read the whole line in, then validate the entire line. \$\endgroup\$ – Justin Sep 1 '16 at 21:37
1
\$\begingroup\$

@Justin advice is solid. Read a line.

  1. scanf_s("%f", &input) offers no advantage over the more portable scanf("%f", &input)

  2. scanf_s/scanf("%f", &input) allows the user to enter multiple lines before the number - so a blank line of input is not detected.

  3. scanf_s/scanf("%f", &input) does not detect trailing non-numeric input.

  4. while (scanf_s("%f", &input) != 1) does not cope with non-numeric entries - they remain in stdin.

  5. Overflow is UB with scanf() and friends.

Instead separate the task: input, parsing.

#define MAXLINE_SIZE 100
char buf[MAXLINE_SIZE];
if (fgets(buf, sizeof buf, stdin) == NULL) Handle_EOF();

size_t len = strlen(buf);
if (len == 0 || buf[len-1] != '\n') {
  // consider the line hostile and fail it.
  // consume rest of line
  clear_stream(stdin);
  return FAIL;
}

Parse using strtod() - Simple example. Additional considerations if over/underflow detection needed.

errno = 0;
char *endptr;
float y = (float) strtod(buf, &endptr);

if (buf == endptr) Handle_NoConversion();
// consume trailing spaces
while (isspace((unsigned char) *endptr) endptr++;
if (*endptr) Handle_TrailingJunk(). 

// Good to go;
return y;

Good use of fflush(stdout); before input and after the re-prompt that may lack a \n. I would not hard code in the re-prompt value, but pass it in.

Good use of int ch; when testing the return value of getc().

Using %f to print floating point is problematic should the float be small (output only 0.000000) or large (many to hundreds of digits). Consider %.*e

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.