proper use of scanf in a while loop to validate input - c

I made this code:
/*here is the main function*/
int x , y=0, returned_value;
int *p = &x;
while (y<5){
printf("Please Insert X value\n");
returned_value = scanf ("%d" , p);
validate_input(returned_value, p);
y++;
}
the function:
void validate_input(int returned_value, int *p){
getchar();
while (returned_value!=1){
printf("invalid input, Insert Integers Only\n");
getchar();
returned_value = scanf("%d", p);
}
}
Although it is generally working very well but when I insert for example "1f1" , it accepts the "1" and does not report any error and when insert "f1f1f" it reads it twice and ruins the second read/scan and so on (i.e. first read print out "invalid input, Insert Integers Only" and instead for waiting again to re-read first read from the user, it continues to the second read and prints out again "invalid input, Insert Integers Only" again...
It needs a final touch and I read many answers but could not find it.

If you don't want to accept 1f1 as valid input then scanf is the wrong function to use as scanf returns as soon as it finds a match.
Instead read the whole line and then check if it only contains digits. After that you can call scanf
Something like:
#include <stdio.h>
int validateLine(char* line)
{
int ret=0;
// Allow negative numbers
if (*line && *line == '-') line++;
// Check that remaining chars are digits
while (*line && *line != '\n')
{
if (!isdigit(*line)) return 0; // Illegal char found
ret = 1; // Remember that at least one legal digit was found
++line;
}
return ret;
}
int main(void) {
char line[256];
int i;
int x , y=0;
while (y<5)
{
printf("Please Insert X value\n");
if (fgets(line, sizeof(line), stdin)) // Read the whole line
{
if (validateLine(line)) // Check that the line is a valid number
{
// Now it should be safe to call scanf - it shouldn't fail
// but check the return value in any case
if (1 != sscanf(line, "%d", &x))
{
printf("should never happen");
exit(1);
}
// Legal number found - break out of the "while (y<5)" loop
break;
}
else
{
printf("Illegal input %s", line);
}
}
y++;
}
if (y<5)
printf("x=%d\n", x);
else
printf("no more retries\n");
return 0;
}
Input
1f1
f1f1
-3
Output
Please Insert X value
Illegal input 1f1
Please Insert X value
Illegal input f1f1
Please Insert X value
Illegal input
Please Insert X value
x=-3
Another approach - avoid scanf
You could let your function calculate the number and thereby bypass scanf completely. It could look like:
#include <stdio.h>
int line2Int(char* line, int* x)
{
int negative = 0;
int ret=0;
int temp = 0;
if (*line && *line == '-')
{
line++;
negative = 1;
}
else if (*line && *line == '+') // If a + is to be accepted
line++; // If a + is to be accepted
while (*line && *line != '\n')
{
if (!isdigit(*line)) return 0; // Illegal char found
ret = 1;
// Update the number
temp = 10 * temp;
temp = temp + (*line - '0');
++line;
}
if (ret)
{
if (negative) temp = -temp;
*x = temp;
}
return ret;
}
int main(void) {
char line[256];
int i;
int x , y=0;
while (y<5)
{
printf("Please Insert X value\n");
if (fgets(line, sizeof(line), stdin))
{
if (line2Int(line, &x)) break; // Legal number - break out
printf("Illegal input %s", line);
}
y++;
}
if (y<5)
printf("x=%d\n", x);
else
printf("no more retries\n");
return 0;
}

Generally speaking, it is my opinion that you are better to read everything from the input (within the range of your buffer size, of course), and then validate the input is indeed the correct format.
In your case, you are seeing errors using a string like f1f1f because you are not reading in the entire STDIN buffer. As such, when you go to call scanf(...) again, there is still data inside of STDIN, so that is read in first instead of prompting the user to enter some more input. To read all of STDIN, you should do something the following (part of code borrowed from Paxdiablo's answer here: https://stackoverflow.com/a/4023921/2694511):
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // Used for strtol
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
#define NaN 3 // Not a Number (NaN)
int strIsInt(const char *ptrStr){
// Check if the string starts with a positive or negative sign
if(*ptrStr == '+' || *ptrStr == '-'){
// First character is a sign. Advance pointer position
ptrStr++;
}
// Now make sure the string (or the character after a positive/negative sign) is not null
if(*ptrStr == NULL){
return NaN;
}
while(*ptrStr != NULL){
// Check if the current character is a digit
// isdigit() returns zero for non-digit characters
if(isdigit( *ptrStr ) == 0){
// Not a digit
return NaN;
} // else, we'll increment the pointer and check the next character
ptrStr++;
}
// If we have made it this far, then we know that every character inside of the string is indeed a digit
// As such, we can go ahead and return a success response here
// (A success response, in this case, is any value other than NaN)
return 0;
}
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
// (Per Chux suggestions in the comments, the "buff[0]" condition
// has been added here.)
if (buff[0] && buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
void validate_input(int responseCode, char *prompt, char *buffer, size_t bufferSize){
while( responseCode != OK ||
strIsInt( buffer ) == NaN )
{
printf("Invalid input.\nPlease enter integers only!\n");
fflush(stdout); /* It might be unnecessary to flush here because we'll flush STDOUT in the
getLine function anyway, but it is good practice to flush STDOUT when printing
important information. */
responseCode = getLine(prompt, buffer, bufferSize); // Read entire STDIN
}
// Finally, we know that the input is an integer
}
int main(int argc, char **argv){
char *prompt = "Please Insert X value\n";
int iResponseCode;
char cInputBuffer[100];
int x, y=0;
int *p = &x;
while(y < 5){
iResponseCode = getLine(prompt, cInputBuffer, sizeof(cInputBuffer)); // Read entire STDIN buffer
validate_input(iResponseCode, prompt, cInputBuffer, sizeof(cInputBuffer));
// Once validate_input finishes running, we should have a proper integer in our input buffer!
// Now we'll just convert it from a string to an integer, and store it in the P variable, as you
// were doing in your question.
sscanf(cInputBuffer, "%d", p);
y++;
}
}
Just as a disclaimer/note: I have not written in C for a very long time now, so I do apologize in advance if there are any error in this example. I also did not have an opportunity to compile and test this code before posting because I am in a rush right now.

If you're reading an input stream that you know is a text stream, but that you are not sure only consists of integers, then read strings.
Also, once you've read a string and want to see if it is an integer, use the standard library conversion routine strtol(). By doing this, you both get a confirmation that it was an integer and you get it converted for you into a long.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
bool convert_to_long(long *number, const char *string)
{
char *endptr;
*number = strtol(string, &endptr, 10);
/* endptr will point to the first position in the string that could
* not be converted. If this position holds the string terminator
* '\0' the conversion went well. An empty input string will also
* result in *endptr == '\0', so we have to check this too, and fail
* if this happens.
*/
if (string[0] != '\0' && *endptr == '\0')
return false; /* conversion succesful */
return true; /* problem in conversion */
}
int main(void)
{
char buffer[256];
const int max_tries = 5;
int tries = 0;
long number;
while (tries++ < max_tries) {
puts("Enter input:");
scanf("%s", buffer);
if (!convert_to_long(&number, buffer))
break; /* returns false on success */
printf("Invalid input. '%s' is not integer, %d tries left\n", buffer,
max_tries - tries);
}
if (tries > max_tries)
puts("No valid input found");
else
printf("Valid input: %ld\n", number);
return EXIT_SUCCESS;
}
ADDED NOTE: If you change the base (the last parameter to strtol()) from 10 to zero, you'll get the additional feature that your code converts hexadecimal numbers and octal numbers (strings starting with 0x and 00 respectively) into integers.

I took #4386427 idea and just added codes to cover what it missed (leading spaces and + sign), I tested it many times and it is working perfectly in all possible cases.
#include<stdio.h>
#include <ctype.h>
#include <stdlib.h>
int validate_line (char *line);
int main(){
char line[256];
int y=0;
long x;
while (y<5){
printf("Please Insert X Value\n");
if (fgets(line, sizeof(line), stdin)){//return 0 if not execute
if (validate_line(line)>0){ // check if the string contains only numbers
x =strtol(line, NULL, 10); // change the authentic string to long and assign it
printf("This is x %d" , x);
break;
}
else if (validate_line(line)==-1){printf("You Have Not Inserted Any Number!.... ");}
else {printf("Invalid Input, Insert Integers Only.... ");}
}
y++;
if (y==5){printf("NO MORE RETRIES\n\n");}
else{printf("%d Retries Left\n\n", (5-y));}
}
return 0;}
int validate_line (char *line){
int returned_value =-1;
/*first remove spaces from the entire string*/
char *p_new = line;
char *p_old = line;
while (*p_old != '\0'){// loop as long as has not reached the end of string
*p_new = *p_old; // assign the current value the *line is pointing at to p
if (*p_new != ' '){p_new++;} // check if it is not a space , if so , increment p
p_old++;// increment p_old in every loop
}
*p_new = '\0'; // add terminator
if (*line== '+' || *line== '-'){line++;} // check if the first char is (-) or (+) sign to point to next place
while (*line != '\n'){
if (!(isdigit(*line))) {return 0;} // Illegal char found , will return 0 and stop because isdigit() returns 0 if the it finds non-digit
else if (isdigit(*line)){line++; returned_value=2;}//check next place and increment returned_value for the final result and judgment next.
}
return returned_value; // it will return -1 if there is no input at all because while loop has not executed, will return >0 if successful, 0 if invalid input
}

Related

Use and explanation of getchar() function

I am writing a program to read a user input statement and extract all integers from the input. For example, if I enter "h3ll0", the program will output "30". I have used the fgets function to read the user input.
However, I am currently reading about getchar() and would like to know what would be the best way to use getchar() in my program to read user input instead of fgets. I am not really clear on how getchar() works and what situations it can be useful in.
This question is related to a project that specifically asks for getchar() as the method of reading user input. As I was unclear on how getchar() works, I built the rest of the program using fgets to ensure it was working.
#include <stdio.h>
int main()
{
char user_input[100];
int i;
int j = 0;
printf("Please enter your string: ");
fgets(user_input ,100, stdin);
for(i = 0; user_input[i] ; i++)
{
if(user_input[i] >= '0' && user_input[i] <= '9')
{
user_input[j] = user_input[i];
j++;
}
}
user_input[j] = '\0';
printf("Your output of only integers is: ");
printf("%s\n", user_input);
return 0;
}
OP: unclear on how getchar() works
int fgetc(FILE *stream) typically returns 1 of 257 different values.
"If ... a next character is present, the fgetc function obtains that character as an unsigned char converted to an int C11 §7.21.7.1 2
On end-of-file or input error (rare), EOF, is returned.
OP: to use getchar() in my program to read user input instead of fgets.
Create your own my_fgets() with the same function signature and same function as fgets() and then replace.
char *fgets(char * restrict s, int n, FILE * restrict stream);
The fgets function reads at most one less than the number of characters specified by n from the stream pointed to by stream into the array pointed to by s. No additional characters are read after a new-line character (which is retained) or after end-of-file. A null character is written immediately after the last character read into the array. C11 §7.21.7.2 2
Return the same value
The fgets function returns s if successful. If end-of-file is encountered and no characters have been read into the array, the contents of the array remain unchanged and a null pointer is returned. If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned. §7.21.7.2 3
Sample untested code
#include <stdbool.h>
#include <stdio.h>
char *my_fgets(char * restrict s, int n, FILE * restrict stream) {
bool something_read = false;
int ch = 0;
char *dest = s;
// Room ("reads at most one less") and EOF not returned?
while (n > 1 && (ch = fgetc(stream)) != EOF) {
n--;
something_read = true;
*dest++ = (char) ch;
if (ch == '\n') {
break; // "No additional characters are read after a new-line character"
}
}
// Did code end the while loop due to EOF?
if (ch == EOF) {
// Was EOF due to end-of-file or rare input error?
if (feof(stream)) {
// "If end-of-file is encountered and no characters ... read into the array ..."
if (!something_read) {
return NULL;
}
} else {
// "If a read error ..."
return NULL; // ** Note 1
}
}
// room for \0?
if (n > 0) {
*dest = '\0'; //" A null character is written immediately after the last character"
}
return s;
}
Perhaps improve fgets() and use size_t for n.
char *my_fgets(char * restrict s, size_t n, FILE * restrict stream);
fgets() with n <= 0 is not clearly defined. Using size_t, an unsigned type, at least eliminates n < 0 concerns.
Note 1: or use s = NULL; instead of return NULL; and let the remaining code null terminate the buffer. We have that option as "array contents are indeterminate".
Something like this should work as a clunky replacement to fgets using only getchar. I don't guarantee the accuracy of the error handling.
I don't think you would ever want to use getchar over fgets in an application. Getchar is more limited and less secure.
#include <stdint.h>
void your_fgets(char *buffer, size_t buffer_size)
{
int i;
size_t j;
if (buffer_size == 0)
return ;
else if (buffer_size == 1)
{
buffer[0] = '\0';
return ;
}
j = 0;
while ((i = getchar()) != EOF)
{
buffer[j++] = i;
if (j == buffer_size - 1 || i == '\n')
{
buffer[j] = '\0';
return ;
}
}
buffer[j] = '\0';
}
I am baffled by the comments on this post suggesting that fgets is easier to use. Using fgets unnecessarily complicates the issue. Just do:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int
main(int argc, char **argv)
{
int c;
while( ( c = getchar() ) != EOF ) {
if(isdigit(c) && (putchar(c) == EOF)) {
perror("stdout");
return EXIT_FAILURE;
}
}
return ferror(stdin);
}
There is absolutely no reason to use any additional buffering, or read the input one line at a time. Maybe you'll want to output newlines as they come in, but that would be an implementation detail that is left unspecified in the question. Either way, it's utterly trivial (if(( c == '\n' || isdigit(c)) && (putchar(c) == EOF))). Just read a character and decide if you want to output it. The logic is much easier if you don't think about the input as being more complicated than it is. (It's not line-oriented...it's just a stream of bytes.)
If, for some unknown reason you want to make this tool usable only in an interactive setting and load up your output with excess verbosity, you can easily do something like:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int
main(int argc, char **argv)
{
int c;
do {
int want_header = 1;
printf("Please enter your string: ");
while( ( c = getchar() ) != EOF && c != '\n' ) {
if(! isdigit(c)) {
continue;
}
if(want_header) {
want_header=0;
printf("Your output of only integers is: ");
}
if(putchar(c) == EOF) {
perror("stdout");
return EXIT_FAILURE;
}
}
if( c == '\n')
putchar(c);
want_header = 0;
} while(c == '\n');
return ferror(stdin);
}
but, please, don't do that. (Imagine if grep started by emitting a prompt that said "please enter the regex you would like to search for"!)

Using isdigit with if

int main()
{
int f;
printf("Type your age");
scanf("%d", &f);
if(!isdigit(f))
{
printf("Digit");
}
else
{
printf("Is not a digit");
}
return 0;
}
No matter if a typed 6 or a always shows me the "Digit" message
isdigit() should be passed a char not an int. And your if-else logic is reversed:
int main() {
char f;
printf("Type your age");
scanf("%c", &f);
if (isdigit(f)) {
printf("Digit");
} else {
printf("Is not a digit");
}
return 0;
}
As mentioned in the comments, this will only work for a single digit age. Validating input is a major topic under the 'C' tag, a search will reveal many approaches to more robust validation.
%d is an integer specifier. Change int f to char f and parse as a character. You are always passing an int into isdigit, which is why it is always true.
There's actually no need to use isdigit at all here since scanf with the %d format specifier already guarantees that the characters will be digits with an optional leading sign. And there's a separate specifier to get rid of the leading sign, %u.
If what you input isn't of the correct format, scanf will tell you (since it returns the number of items successfully scanned).
So, for a simple solution, you can just use something like:
unsigned int age;
if (scanf("%u", &age) == 1) {
puts("Not a valid age");
return 1;
}
// Now it's a valid uint, though you may want to catch large values.
If you want robust code, you may have to put in a little more effort than a one-liner scanf("%d") - it's fine for one-time or throw-away programs but it has serious shortcomings for code intended to be used in real systems.
First, I would use the excellent string input routine in this answer(a) - it pretty much provides everything you need for prompted and checked user input.
Once you have the input as a string, strtoul allows you to do the same type of conversion as scanf but with the ability to also ensure there's no trailing rubbish on the line as well. This answer (from the same author) provides the means for doing that.
Tying that all together, you can use something like:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
// Code to robustly get input from user.
#define OK 0 // Return codes - okay.
#define NO_INPUT 1 // - no input given.
#define TOO_LONG 2 // - input was too long.
static int getLine (
char *prmpt, // The prompt to use (NULL means no prompt).
char *buff, // The buffer to populate.
size_t sz // The size of the buffer.
) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
// Code to check string is valid unsigned integer and within range.
// Returns true if it passed all checks, false otherwise.
static int validateStrAsUInt(
char *str, // String to evaluate.
unsigned int minVal, // Minimum allowed value.
unsigned int maxVal, // Maximum allowed value.
unsigned int *pResult // Address of item to take value.
) {
char *nextChar;
unsigned long retVal = strtoul (str, &nextChar, 10);
// Ensure we used the *whole* string and that it wasn't empty.
if ((nextChar == str) || (*nextChar != '\0'))
return 0;
// Ensure it's within range.
if ((retVal < minVal) || (retVal > maxVal))
return 0;
// It's okay, send it back to caller.
*pResult = retVal;
return 1;
}
// Code for testing above functions.
int main(void) {
int retCode;
unsigned int age;
char buff[20];
// Get it as string, detecting input errors.
retCode = getLine ("Enter your age> ", buff, sizeof(buff));
if (retCode == NO_INPUT) {
printf ("\nError, no input given.\n");
return 1;
}
if (retCode == TOO_LONG) {
printf ("Error, input too long [%s]\n", buff);
return 1;
}
// Check string is valid age.
if (! validateStrAsUInt(buff, 0, 150, &age)) {
printf("Not a valid age (0-150)\n");
return 1;
}
// It's okay, print and exit.
printf("Age is valid: %u\n", age);
return 0;
}
(a) I'm reliably informed the author is actually quite clever, and very good looking :-)

Replacing `goto` with a different programming construct

I m trying to do this little programm with defensive programming but its more than difficult for me to handle this avoiding the Loop-Goto as i know that as BAD programming. I had try with while and do...while loop but in one case i dont have problem. Problem begins when i m going to make another do...while for the second case ("Not insert space or click enter button"). I tried and nested do...while but here the results was more complicated.
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int i;
int length;
char giventext [25];
Loop:
printf("String must have 25 chars lenght:\n");
gets(giventext);
length = strlen(giventext);
if (length > 25) {
printf("\nString has over %d chars.\nMust give a shorter string\n", length);
goto Loop;
}
/* Here i trying to not give space or nothing*/
if (length < 1) {
printf("You dont give anything as a string.\n");
goto Loop;
} else {
printf("Your string has %d\n",length);
printf("Letter in lower case are: \n");
for (i = 0; i < length; i++) {
if (islower(giventext[i])) {
printf("%c",giventext[i]);
}
}
}
return 0;
}
Note that your code is not defensive at all. You have no way to avoid a buffer overflow because,
you check for the length of the string after it has been input to your program so after the buffer overflow has already occurred and
you used gets() which doesn't check input length and thus is very prone to buffer overflow.
Use fgets() instead and just discard extra characters.
I think you need to understand that strlen() doesn't count the number of characters of input but instead the number of characters in a string.
If you want to ensure that there are less than N characters inserted then
int
readinput(char *const buffer, int maxlen)
{
int count;
int next;
fputc('>', stdout);
fputc(' ', stdout);
count = 0;
while ((next = fgetc(stdin)) && (next != EOF) && (next != '\n')) {
// We need space for the terminating '\0';
if (count == maxlen - 1) {
// Discard extra characters before returning
// read until EOF or '\n' is found
while ((next = fgetc(stdin)) && (next != EOF) && (next != '\n'))
;
return -1;
}
buffer[count++] = next;
}
buffer[count] = '\0';
return count;
}
int
main(void)
{
char string[8];
int result;
while ((result = readinput(string, (int) sizeof(string))) == -1) {
fprintf(stderr, "you cannot input more than `%d' characters\n",
(int) sizeof(string) - 1);
}
fprintf(stdout, "accepted `%s' (%d)\n", string, result);
}
Note that by using a function, the flow control of this program is clear and simple. That's precisely why goto is discouraged, not because it's an evil thing but instead because it can be misused like you did.
Try using functions that label logical steps that your program needs to execute:
char * user_input() - returns an input from the user as a pointer to a char (using something other than get()! For example, look at scanf)
bool validate_input(char * str_input) - takes the user input from the above function and performs checks, such as validate the length is between 1 and 25 characters.
str_to_lower(char * str_input) - if validate_input() returns true you can then call this function and pass it the user input. The body of this function can then print the user input back to console in lower case. You could use the standard library function tolower() here to lower case each character.
The body of your main function will then be much simpler and perform a logical series of steps that tackle your problem. This is the essence of defensive programming - modularising your problem into separate steps that are self contained and easily testable.
A possible structure for the main function could be:
char * user_input();
bool validate_input(char *);
void str_to_lower(char *);
int main()
{
char * str_input = user_input();
//continue to get input from the user until it satisfies the requirements of 'validate_input()'
while(!validate_input(str_input)) {
str_input = user_input();
}
//user input now satisfied 'validate_input' so lower case and print it
str_to_lower(str_input);
return 0;
}

Data validation in C - ensuring the input is in a correct format

I want to write a code to ensure that the users input only 1 digit. If a user enters something like "0 1 3" I want my program to read an error message which I have no idea how to do. Anyone has an idea how to approach this? My current code just takes in the first number if a user enters bunch of numbers with a space in between.
Please see my code below. Thanks :D
//Prompt the user to enter the low radius with data validation
printf("Enter the low radius [0.0..40.0]: ");
do
{
ret = scanf("%lf", &lowRadius);
//type validation
if (ret != 1)
{
int ch = 0;
while (((ch = getchar()) != EOF) && (ch != '\n'));
printf("Wrong input. Please enter one numerical value: ");
}
//range validation
else if((lowRadius < 0 || lowRadius > 40))
{
printf("Incorrect value. Please enter in range 0-40: ");
}
else break;
} while ((ret != 1) || (lowRadius < 0 || lowRadius > 40));//end while lowRadius
If you read the line into a string, then analyse it, you avoid the problem of hanging on unsupplied input. You have done most of the work already, but this shows how to trap too much input. It works by scanning a string after the double to pick up any more input. The return value from sscanf tells you if there was, because it returns the number of items successfully scanned.
#include <stdio.h>
#include <stdlib.h>
void err(char *message)
{
puts(message);
exit(1);
}
int main(void)
{
double lowRadius = 0.0;
char inp[100];
char more[2];
int conv;
if(fgets(inp, sizeof inp, stdin) == NULL) {
err("Input unsuccesful");
}
conv = sscanf(inp, "%lf %1s", &lowRadius, more); // conv is number of items scanned
if(conv != 1) {
err("One input value is required");
}
if(lowRadius < 0.0 || lowRadius > 40.0) {
err("Number out of range");
}
printf("%f\n", lowRadius);
return 0;
}
I'm unsure about your stipulation of a single digit, since that won't allow your maximum value to be entered.
Read a whole line and convert it with strtod.
Alexander has the right approach, but doesn't give much detail. Here is how I would do it, using getline() to read the input, and then strspn() plus strtod() to parse the input that was read. If you are not familiar with working with pointers, this will be difficult to understand - but if you are learning C, you'll get there eventually:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
double lowRadius;
char *lineptr = NULL;
size_t n;
char *startptr;
char *endptr;
char *ws = " \t\n"; /* possible whitespace characters */
printf("Enter the low radius [0.0..40.0]: ");
while(1) {
/* free lineptr if set - neeeded if we iterate on error input */
if( lineptr ) {
free(lineptr);
lineptr = NULL;
}
/* now read a line of input */
while( getline(&lineptr, &n, stdin) == -1 ) {
/* error returned, just retry */
continue;
}
/* skip over any leading whitespace */
startptr = lineptr + strspn(lineptr,ws);
/* Now try to convert double */
lowRadius = strtod(startptr, &endptr);
if( endptr==startptr || endptr[strspn(endptr,ws)] != 0 ) {
/* either no characters were processed - e.g., the
line was empty, or there was some non-whitespace
character found after the number. */
printf( "Wrong input. Please enter one numerical value: ");
} else if( (lowRadius < 0.0) || (lowRadius > 40.0) ) {
printf( "Incorrect value. Please enter in range 0-40: " );
} else {
if( lineptr ) free(lineptr);
break;
}
}
printf( "value entered was %lf\n", lowRadius );
}

Scanf keeps looping

Well i have this code to find the paint quality of a room.
void get_room_size(char room_id, int * length, int * width) {
while (*length <= 0 && *width <= 0) {
printf("Enter length and width of room %c in feet: ", room_id);
if (scanf("%d,%d", length, width)) {
if (*length <= 0) {
printf("###Error! Length must be a positive value!\n");
}
if (*width <= 0) {
printf("###Error! Width must be a positive value!\n");
}
printf("\n");
} else {
printf("bad data");
*length = 0;
*width = 0;
}
}
}
Basically, if i enter
a,1
It will go crazy and keep looping. Whats the problem?
The reason it's going "crazy" is as follows. When scanf fails to read in a as a number (because it's not numeric, obviously), it won't advance the file pointer.
This is one reason why you shouldn't generally use scanf operations, a failure can leave the file pointer in an indeterminate position (such as if you only scan in 3 of 12 items).
The other reason is that scanf means "scan formatted" and you would be hard pressed finding anything more unformatted than user input.
Anyway, back to the failure. Because the file pointer isn't advanced, the next time you come back to do fscanf, it will try to read that a again (and again and again).
If you want a decent function for handling user input, look no further than here:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
This will input a line from the user, with overflow protection (unlike gets or scanf with unbounded "%s").
It also flushes to end of line if the input was too long, which will stop the remainder of the line from affecting the next input operation.
You can then sscanf the buffer to your heart's content without any concerns re the file pointer.
The following test program shows how to use this:
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long [%s]\n", buff);
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
As an aside, you may want to re-examine your logic for a valid sized room. What you currently have would allow a room to be entered as 7 by -42 feet :-)
It's also not usually good form to rely on the output values being set to specific values on entry. If length and width are (for example) 3 and 4 on entry, this function will exit straight away without asking the user for input.
The first problem can be fixed by using || instead of &&. The second by initialising the variables to 0 at the start of the function so that the loop is entered.
For completeness, if you combine that original snippet above (the include statements and the getLine() function) with the following slightly modified get_room_size() function:
static void get_room_size(char room_id, int * length, int * width) {
char buff[100];
*length = *width = 0;
while ((*length <= 0) || (*width <= 0)) {
printf("Enter length and width of room %c in feet: ", room_id);
int rc = getLine (NULL, buff, sizeof (buff));
if (rc == NO_INPUT) {
printf ("\nEnd of file encountered.\n");
return;
}
if (rc == TOO_LONG) {
printf ("\nInput too long, please retry.\n");
continue;
}
if (sscanf(buff, "%d,%d", length, width) != 2) {
*length = *width = 0;
printf ("\nInput not in desired form (<number>,<number>), "
"please retry.\n");
continue;
}
if ((*length <=0) || (*width <= 0)) {
*length = *width = 0;
printf ("\nBoth length and width must be greater than zero, "
"please retry.\n");
}
}
}
and the very simple test main(), you'll see a complete program showing how to do it.
int main (void) {
int len, wid;
get_room_size ('x', &len, &wid);
printf ("Length is %d, width is %d.\n", len, wid);
return 0;
}
your scanf is taking in two integer. A is a character, think you want to
scanf("%c", *room_id);
scanf("%d", length);
I reccommend you do them separately
Try this:
...
if (scanf("%d,%d", length, width) == 2) {
if (*length <= 0) {
printf("###Error! Length must be a positive value!\n");
}
if (*width <= 0) {
printf("###Error! Width must be a positive value!\n");
}
printf("\n");
} else {
printf("bad data");
*length = -1;
*width = -1;
}
You dont put & in scanf() statement so how its read
if (scanf("%d,%d", &length, &width))

Resources