1

An assignment is requesting that I qualify input number to ensure that it is within range of an unsigned-int on my machine. How do I determine what that range is? it says that the input needs to be taken in at execution time and that no assumptions can be made about what was entered.

I tried using plugging in different ranges (2^7 , 2^15 , 2^31) but they all resulted in overflow.

Mat
  • 202,337
  • 40
  • 393
  • 406
Anthu.ny
  • 9
  • 2
  • 1
    Are you supposed to do it by reading the whole string and using `strtoul` or a related function? Or are you supposed to do it by reading the individual characters and interpreting them yourself? What did your class study recently? – Eric Postpischil Nov 12 '22 at 19:39
  • 4
    You are probably looking for [`UINT_MAX` from ``](https://en.cppreference.com/w/c/types/limits). Of course, for this to make any sense, you will need to have read your number into a type *larger* than `unsigned int` (or be parsing it from a string as you go). – Nate Eldredge Nov 12 '22 at 19:50
  • 1
    A int can never be bigger than max int, since max int is the maximum value of int. – mousetail Nov 12 '22 at 20:04
  • 1
    [`strtoul()` What is the correct return value for very "negative" strings?](https://stackoverflow.com/q/60955490/2410359) may be useful. – chux - Reinstate Monica Nov 12 '22 at 21:04
  • 1
    Also see [How to verify that user input number is in fact a valid unsigned int in c](https://stackoverflow.com/q/32600232/2410359)? – chux - Reinstate Monica Nov 12 '22 at 21:35

3 Answers3

2

To test if an string converts to a unsigned

Use strtoul() to convert to a unsigned long. Yet since that function rolls over negative text to positive integers, use strtol() first.

bool valid_unsigned(const char *s, unsigned *uval) {
  char *endptr;
  errno = 0;
  int base = 0; // Use 10 here if only decimal text acceptable.
  long lvalue = strtol(s, &endptr, base);
  if (s == endptr) {
    *uval = 0;
    return false;  // No conversion
  }
  if (lvalue < 0) {
    errno = ERANGE;  // Perhaps calling code would like to test this.
    *uval = 0;
    return false; // Negative
  }
  if ((unsigned long) lvalue <= UINT_MAX && errno == 0) {
    *uval = (unsigned) lvalue;
    return true; // Success
  }

  #if UINT_MAX > LONG_MAX
    // Still could be a value in the LONG_MAX...UINT_MAX range.
    errno = 0;
    unsigned long uvalue = strtoul(s, &endptr, base);
    if (uvalue <= UINT_MAX && errno == 0) {
      *uval = (unsigned) uvalue;
      return true;  // Success
    }
  #endif
  errno = ERANGE;
  *uval = UINT_MAX;
  return false;  // Too big.
} 

To do: Test trailing text.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

If you are using the function strtoul for converting the string input to an integer, then you don't need to determine yourself whether the input is larger than ULONG_MAX. The function strtoul will report this, by setting the value of errno accordingly. However, depending on the platform, an unsigned long may be able to represent more numbers than an unsigned int. Therefore, after the function strtoul reports that the input range is ok, you should additionally verify that the number is not larger than UINT_MAX.

Another problem with using the function strtoul is that it will accept negative numbers as valid. We must therefore check ourselves whether the first non-whitespace character is a minus sign.

Here is an example program which uses a function get_unsigned_int_from_user which will continue prompting the user for input, until the input is valid and in the range of an unsigned int:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

unsigned int get_unsigned_int_from_user( const char *prompt )
{
    for (;;) //loop forever until user enters a valid number
    {
        char buffer[1024], *p, *q;
        unsigned long ul;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "unrecoverable error reading from input\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "unrecoverable error reading from input\n" );
                    exit( EXIT_FAILURE );
                }

            } while ( c != '\n' );

            continue;
        }

        //make "p" point to first non-whitespace character
        for ( p = buffer; isspace( (unsigned char)*p ); p++ )
            ;

        //since the function "strtoul" accepts negative
        //input as valid input, which we don't want, we must
        //first check ourselves whether the input starts
        //with a minus sign
        if ( *p == '-' )
        {
            printf( "number must not be negative!\n" );
            continue;
        }

        //attempt to convert string to number
        errno = 0;
        ul = strtoul( p, &q, 10 );
        if ( q == p )
        {
            printf( "error converting string to number\n" );
            continue;
        }

        //make sure that number is representable as an "unsigned int"
        if ( errno == ERANGE || ul > UINT_MAX )
        {
            printf( "number out of range error\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6sdfh4q" gets rejected
        for ( ; *q != '\0'; q++ )
        {
            if ( !isspace( (unsigned char)*q ) )
            {
                printf( "unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return ul;

    continue_outer_loop:
        continue;
    }
}

int main( void )
{
    unsigned int num;

    num = get_unsigned_int_from_user( "Please enter a number: " );

    printf( "Input is valid, you entered: %u\n", num );
}

This program has the following behavior:

Please enter a number: -1  
number must not be negative!
Please enter a number: -5000000000
number must not be negative!
Please enter a number: 5000000000
number out of range error
Please enter a number: 4000000000
Input is valid, you entered: 4000000000

The program behaves this way because UINT_MAX has the value 4294967295 on most common platforms.

The function get_unsigned_int_from_user is a modified version of my function get_int_from_user from this answer of mine. See that answer for further information on how that function works.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • Technically, the program should not reject a sequence of 2000 zeroes... – chqrlie Nov 12 '22 at 20:44
  • Andreas Wenzel, Try `errno = 0; printf("%lu\n", strtoul("-1", NULL, 10)); printf("%d\n", errno);`. I get "18446744073709551615 0". (pc gcc). – chux - Reinstate Monica Nov 12 '22 at 20:45
  • The value `-1` is reported as out of range because `ULONG_MAX > UINT_MAX` on your system, which is coincidental – chqrlie Nov 12 '22 at 20:47
  • @AndreasWenzel To be fair, it's not a matter of the *compiler* implementation but the *library* implementation... – DevSolar Nov 12 '22 at 22:19
  • @AndreasWenzel Concerning `strtol()` and `strtoul()`, this [pic](https://i.stack.imgur.com/k9fUQ.png) helps visualize the situation. – chux - Reinstate Monica Nov 12 '22 at 22:24
  • Corner case: How to handle `"-0"`? The _value_ of zero is representable as an `unsigned` even though it beings with a `-` : a pesky corner case. – chux - Reinstate Monica Nov 12 '22 at 22:52
  • @chux: Well spotted! I don't think I will make the effort of attempting to fix this corner case. Using `strtol` before `strtoul` for the sole sake of detecting the sign (as you do in your answer) seems rather wasteful of CPU resources, but it probably is the simplest and most reliable solution. – Andreas Wenzel Nov 12 '22 at 23:14
  • @AndreasWenzel Note that in the [answer](https://stackoverflow.com/a/74416660/2410359) the call to `strtoul()` only conditionally occurs: values more than `LONG_MAX` else it is just the one call to `strtol()`. – chux - Reinstate Monica Nov 13 '22 at 05:25
  • @chux: Ah, yes, that is a good idea. That way, in most common cases, only one of the functions must be called. – Andreas Wenzel Nov 13 '22 at 11:05
0
  1. Each implementation of the C Standard made choices about the size of fundamental (here integer) types. So an int is guaranteed to be at least 16 bits wide, but it is possible to be 32 or 64 bits wide. See: Integer types and Data models.

  2. For simplicity, we assume that an int is 32 bits. Then the value of UINT_MAX would be: 4294967295. This token is 10 digits long. So your input is definitively out of range if it is longer than 10 digits (@chux-ReinstateMonica made a good point in the comments below, e.g. an input of "+000000000000000001" would be longer than 10 digits but still in the range of an unsigned int).

  3. Even if your input has 10 digits, it still could be greater than UINT_MAX. Therefore, parse the string into an unsigned long (strtoul) and test if the value is less than or equal to UINT_MAX.

Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11