(C) Get safe int input - c

So I tasked myself to write a function, that:
overwrites an int with a safe value (not return gibberish if the
user decides to input char-s or anything bigger by absolute value
than (2^31-1)
if input exceeds (2^31 - 1) (meaning if the user inputs 8 or more
digits) the int must be overwritten with the upper value
Here is the code:
void getSafeIntWithBoundaries(int *dest, int lo, int hi, const char *message);
bool anyChars(const char *input, int len);
int main() {
int x;
getSafeIntWithBoundaries(&x, 1, 10, "Enter an integer between 0 and 10.");
printf("x = %d\n", x);
return 0;
}
void getSafeIntWithBoundaries(int * dest, int lo, int hi, const char * message) {
char input[33];
while (1) {
puts(message);
fgets(input, 33, stdin);
int len = strlen(input);
if (input[len - 1] == '\n') { input[len - 1] = '\0'; }
--len;
if (bool reset = anyChars(input, len)) {
puts("Try again.");
continue;
}
else {
int ret;
if (strcmp("2147483648", input) < 0) {
*dest = hi;
return;
}
sscanf(input, "%d", &ret);
ret = ret > hi ? hi : ret;
ret = ret < lo ? lo : ret;
*dest = ret;
break;
}
}
}
bool anyChars(const char * input, int len) {
for(int i = 0; i < len; i++) {
if (!isdigit(input[i])) {
return true;
}
}
return false;
}
A few more notes:
in getSafeIntWithBoundaries(...) I'm getting rid of the '\n', I'm
changing it for a '\0', respectively decreasing int len; which holds
the length of the input.
anyChars() checks whether the input contains any non digit char. If
it does, then the user has to re-enter. One of the problems is
however that in case of failure, message needs to be printed out only
once. If I input something ridiculously long, message will be printed
multiple times. I don't know how to fix this.
the strcmp() bit checks if the user entered a number bigger than
(2^31 - 1). If the user has, then the int must be overwritten with
the high value and the function needs to end. Problem is however, if
the user enters a very long number, the target int will be
overwritten with the low boundary. I don't know how to fix that
either.
2 ?s making sure the target int won't exceed its boundaries. I marked
the parts that I can't figure out with bold, essentially that's the
whole question.
Suggestions on improving the code are welcomed as well.

Suggestions on improving the code are welcomed
Code fails many cases
Overflow UB
When the range exceed int, sscanf(input, "%d", &ret) is undefined behavior.
Long lines not consumed
When input is more than 32 characters (including the '\n), left over input remains.
Null character input
Input starting with a null character '\0' lead to undefined behavior with input[len - 1]
Non ASCII input
isdigit(input[i]) is undefined behavior when input[i] < 0.
Assumed ranged
Code uses int assuming it covers the range 2^31 - 1. C requires int to have a
minimum range of [-32,767 ... 32,767].
Unclear goals
"if input exceeds (2^31 - 1) (meaning if the user inputs 8 or more digits)" --> What if input is `"0000000000000000000000000000000000001\n"? 35 zeros? It is in range yet exceeds 8 digits and exceed 33 character buffer.
End-of-file
puts("Try again."); does not make sense if input is closed. I'd expect int getSafeIntWithBoundaries() to return 1 on success, 0 on failure, EOF on end-of-file/input error.
Below is some untested code - will test later. I'll work on the message details later. It is certainty more than what one might think is needed to simply read an `int, but if you want robust code, it is work.
To read an entire line of input obliges reading until '\n' or EOF.
I'd tolerate leading and trailing spaces.
strtol() is good , but then the entire line needs to be read first. Recall valid input can have many leading spaces or zeros.
Do not overflow intmath- it is UB. Summing the value with negativesint` has greater range than the positive side.
Pre-C99 /,% has implementation defined behavior when the remainder is non-zero - so I avoided that.
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#define INT_MIN_LS_DIGIT ((-(INT_MIN + 10)) % 10)
#define INT_MIN_DIV_10 ((INT_MIN + INT_MIN_LS_DIGIT)/10)
int getSafeIntWithBoundaries(int * dest, int lo, int hi, const char *message) {
fputs(message, stdout);
fflush(stdout); // Insure data to sent out completely
int ch;
while (isspace((ch = fgetc(stdin))) && (ch != '\n')) {
;
}
bool positive = true;
if (ch == '-' || ch == '+') {
positive = ch == '+';
ch = fgetc(stdin);
}
bool digit_found = false;
bool overflow = false;
int sum = 0;
while (isdigit(ch)) {
digit_found = true;
int digit = ch = '0';
// Detect possible overflow
if (sum <= INT_MIN_DIV_10
&& (sum < INT_MIN_DIV_10 || digit > INT_MIN_LS_DIGIT)) {
sum = INT_MIN;
overflow = true;
} else {
sum = sum * 10 - digit;
}
}
if (positive) {
if (sum < -INT_MAX) {
sum = INT_MAX;
overflow = true;
} else {
sum = -sum;
}
}
if (sum > hi) {
sum = hi;
overflow = true;
}
if (sum < lo) {
sum = lo;
overflow = true;
}
*dest = sum;
while (isspace(ch) && ch != '\n') {
ch = fgetc(stdin);
}
if (ch == EOF && iserror(stdin)) {
return EOF; // Rare input error detected
}
if (!digit_found) {
return 1; // or a "No digit found" error code
}
if (overflow) {
errno = ERANGE;
return 1; // or a "Overflow" error code
}
if (ch != '\n' && ch != EOF) {
return 1; // or a "Extra trailing junk" error code
}
return 0;
}

strtol could be used to parse an integer from a string. It provides for overflow and the pointer to the last character allows for testing for valid terminating characters. This set the range to 0 and INT_MAX but any range from INT_MIN to INT_MAX could be used. The terminating character is nul but could be comma, semicolon or any appropriate character.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
//inputs
// char *line : pointer to text to be parsed
// char **next : pointer to pointer to allow modification of caller's pointer
// char *term : pointer to characters to be considered terminators
// int *value : pointer to int to allow modification of caller's int
// int min : minimum value of range
// int max : maximum value of range
// returns : 0 failure or 1 success
int get_int_range ( char *line, char **next, char *delim, int *value, int min, int max)
{
long int input = 0;
char *end = NULL;//will point to end of parsed value
if ( line == NULL) {
return 0;
}
errno = 0;
input = strtol ( line, &end, 10);//get the integer from the line. end will point to the end of the parsed value
if ( end == line) {// nothing was parsed. no digits
printf ( "input [%s] MUST be a number\n", line);
return 0;// return failure
}
// *end is the character that end points to
if ( *end != '\0' && !( delim && strchr ( delim, *end))) {// is *end '\0' or is *end in the set of term characters
printf ( "problem with input: [%s] \n", line);
return 0;
}
if ( ( errno == ERANGE && ( input == LONG_MAX || input == LONG_MIN))
|| ( errno != 0 && input == 0)){// parsing error from strtol
perror ( "input");
return 0;
}
if ( input < min || input > max) {// parsed value is outside of range
printf ( "input out of range %d to %d\n", min, max);
return 0;
}
if ( next != NULL) {// if next is NULL, caller did not want pointer to end of parsed value
*next = end;// *next allows modification to caller's pointer
}
if ( value == NULL) {
return 0;
}
*value = input;// *value allows modification to callers int
return 1;// success
}
int main( int argc, char *argv[])
{
char line[900] = {'\0'};
int valid = 0;
int number = 0;
do {
printf ( "Enter number or enter quit\n");
fgets ( line, sizeof ( line), stdin);//read a line
if ( strcmp ( line, "quit\n") == 0) {
return 1;// if quit is entered, exit the program
}
line[strcspn ( line, "\n")] = '\0';//remove trailing newline
valid = get_int_range ( line, NULL, "", &number, 0, INT_MAX);// call to parse a value
} while ( !valid);// on failure, keep looping the above
printf ( "input is %d\n", number);
return 0;
}

Related

How to join two numbers of type char and make them int type

Essentially I have a line which ends in two numbers. I can read the numbers eg) '4' and '1'. I want to concatenate them into '41' and then read that as an int type of value 41. Converting a single character to int is straight forward but how would this work for two (or more) characters?
I am grabbing the characters using:
int first_digit = ctoi(line[1]);
int second_digit = ctoi(line[2]);
where ctoi is defined as :
int ctoi( int c ) // https://stackoverflow.com/a/2279401/12229659
{
return c - '0';
}
Easiest way would be to use a function such as sscanf (provided that line is a proper string)
int num;
if (sscanf(line, "%d", &num) != 1) {
// handle conversion error
}
Although, scanf in general doesn't provide protection from arithmetic overflow, so for a big number it will fail (and you won't be able to track it).
strtol and friends, will fail (and will let you know) when you exceed the range.
You could however build your own function, again with no overflow protection:
#include <ctype.h>
#include <stdlib.h>
int stringToInt(char *str) {
int num = 0;
size_t start = (*str == '-') ? 1 : 0; // handle negative numbers
for (size_t i = start; str[i] != '\0'; i++) {
if (isdigit((unsigned char)str[i]) == 0) { // we have a non-digit
exit(1); // ideally you should set errno to EINVAL and return or terminate
}
num = (num * 10) + (str[i] - '0');
}
return (start) ? -num : num;
}

the program, receiving an integer (as a string), after converting the data type perceives it as a string

The program receives the data, but treats the string ("20x") as an integer.(This is not my code, I found it on the internet.)
// C program to find data type
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
# define MAX_INPUT 100
int main()
{
// To read input
char value[MAX_INPUT] = "";
// To store numeric value of input if a
// number (float or integer)
double temp;
// To store integral value of input
int n;
// To store string value of input
char str[MAX_INPUT] = "";
// Precision for integer checking
double val = 1e-12;
fgets(value, 100, stdin); // Read input
// Check for integers.
if (sscanf(value, "%lf", &temp) == 1)
{
n = (int)temp; // typecast to int.
if (fabs(temp - n) / temp > val)
printf("The input is a floating point\n");
else
printf("The input is an integer\n");
}
// Check for string
else if (sscanf(value, "%s", str) == 1)
printf("The input is a string\n");
else // No match.
printf("input not recognized\n");
}
So, If we enter "20x" for example, we will get a message "The input is an integer".
I tried changing datatypes and array values, but it still didn't work as it should. Maybe it's a bug of this language.
Thank you.
OP's code fails to check for trailing non-numeric text.
Further, the fabs(temp - n) / temp > val is at best - dodgy. At worst, it is worthless - consider value < 0 always reports as "The input is an integer\n".
(int)temp is UB when temp far out of int range.
A robust test of a string for a valid int does not need a floating point test.
To check if the input string is a integer, directly use strtol() instead of sscanf(). It is more tightly defined.
char *endptr;
errno = 0;
long lval = strtol(value, &endptr, 0);
// If no conversion or out of range.
if (endptr == value || errno == ERANGE) {
printf("The input is not an `long`\n");
} else {
// Tolerated trailing white-space.
while (isspace(*(unsigned char *)endptr)) {
endptr++;
}
// If junk at the end or out of int range.
if (*endptr != 0 || lval < INT_MIN || lval > INT_MAX) {
printf("The input is not an `int`\n");
} else {
printf("The input is an int\n");
}
}
Or not quite as robust, use "%n" to record scanning offset. It does not certainly detect overflow (UB).
int n = 0;
int val;
// v---- Optional spaces
// | v-- Scanning offset
sscanf(value, "%d %n", &val, &n);
if (n && value[n] == 0) {
puts("Success");
} else {
puts("Fail");
}
The key of this program is sscanf() and what the function returns. The sscanf() function returns the number of fields that were successfully converted and assigned. The return value does not include fields that were read but not assigned.
The return value is EOF when the end of the string is encountered before anything is converted. source
The program first checks if there are any double or integer values on the string. Then it checks for non-numeric character strings. So if the input is "20X" or any "1234abcd" the temp value is successfully assigned as 20(for 20X input) or 1234 (for 1234abcd input). So the final verdict is an integer.
Hope this code will help in your purpose to find the datatype of input.
// C program to find data type
#include <stdio.h>
#include <string.h>
# define MAX_INPUT 100
void check_type(char strIn[]){
int dotFlag = 0 ;
int dotPos = 0 ;
int firstSignCheck = 1 ;
int len = strlen(strIn) ;
//checking for string and counting '.'
for (int i = 0 ; i < len ; i ++ ) {
if((strIn[i] == '+' || strIn[i] == '-') && firstSignCheck == 1 ) //skipping first '+' and '-'
continue ;
else
firstSignCheck = 0 ;
if((strIn[i] < '0' || strIn[i] > '9') && strIn[i] != '.') { //if any character out of 0-9 and '.' it is string
printf("The input is string") ;
return ;
}
if(strIn[i] == '.'){ //counting dots and keeping the position
dotFlag ++ ;
dotPos = i ;
}
}
if(firstSignCheck == 1) { // if all are '+' or '-'
printf("The input is string") ;
return ;
}
if(dotFlag > 1) { //if several '.' its a string
printf("The input is string") ;
return ;
}
else if(dotFlag == 0 ) { // no '.' integer
printf("The input is integer") ;
return ;
}
else{
for (int i = dotPos + 1 ; i < len ; i++) { // after '.' checkin for zeros
if(strIn[i] != '0') { // any nonzeros float
printf("%c" ,strIn[i] ) ;
printf("The input is float/double") ;
return ;
}
}
printf("The input is integer") ; // no non zero ineger
return ;
}
}
int main()
{
// To read input
char value[MAX_INPUT] = "";
// To store string value of input
char str[MAX_INPUT] = "";
//input string
scanf("%s" , value) ;
//checking datatype
check_type(value) ;
}

C Program doesn't end after giving the correct output

So I'm trying to do a program that reads a sequence of numbers separated by spaces and new lines. The output should be the same sequence, but erasing unnecessary zeros(The sequence of charachters 'EOF' ends the program). Per example
01492 102934 should come out as 1492 102934
9312 0 01923 should come out as 9312 0 1923
0001249 0000 should come out as 1249 0
Well I've achieved that purpose but have come across a roadblock. The program doesn't exit unless I type the EOF sequence. Maybe it's because I have a while(1) running that gives an infinite loop. But when I try to delete it the program doesn't even print at all. I'm still learning this is for a school project.
Any help would be apreciated!
Here's the code:
#include <stdio.h>
int main(){
char c;
int i=0;
while(1){
c=getchar();
if (i==0){
if(c=='0'){
while (c=='0'){
c=getchar();
}
}
printf("%c",c);
i=i+1;
}
else if (c==' '){
printf("%c",c);
c=getchar();
if(c=='0'){
while (c=='0'){
c=getchar();
}
}
printf("%c",c);
}
else if (c=='E'){
c=getchar();
if (c=='O'){
c=getchar();
if(c=='F'){
printf("\n");
return 0;
}
}
}
else{
printf("%c",c);
}
}
}
The important stuff:
int c; // IMPORTANT, cannot be char
while (1) {
c = getchar();
if (c == EOF) break; // exit loop
// ...
}
There has to be some way to tell the program to exit.
With this, the program will exit on the letter x or two consecutive newlines or entering END.
getchar will return EOF when there is nothing left to read from a file. That can be simulated from stdin ( the keyboard) with ctrl + z on Windows or ctrl + d on Linux.
#include <stdio.h>
#include <string.h>
int main ( void) {
char done[4] = "";
int c = 0;
int prior = 0;
int reading = 0;
int zero = 1;
while ( EOF != ( c = getchar ( )) && 'x' != c) {
if ( '\n' == c && '\n' == prior) {
break;
}
if ( c >= '0' && c <= '9') {
reading = 1;
if ( '0' != c) {
zero = 0;
}
if ( ! zero) {
putchar ( c);
}
}
else {
if ( reading) {
if ( zero) {
putchar ( '0');
}
if ( ' ' == c || '\n' == c) {
putchar ( c);
}
else {
putchar ( ' ');
}
}
reading = 0;
zero = 1;
}
prior = c;
done[0] = done[1];
done[1] = done[2];
done[2] = c;
done[3] = 0;
if ( 0 == strcmp ( done, "END")) {
break;
}
}
putchar ( '\n');
return 0;
}
getchar() returns an int, not a char. If it only returned a char, there would be no way for it to return a value that indicates end of file, since all char values are valid and can’t be used for another purpose.
A motivating example in decimal system may be: A function checks the temperature returns a two-digit number. Any temperature between 0 and 99 is valid. How do you report errors when the thermometer is disconnected? You have to return a number with more digits, and use a special value like UNPLUGGED = 100.
But int is a wider type: it has many more values than char, and the “extra” values can be used to indicate some special condition that means “hey, this is not a valid character, but something else I had to tell you”.
getchar() returns the EOF constant upon failure (any failure), for example if no more input is available. There’s nothing sensible you can do even if the reason for the failure other than end of input. You should end processing at the first EOF.
Thus, change the type of c to int, and every time you call getchar(), you must check that its value is not EOF, and return when you encounter it.
The nested structure of your loops means that EOF checking has to be repeated all over the place. There are other ways to structure the code to keep this check in one place, but, admittedly, the nested loops have at least the potential to exploit the branch predictor, whereas a single getchar followed by a state-machine style switch statement will make it perform potentially worse. None of this matters in a simple homework problem, but it’s something to keep in mind. In any case, performance has to be benchmarked - no other way around it.
Try this code, I think it does what you requested:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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 -2;
// 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) ? -1 : 0;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff) - 1] = '\0';
return 0;
}
int* convert2numbers(char* arr, int size) {
int i;
int j;
int k;
char token[100];
int* numbers;
int last_space = 0;
int index = 1;
int amount = 1;
// Count the amount of tokens.
for (i = 0; i < size; ++i) {
if (arr[i] == ' ') {
++amount;
}
}
numbers = (int *)malloc(amount * sizeof(int));
numbers[0] = amount;
for (j = 0; j <= size; ++j) {
if (arr[j] == ' ' || arr[j] == '\0') {
// Copy token from input string.
for (k = 0; k < j; ++k) {
token[k] = arr[k + last_space];
}
token[j] = '\0';
numbers[index] = atoi(token);
// Clear the token and continue.
memset(token, '\0', sizeof(token));
last_space = j;
++index;
}
}
return numbers;
}
int main(void) {
int i;
int size;
int* numbers;
int amount;
char input[100];
char help[] = "Numbers> ";
printf("Input numbers below or press enter to exit!\n");
while (1) {
getLine(help, input, sizeof(input));
// If input is empty exit.
if (input[0] == '\0') {
break;
}
size = strlen(input);
numbers = convert2numbers(input, size);
amount = numbers[0];
for (i = 1; i < amount + 1; ++i) {
printf("%d ", numbers[i]);
}
printf("\n");
}
return 0;
}
When run with these inputs this code outputs:
Input numbers below or press enter to exit!
Numbers> 01492 102934
1492 102934
Numbers> 9312 0 01923
9312 0 1923
Numbers> 0001249 0000
1249 0
Also if you press enter in console, it exits, as to escape the while(1) loop, easily.

Reading integers with commas between them

I assumed using strtok would be best because of the formatting of the input.
But I've run into a few problems when trying to detect errors:
an example of a line the program would read:
.data 123,456,89
.data 12, 34, 53 , 64
these are all ok.
My problem is when the input is incorrect, for example:
.data 200 4000 // no comma speration
.data 1, ,3 // ,3 should be an error
.data 4, // the extra , should be an error
.data 12.2 // the .2 should be an error
and so on
My code (SIZE is for buffer size = 30, valid_num goes through the token to see if all the chars are numbers), the idea was to first check the validity of the tokens and add them to a buffer, if all numbers are valid, add the numbers to my data base:
while((sptr = strtok(NULL, ", \t\n")) != NULL){ //this is after reading using strtok before.
if(i < SIZE && valid_num(sptr)){ //buffer is not full and the token contains only numbers
temp_num = atoi(sptr);
if(temp_num >= MIN_VAL && temp_num <= MAX_VAL){ //number is within the required size
buffer[i] = temp_num; /*fill buffer*/
i++;
}
else{
fprintf(stderr, "(%d) Error: %d is out of bounds. Valid numbers are between %d and %d\n", line_count, temp_num, MIN_VAL, MAX_VAL);
}
}
else{
fprintf(stderr, "(%d) Error: %s is not a valid number\n",line_count, sptr);
}
tok_count++;
}
if(i == tok_count){ //if all tokens were read correctly, add the data to database.
DC += add_data(buffer, tok_count, DC, data_Table);
}
else{
if(sptr != NULL){
fprintf(stderr, "(%d) Error: %s is not a digit, .data can only be used for integers\n", line_count, sptr);
}
}
Should I try to do the same but with sscanf, even though the length of the input is unknown?
How can I enforce a certain pattern? number - comma - number ...
Perhaps using a few different strtok inside the loop?
There are many ways to parse the line.
OP's temp_num = atoi(sptr); does not detect overflow as 1) overflow with atoi() is undefined and 2) there is no error return value.
I believe the below will cope with all hostile input. It does not use strtok(), but strtol() to find non-numeric input.
Making use of helper functions provides clarity of each step.
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
void consume_whitespace(char **input) {
while (isspace((unsigned char ) **input))
(*input)++;
}
int parse_int(char **input, int *dest) {
char *endptr;
errno = 0;
long y = strtol(*input, &endptr, 10);
if (*input == endptr) return -1; // no conversion
if (errno) return -1; // overflow
#if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
if (y < INT_MIN || y > INT_MAX) return -1; // overflow
#endif
*input = endptr;
*dest = (int) y;
return 0;
}
int parse_data_line(char *input, const char *prefix, int *dest, int n) {
size_t prefix_length = strlen(prefix);
if (memcmp(input, prefix, prefix_length)) return -1;
input += prefix_length;
int i;
for (i = 0; i < n; i++) {
consume_whitespace(&input);
if (*input == '\0') break;
if (i > 0 && *input++ != ',') return -1;
if (parse_int(&input, &dest[i])) return -1;
}
consume_whitespace(&input);
if (*input) return -1; // extra text
return i;
}
Example usage
#define SIZE 30
int main() {
int numbers[SIZE];
char *input = foo();
int count = parse_data_line(input, ".data", numbers, SIZE);
if (count < 0) puts("Fail");
else bar(numbers, count);
}

Display error message on second loop of do-while

If I have a while or do while loop in C, is there some (native) way I can have something happen on the second loop?
I ask for getting inputs; I have this:
int size;
do {
printf("Size of tower (0 <= x <= 23): ");
scanf("%i", &size);
} while (size > 23 || size < 0);
If the user inputs some value which is not between 0 and 23, I want to display an error message and ask for another value. Obviously I could do it like this:
int size;
int error = 0;
do {
if (error) { printf("Invalid size\n"); }
printf("Size of tower (0 <= x <= 23): ");
scanf("%i", &size);
error = 1;
} while (size > 23 || size < 0);
However, this feels gross. I'm looking for an elegant solution, and I figure that having something run on the second loop would work.
I think you want something like this:
int size = -1;
int MAX_TRIES = 10;
while (MAX_TRIES--)
{
printf("Size of tower (0 <= x < 23): ");
if (scanf("%i", &size) != 1)
{
printf("Read error!!\n");
break;
}
if (size >= 0 && size < 23)
{
break;
}
printf("Error: You entered '%d' which is not in the range 0 <= x < 23\n", size);
}
By writing it this way, you won't have to compute the negation of your boolean condition logic mentally while writing the code.
Also, checking for the return value of scanf() is important. Thanks to Weather Vane's comment for reminding this.
Further, it is probably better to limit the number of executions of this loop rather than letting it run till infinity. ( Thanks to Jonathan Leffler's comment )
The conversion and error reporting could be moved to a function to simplify the calling code. Input is taken by fgets and the value is parsed in the function by strtol. This function returns success or failure. Other values get back to the caller through pointers.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
int get_int_range ( char *line, char **next, char *delim, int *value, int min, int max);//prototype
int main( int argc, char *argv[])
{
char line[100] = {'\0'};//input buffer
int valid = 0;
int size = 0;
do {
printf ( "Size of tower (0 <= x <= 23) or quit\n");
fgets ( line, sizeof ( line), stdin);//read a line
if ( strcmp ( line, "quit\n") == 0) {
valid = 0;
break;// if quit is entered, exit loop
}
valid = get_int_range ( line, NULL, "\n", &size, 0, 23);// call to parse a value
} while ( !valid);// on failure, keep looping the above
if ( valid) {
printf ( "Size of tower is %d\n", size);
}
return 0;
}
//inputs
// char *line : pointer to text to be parsed
// char **next : pointer to pointer to allow modification of caller's pointer
// char *term : pointer to characters to be considered terminators
// int *value : pointer to int to allow modification of caller's int
// int min : minimum value of range
// int max : maximum value of range
// returns : 0 failure or 1 success
int get_int_range ( char *line, char **next, char *delim, int *value, int min, int max)
{
long int input = 0;
char *end = NULL;//will point to end of parsed value
if ( line == NULL) {
printf ( "no text to parse\n");
return 0;
}
if ( value == NULL) {
printf ( "unable to save parsed value\n");
return 0;
}
errno = 0;
input = strtol ( line, &end, 10);//get the integer from the line. end will point to the end of the parsed value
if ( ( errno == ERANGE && ( input == LONG_MAX || input == LONG_MIN))
|| ( errno != 0 && input == 0)){// parsing error from strtol
perror ( "input");
return 0;
}
if ( end == line) {// nothing was parsed. no digits
line[strcspn ( line, "\n")] = '\0';//remove newline
printf ( "input [%s] MUST be a number\n", line);
return 0;// return failure
}
// *end is the character that end points to
if ( *end != '\0' && !( delim && strchr ( delim, *end))) {// is *end '\0'? is *end in the set of term characters?
line[strcspn ( line, "\n")] = '\0';//remove newline
printf ( "problem with input terminator: [%s] \n", line);
return 0;
}
if ( input < min || input > max) {// parsed value is outside of range
printf ( "input out of range %d to %d\n", min, max);
return 0;
}
if ( next != NULL) {// if next is NULL, caller did not want pointer to end of parsed value
*next = end;// *next allows modification to caller's pointer
}
*value = input;// *value allows modification to callers int
return 1;// success
}
My preferred technique would involve duplicating the input, but would simplify your loop:
printf("Size of tower (0 <= x <= 23): "); // Try to get good input once.
scanf("%i", &size);
while (size < 0 || 23 < size ) { // While the user is wrong:
printf("Invalid size\n");
printf("Size of tower (0 <= x <= 23): "); // Try, try again.
scanf("%i", &size);
}

Resources