Integer Representation

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

U_w (x) = \displaystyle\sum_{i=0}^{w-1} x_i2^i

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

T_w (x) = -x_{w-1}2^{w-1} + \displaystyle\sum_{i=0}^{w-2} x_i2^i

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 128+(64+32+16)=240.  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 x \mod 2^k.

For example, if we truncate the short unsigned int, [0000 0001 0100 1000] = 328, to an 8-bit number we get [0100 1000] = 328 \mod 256 = 72.

Truncating a signed number of length w to length k we get a value equivalent to -x_{k-1}2^{k} + (x \mod 2^k).

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  0 + (-32767 \mod 256)  = 0 + (1) = 1.

Lets look at another example, where the x_{k-1}=1.  Lets truncate the short signed int, [1000 0000 1000 0001] = 32897, to an 8-bit signed number.  We get [1000 0001]  = -256 + (32897 \mod 256) = -256 + 129 = -127.

Advice

Use signed numbers whenever possible.

© 2017 – 2020, Eric. All rights reserved.