Unsigned keyword in C++

Learn unsigned keyword in c++ with practical examples, diagrams, and best practices. Covers c++, unsigned development techniques with visual explanations.

Understanding the 'unsigned' Keyword in C++

Hero image for Unsigned keyword in C++

Explore the 'unsigned' keyword in C++, its impact on integer types, and how it affects data representation and arithmetic operations. Learn best practices for its use.

In C++, the unsigned keyword is a type modifier that can be applied to integer data types (like int, short, long, long long, and char). Its primary purpose is to specify that the variable can only hold non-negative values (zero and positive integers). This seemingly small change has significant implications for how numbers are stored, their maximum range, and how arithmetic operations are performed.

What 'unsigned' Means for Data Representation

When an integer type is declared as unsigned, the most significant bit (MSB), which is typically used to represent the sign of the number (0 for positive, 1 for negative), is instead used as part of the magnitude. This effectively doubles the positive range of the integer type while eliminating the ability to store negative values. For example, a standard 32-bit int might range from approximately -2 billion to +2 billion, while a 32-bit unsigned int would range from 0 to approximately +4 billion.

#include <iostream>
#include <limits>

int main() {
    int signed_val = -10;
    unsigned int unsigned_val = 10;

    std::cout << "Signed int min: " << std::numeric_limits<int>::min() << std::endl;
    std::cout << "Signed int max: " << std::numeric_limits<int>::max() << std::endl;
    std::cout << "Unsigned int min: " << std::numeric_limits<unsigned int>::min() << std::endl;
    std::cout << "Unsigned int max: " << std::numeric_limits<unsigned int>::max() << std::endl;

    // Attempting to assign a negative value to an unsigned int
    unsigned int negative_attempt = -1;
    std::cout << "Assigned -1 to unsigned int: " << negative_attempt << std::endl;

    return 0;
}

Demonstrating the range of signed vs. unsigned integers and the effect of assigning a negative value to an unsigned type.

Arithmetic with Unsigned Integers

Arithmetic operations involving unsigned integers behave differently than those with signed integers, especially when mixing types or dealing with underflow. When an operation results in a value less than zero for an unsigned type, it 'wraps around' to the maximum possible value for that type. This behavior is well-defined in C++ and is often referred to as modular arithmetic.

#include <iostream>

int main() {
    unsigned int a = 5;
    unsigned int b = 10;
    unsigned int result_subtraction = a - b; // 5 - 10 = -5

    std::cout << "Unsigned subtraction (5 - 10): " << result_subtraction << std::endl; // Will be a very large positive number

    int signed_c = -5;
    unsigned int unsigned_d = 10;
    int mixed_result = signed_c + unsigned_d; // -5 + 10 = 5
    std::cout << "Mixed type addition (-5 + 10): " << mixed_result << std::endl;

    unsigned int mixed_result_unsigned = signed_c + unsigned_d; // -5 + 10 = 5
    std::cout << "Mixed type addition to unsigned (-5 + 10): " << mixed_result_unsigned << std::endl;

    // Comparison between signed and unsigned
    int s_val = -1;
    unsigned int u_val = 1;
    if (s_val < u_val) {
        std::cout << "-1 is less than 1 (as expected)" << std::endl;
    } else {
        std::cout << "-1 is NOT less than 1 (due to implicit conversion)" << std::endl;
    }

    unsigned int large_unsigned = 4294967295U; // Max for 32-bit unsigned int
    unsigned int small_unsigned = 1;
    unsigned int overflow_add = large_unsigned + small_unsigned;
    std::cout << "Unsigned overflow (max + 1): " << overflow_add << std::endl; // Wraps around to 0

    return 0;
}

Illustrating unsigned arithmetic, including underflow and mixed-type comparisons.

flowchart TD
    A[Start: Declare unsigned int] --> B{Operation: Subtract larger value?}
    B -- Yes --> C[Result: Wraps around to large positive number]
    B -- No --> D[Result: Standard positive value]
    A --> E{Operation: Compare with signed int?}
    E -- Yes --> F[Implicit Conversion: Signed int becomes unsigned]
    F --> G[Comparison: May yield unexpected results]
    C --> H[End]
    D --> H[End]
    G --> H[End]

Flowchart illustrating the behavior of unsigned integer arithmetic and comparisons.

When to Use 'unsigned'

The unsigned keyword is best used when you are certain that a variable will never need to store a negative value. Common use cases include:

  • Bit flags and masks: When individual bits represent distinct states or permissions.
  • Counts and sizes: Variables storing the number of items, array sizes, or loop counters (e.g., std::vector::size_type is typically unsigned).
  • Memory addresses: Pointers are often treated as unsigned integers when their raw numerical value is needed.
  • Hash values: Hash functions typically produce non-negative outputs.

However, for general-purpose arithmetic where negative values are possible or where there's a risk of underflow, signed integers are usually safer and more intuitive. Mixing signed and unsigned types in expressions can lead to implicit conversions that might produce unexpected results, as demonstrated in the code example above.