Unsigned and Two’s Complement Encodings
Let x be a bit vector of length w. Then if x represents an unsigned integer the decimal value can be computed as
One way to encode signed integers is using two’s complement encoding. If x is interpreted as a signed integer, the most significant bit represents the sign, and the decimal value is computed as
For positive numbers the sign bit is 0, thus the first term is always 0. The magnitude of a positive number therefore is just the unsigned value (above) of the remaining w-1 bits.
For negative numbers the sign bit is 1 giving the first term a value equal to the largest negative number possible with w bits. The second term can be thought of as reducing the negative number.
Binary value | First term | Second term | Decimal value |
01111111 | 0 | 127 | 127 |
… | |||
00000011 | 0 | 3 | 3 |
00000010 | 0 | 2 | 2 |
00000001 | 0 | 1 | 1 |
00000000 | 0 | 0 | 0 |
10000000 | -128 | 0 | -128 |
10000001 | -128 | 1 | -127 |
10000010 | -128 | 2 | -126 |
10000011 | -128 | 3 | -125 |
… | |||
11111111 | -128 | 127 | -1 |
If w is 8, the largest negative integer is [10000000] with a decimal value of -128 and the largest positive integer is [01111111] with a decimal value of 127.
By default all integral values (including char) are signed unless specified otherwise with the unsigned keyword.
Limits
The C header file limits.h defines a set of constants delimiting the ranges of the different integer data types. For example: INT_MAX, INT_MIN, etc.
Conversion Between Signed and Unsigned
Sometimes we need to convert between signed and unsigned numbers. The printf function can perform conversions for us.
char y = 16; // 0001 0000 printf("signed: %hhd unsigned: %hhu\n", y, y);
When run, the program will print the signed value (16) and then interpret the byte as an unsigned value. When viewed as an unsigned value, the sign bit is 0, thus having the same value as when interpreted as a signed number (16).
signed: 16 unsigned: 16
Lets now examine what happens with a signed negative number.
y = -16; // 1111 0000 printf("signed: %hhd unsigned: %hhu\n", y, y);
When run, the program prints
signed: -16 unsigned: 240
Notice that when the signed value is interpreted as an unsigned number the value becomes . This may not be the expected value. This illustrates we must be careful when converting between sign and unsigned types.
Expanding the Bit Representation of a Number
Converting an unsigned number to a larger data type entails padding the left with 0’s. This is called zero extension.
[1111 0000] -> [0000 0000 1111 0000]
For converting a 2’s complement number to a larger data type, the rule is to perform a sign extension by adding copies of the most significant bit to the representation.
[1111 0000] -> [1111 1111 1111 0000]
Truncating Numbers
When truncating an unsigned number of length w to length k, we get a value equivalent to .
For example, if we truncate the short unsigned int, [0000 0001 0100 1000] = 328, to an 8-bit number we get [0100 1000] .
Truncating a signed number of length w to length k we get a value equivalent to .
For example, if we truncate the short signed int, [1000 0000 0000 0001] = -32768 + 1 = -32767, to an 8-bit signed number. We get [0000 0001] which is equivalent to .
Lets look at another example, where the . Lets truncate the short signed int, [1000 0000 1000 0001] = 32897, to an 8-bit signed number. We get [1000 0001] .
Advice
Use signed numbers whenever possible.
© 2017 – 2020, Eric. All rights reserved.