Unsigned Integers

Understanding Unsigned Integers

Unsigned Integers are integers that can only hold non-negative whole numbers.

Defining unsigned integers

To define an unsigned integer, we use the unsigned keyword. By convention, this placed before the type:

unsigned short ushort;
unsigned int uint;
unsigned long ulong;
unsigned long long ulonglong;

Unsigned integer range

A 1-byte unsigned integer has a range of 0 to 255. Compare this to the 1-byte integer range of -128 to 127. Both can store 256 different values, but signed integers use half of their range for negative numbers, whereas unsigned integers can store positive numbers that are twice as large.

  • An n-bit unsigned variable has a range of 0 to (2 ^ n) - 1.

Unsigned integer overflow

What happens if we try to store the number which requires 9 bits to represent in a 8-bit unsigned integer? The overflow.

If an unsigned value is out of range, it is divided by one greater than the largest number (or the capacity) of the type, and only remainder is kept.

For example, If we try to store the number 280 to 8 bit range of 0 to 255. 1 greater than the largest number of the type is 256. Therefore, we divide 280 by 256, getting 1 quotient and remainder 24. The remainder of 24 is what is stored.

Any number bigger than the largest number representable by the type simply “wraps around” also known as “modulo wrapping”.

#include <iostream>

int main()
{
    unsigned short x{ 65535 }; // largest 16-bit unsigned value possible
    std::cout << "x was: " << x << '\n';

    x = 65536; // 65536 is out of our range, so we get modulo wrap-around
    std::cout << "x is now: " << x << '\n';

    x = 65537; // 65537 is out of our range, so we get modulo wrap-around
    std::cout << "x is now: " << x << '\n';

    return 0;
}

// Output
x was: 65535
x is now: 0
x is now: 1

It is possible to wrap around the other direction as well. 0 is representable in a 16-bits unsigned inter, so that's fine, -1 is not representable, so it wraps around to the top of the range, producing the value 65535, -2 wraps around to 65534, and so on.

#include <iostream>

int main()
{
    unsigned short x{ 0 }; // smallest 2-byte unsigned value possible
    std::cout << "x was: " << x << '\n';

    x = -1; // -1 is out of our range, so we get modulo wrap-around
    std::cout << "x is now: " << x << '\n';

    x = -2; // -2 is out of our range, so we get modulo wrap-around
    std::cout << "x is now: " << x << '\n';

    return 0;
}

// Output
x was: 0
x is now: 65535
x is now: 65534

The controversy over unsigned numbers

Many believe that developers should generally avoid unsigned integers.

This is largely because of the behaviors that can cause problems.

Unexpected behavior can result when you mix signed and unsigned integers. In C++, if a mathematical operation (e.g., arithmetic or comparison) has one signed integer and one unsigned integer, the signed integer will usually be converted to an unsigned integer. And the result will thus be unsigned. For example:

#include <iostream>

// int is 4 bytes
int main()
{
	unsigned int u{ 2 };
	signed int s{ 3 };

	std::cout << u - s << '\n'; // 2 - 3 = 4294967295

	return 0;
}

// Output
4294967295

In this case, if u were signed, the correct result would be produced. But because u is unsigned, s gets converted to unsigned, and the result (-1) is treated as an unsigned value. Since -1 can't be stored in an unsigned value, so we get overflow and an unexpected answer.

Comparison Problems:

When comparing signed and unsigned integers, implicit conversions may occur. This can lead to unexpected behavior, especially when comparing values with different signs. For instance, if a signed integer is compared with an unsigned integer, the signed integer may be implicitly converted to an unsigned integer, potentially leading to incorrect results. Consider the code given below: 

#include <iostream>

// int is 4 bytes
int main()
{
    signed int s { -1 };
    unsigned int u { 1 };

    if (s < u) // -1 is implicitly converted to 4294967295, and 4294967295 < 1 is false
        std::cout << "true, -1 is less than 1\n";
    else
        std::cout << "false, 1 is less than -1\n"; // this statement executes

    return 0;
}

// Output
false, 1 is less than -1

This program is well formed, compiles, and is logically consistent to the eye. But it prints the wrong answer. And while your compiler should warn you about signed/unsigned mismatch in this case,.