How to Detect Negative Numbers as Parsing Errors When Reading Unsigned Integers

How to detect negative numbers as parsing errors when reading unsigned integers?

Consulting C++03, 22.2.2.1.2/11, the formats are inherited from scanf and friends, which in turn all say that the converted character sequence is "optionally signed", even for the ones with unsigned output. strtoul is the same.

So, I suppose you could say the standard that mandates the behavior is C89 for C++03, C99 for C++11.

Since the - happens to be allowed only as the first character, I suppose that the workaround is to check for it with peek before using operator>>.

Parse string to unsigned int with error handling if string represents negative number

AFAIK, there is no such function/operator in the standard library. You could:

  • Check for - character in the string beforehand, as already suggested, which, while strange, will work AFAICT

    int mystrtoul(char const *s, unsigned &y)
    {
    if (strchr(s, '-') == NULL) {
    y = strtoul(s, NULL, 0);
    return 0;
    }
    return -1;
    }
  • Use strtod() first. It will detect negative numbers and you can then call strtoul() if the number is not negative, like:

    int yastrtoul(char const *s, unsigned &y)
    {
    if (strtod(s, NULL) >= 0) {
    y = strtoul(s, NULL, 0);
    return 0;
    }
    return -1;
    }
  • Do the whole thing yourself, but be careful as detecting overflow is tricky, because of undefined behavior.

std::istream to unsigned numerical value, how to detect negative values?

Streams are allowed to take in negative numbers for unsigned types. It has the same mechanics as

unsigned type foo = -some_value

Since they can take in a negative number the stream will never fail and you will have the normal behavior of assigning a negative number to an unsigned type.

We can add a check for this though in your function. For a type T, T() - T(1) < 0, will only be true if the type is signed, otherwise the subtraction would wrap around and become the largest value T can represent. So, if we check that condition, and the string starts with a '-', then you know it is not a "valid" value. That makes you function look like

template<typename T>
void checkValid( const std::string& val )
{
std::stringstream str1;
T temp1;

str1 << val;
str1 >> temp1;
if ( str1.fail() || str1.bad() || (!(T() - T(1) < T()) && val[0] == '-') )
std::cout << "error, " << val << " is not a valid string value" << std::endl;
else
std::cout << "ok, " << val << " is converted to " << temp1 << std::endl;
}

If your string can have leading whitespace then you will want to replace the val[0] == '-' check with something like val[val.find_first_not_of(" ")] == '-'

How to check against negative numbers (by user input) that are itself unsigned variables?

When the input number is signed, scanf with %u produces the value that results from negating the number in the unsigned type, so it always produces a non-negative result.

The specification of the %u conversion for scanf is in C 2018 7.21.6.2 12:

u Matches an optionally signed decimal integer, whose format is the same as expected for the subject sequence of the strtoul function with the value 10 for the base argument. The corresponding argument shall be a pointer to unsigned integer.

For strtoul, 7.22.1.4 3 says:

… [For base value 10] the expected form of the subject sequence is a sequence of letters and digits representing an integer with the radix specified by base, optionally preceded by a plus or minus sign,…

and 7.22.1.4 5 says:

… If the subject sequence begins with a minus sign, the value resulting from the conversion is negated (in the return type).

Thus, for input characters “-1”, scanf converts “1” to an unsigned value of 1 and then applies the - operator. Arithmetic in unsigned wraps modulo UINT_MAX+1, so the mathematical negation, −1, wraps to −1+UINT_MAX+1, which is UINT_MAX.

To test whether an input is negative, you can read the individual characters and check for a “-” character. To do this, you can either accumulate characters in a temporary buffer and then use sscanf to process them, or you can skip white space characters until you see either a digit or a “-” (or some other character, which you would treat as an error). If it is a “-”, then report an error. If it is a digit, use ungetc to put it back into the input stream, then use scanf.

How to check if the input is negative?

Altough the question doesn't provide the example inputs causing the behavior you notice, you are probably experiencing overflow issues.

int type, in most modern machine swhere it is stored using 4 bytes, can represent integers from -2,147,483,648 to 2,147,483,647. If you provide a value greater than the maximum positive number (INT_MAX) the value would require a bigger number of bytes to be stored, so in the one actually stored the information of the most significant bits is lost, and the resulting value might end up in the negative range. The same can happen also for big negative values.

What you can do is using long integers so that 8 bits are used and the range is bigger: from -9223372036854775808 to 9223372036854775807.

long num;
scanf ("%ld", &num);

What I would like to emphatize is that numbers representation in computers will always be limited whatever is the number of bytes you use. If these limits are not ok for your application requirements you will have to change your approach, either

  • using strtol, which warns you with a proper return value if an overflow or underflow occurs
  • acquiring the whole string and parsing it yourself

How to safely read an unsigned int from a stream?

You can read into a variable of a signed type that can handle the entire range first and test if it is negative or beyond the maximum of your target type. If your unsigned values may not fit into the largest signed type available, you'll have to do parsing using something other than iostreams.

C++ How to block negative numbers being passed as argument where unsigned is expected

I created a small function template that checks whether the input lies within the range of the output data type, and throws an exception otherwise. It requires that the input and output data types are integral, and that the size of the input data type is larger than the output data type.

template<typename DestType, typename SrcType>
DestType range_check_and_convert(SrcType const& src)
{
static_assert(sizeof(SrcType) > sizeof(DestType),
"SrcType must be larger than DestType");
static_assert(std::is_integral<SrcType>::value &&
std::is_integral<DestType>::value, "integral types only");

if(src > static_cast<SrcType>(std::numeric_limits<DestType>::max())) {
throw std::out_of_range("input too big");
} else if(src < static_cast<SrcType>(std::numeric_limits<DestType>::min())) {
throw std::out_of_range("input too small");
}

return static_cast<DestType>(src);
}

Live demo



Related Topics



Leave a reply



Submit