In addition to the integer types short int, int, long int and so on, in C/C++ there are unsigned types that can only store non-negative values. The unsigned types are marked as unsigned (unsigned int etc.), the signed types signed like signed int. The predicate signed can also be omitted. An exception is the type char, which is primarily intended for characters. If used as an shortest integer type, which is also possible, it depends on the implementation whether the type works with or without sign. signed or unsigned must be specified here if the programme is to work with all compilers and systems.
The unsigned types have the advantage that the absulte value of the numbers can be larger, since the sign bit is not needed [1]. Since the standards for C and C++ leave the definition of the exact number ranges for these types to the implementation, only one typical number range can be mentioned here:
type | min | max |
---|---|---|
signed int | -2,147,483,648 | 2.147.483.647 |
unsigned int | 0 | 4.294.967.295 |
Typically, one can assume a doubling of the maximum value for the unsigned types.
The following rules apply to the integer types in C/C++:
- signed and unsigned types each require the same memory space.
- The conversion between signed and unsigned types does not require an explicit operation, it is simply a different interpretation of the number codes. This obviously means that the coding of non-negative numbers must be the same for both types.
Since in C and C++ many type conversions are done implicitly, the use can be sometimes difficult: an unexpected conversion of a negative number into an unsigned number results in a wrong value:
unsigned int base = 10;
int a = -17;
cout << ( a % base ) << endl; // output: 9
The output here is not -7, as one might expect here. The reason is that the int number -17 is converted to an unsigned int number (4294967279), and then modulo 10 is calculated. This conversion is typical: If an operand of a binary operation is is unsigned, the other operand is also converted to an unsigned type.
Since this is not always easy to keep track of, the general recommendation is not to use mixed operations of unsigned and signed types. This can be achieved by an explicit conversion:
unsigned int base = 10;
int a = -17;
cout << ( a % (int)base ) << endl; // output: -7
Another risk in using unsigned types is that the range limit 0 is often reached. Now, loops are often programmed in such a way that the loop is run until the run variable leaves a desired range:
for (unsigned int a = 0; a < 100; a++) // OK.
cout << a << endl;
for (unsigned int a = 99; a >= 0; a--) // Endless loop.
cout << a << endl;
While the first loop works without problems, the second loop does not stop. The problem: an unsigned int variable never becomes less than zero, the test a >= 0 is always true [2]. If a has the value 0 and is decremented again, it is set to the largest (positive) value - C and C++ do not test for overflows or range overruns.
The problem is not so easy to solve. A functioning loop with decreasing values of a has an astonishing form:
for (unsigned int a = 99; a < 100; a--)
cout << a << endl;
[1] | This explanation is a bit simplified. Typically the sign is not stored in a separate bit. |
[2] | A good compiler warnes here, that a>=1 is always true. |