Expressions Involving Unsigned Types
Although we are unlikely to intentionally assign a negative value to an object of unsigned type, we can (all too easily) write code that does so implicitly. For example, if we use both unsigned and int values in an arithmetic expression, the int value ordinarily is converted to unsigned. Converting an int to unsigned executes the same way as if we assigned the int to an unsigned:
- unsigned u = 10;
- int i = -42;
- std::cout << i + i << std::endl;
- std::cout << u + i << std::endl;
In the first expression, we add two (negative) int values and obtain the expected result. In the second expression, the int value -42 is converted to unsigned before the addition is done. Converting a negative number to unsigned behaves exactly as if we had attempted to assign that negative value to an unsigned object. The value “wraps around” as described above.
Regardless of whether one or both operands are unsigned, if we subtract a value from an unsigned, we must be sure that the result cannot be negative:
- unsigned u1 = 42, u2 = 10;
- std::cout << u1 - u2 << std::endl;
- std::cout << u2 - u1 << std::endl;
The fact that an unsigned cannot be less than zero also affects how we write loops. For example, in the exercises to § 1.4.1 (p. 13), you were to write a loop that used the decrement operator to print the numbers from 10 down to 0. The loop you wrote probably looked something like
- for (int i = 10; i >= 0; --i)
- std::cout << i << std::endl;
We might think we could rewrite this loop using an unsigned. After all, we don’t plan to print negative numbers. However, this simple change in type means that our loop will never terminate:
-
- for (unsigned u = 10; u >= 0; --u)
- std::cout << u << std::endl;
Consider what happens when u is 0. On that iteration, we’ll print 0 and then execute the expression in the for loop. That expression, --u, subtracts 1 from u. That result, -1, won’t fit in an unsigned value. As with any other out-of-range value, -1 will be transformed to an unsigned value. Assuming 32-bit ints, the result of --u, when u is 0, is 4294967295.
One way to write this loop is to use a while instead of a for. Using a while lets us decrement before (rather than after) printing our value:
- unsigned u = 11;
- while (u > 0) {
- --u;
- std::cout << u << std::endl;
- }
This loop starts by decrementing the value of the loop control variable. On the last iteration, u will be 1 on entry to the loop. We’ll decrement that value, meaning that we’ll print 0 on this iteration. When we next test u in the while condition, its value will be 0 and the loop will exit. Becausewe start by decrementing u, we have to initialize u to a value one greater than the first value we want to print. Hence, we initialize u to 11, so that the first value printed is 10.
CAUTION: DON’T MIX SIGNED AND UNSIGNED TYPES
Expressions that mix signed and unsigned values can yield surprising results when the signed value is negative. It is essential to remember that signed values are automatically converted to unsigned. For example, in an expression like a * b, if a is -1 and b is 1, then if both a and b are ints, the value is, as expected -1. However, if a is int and b is an unsigned, then the value of this expression depends on how many bits an int has on the particular machine. On our machine, this expression yields 4294967295.
EXERCISES SECTION 2.1.2
Exercise 2.3: What output will the following code produce
- unsigned u = 10, u2 = 42;
- std::cout << u2 - u << std::endl;
- std::cout << u - u2 << std::endl;
- int i = 10, i2 = 42;
- std::cout << i2 - i << std::endl;
- std::cout << i - i2 << std::endl;
- std::cout << i - u << std::endl;
- std::cout << u - i << std::endl;
Exercise 2.4: Write a program to check whether your predictions were correct. If not, study this section until you understand what the problem is.