C++ Signed Unsigned Mismatch

·

3 min read

Imagine that you have a function that returns an int value. This function has a parameter named x, which is passed into the function when it's called. The function should return the value of x + 1 if x is positive or -1 if x is negative. But what happens if you call the function with an unsigned integer? You might expect it to work and always return 0, but this isn't necessarily true! We'll go over why and how we can fix this problem with C++ signed unsigned mismatches

Image description

What is C++ signed unsigned mismatch

It comes from a very general rule that states that if both operands of an operator are the same size, with one being unsigned and the other signed, the signed operand is transformed to unsigned before the operator is ever executed. When this general rule is applied to comparisons, it implies that if a negative signed integer x is compared to an unsigned one y, the signed integer will first be transformed into an unsigned one. As our signed integer x is negative at this point, and at least when our system utilizes the universal-in-practice "two's complement" representation 2, it will become a very-large unsigned value (2N+x, where N is the number-of-bits-in-our-int-type). Then our desired comparison operator will be used to compare 2N+x with y, which is usually not what we intended when we created our code. In this case,

static_assert( -1 < 1U ); //C++ signed unsigned mismatches

Why this furious message will appear

A signed unsigned mismatch is an error that can be caused when one or more variables are incorrectly cast.

In C++, integer types are represented as sign-and-magnitude integers. The sign bit is the leftmost bit and it indicates whether a number is positive or negative. For example, in the number 23, the sign bit of 2 indicates that 2 is negative because 2 has a 1 in its most significant bit (MSB). In contrast, 0 doesn't have any 1s in its MSB so it's considered positive; 23 << 1 gives us a value of -46 but 0 << 1 gives us an answer of 64 because 0 has no signs to negate!

The same principle applies with non-integral types: if you add a character 'A' (which corresponds to ASCII code 97) to 907104902504045361234151609710^98 then your result will always be 907104902504045361234151609710^98 regardless whether your system considers them integral or not because there are no leading zeros before the decimal point that could change this fact.

The Solution

In the near run, altering a type (typically from signed to unsigned) – which is normally an ideal solution for this kind of issue at least in terms of performance – is not that much safer than explicit casting (though it is indeed less fragile in the long run). For each such type change, I generally prefer to add an assert(x>=0) (to ensure that the value-which-I-think-should-never-be-negative, is indeed never negative; as long as this stands, the conversion from signed to unsigned becomes perfectly safe). Even so, such a claim (which we may or may not encounter during our tests) does not qualify as truly safe.

int main() 
{
    unsigned int uc = 0;
    int c = 0;
    unsigned int c2 = c; // implicit conversion

    if (uc < c)           // C++ signed unsigned mismatches 
        uc = 0;

    if (uc < unsigned(c)) // OK
        uc = 0;

    if (uc < c2)          // Also OK
        uc = 0;

}

The Conclusion

In conclusion, I have to say that this was a very fun experience for me. I learned about many things that I did not know before, especially about working with different types of data. I hope you enjoyed reading this blog post as much as I enjoyed writing it!