The "Go" tools
     The GoAsm manual

understand ....

finite, negative, signed and two's complement numbers

by Jeremy Gordon -

This file is intended for those interested in 32 bit assembler programming, in particular for Windows.

Outside the world of computers, numbers are regarded as having the capacity to be infinite in the sense that if you have a number like 345,000,000 you can just keep adding more zeroes to it and it will get larger and larger.
Computer numbers are necessarily different because the size of the chunk of data being worked with is finite. For example a byte cannot hold a number larger than 255. If in a byte instruction, you add 1 to 255, the number in the byte becomes zero. In practice in this situation the instruction causes the carry flag to be set to 1 to show that the new number was too large for the data size being used. You would then know that the result was not zero, but actually 256.
Because the data size is finite, it is possible to regard the data as having two values at any one time, a positive value and a negative value.
Let me explain. Suppose you have a byte with all its bits set to 1. Then you might say the byte contains the number 0FFh (or 255 decimal). But this can also be regarded as the number -1 (that is, one less than the condition in which the number is too large for the data size being used). You can prove that it is correct to regard the number as being -1 because if you add +1 to -1 the result is zero, which is correct for the data size concerned (although there is a carry).
In programmer's jargon, regarding the chunk of data as possibly holding a negative number means that the number is referred to as a "signed number". Whether or not the number is negative depends on the most significant (leftmost) bit in the chunk of data, which is called the "sign bit". If the data has the sign bit set to 1, this would represent a negative signed number. For example, suppose a byte contains the value 82h. In this case the sign bit is set to 1 so the number is -7Eh (or -126 decimal) if it is regarded as a signed number. Similarly the value 81h in a byte is -7Fh (or -127 decimal). The value 80h in a byte is -80h (or -128 decimal). But the value 7Fh is +7Fh (here the sign bit is clear).
The sign bit is always the leftmost bit so in a byte it will be bit 7, in a word it will be bit 15 and in a dword it will be bit 31. This assumes we are calling the rightmost bit in all these cases bit 0 (which is the traditional thing to do).
After most arithmetic instructions the sign flag is set if the sign bit is set. This can be tested to see if the number is negative after the instruction was carried out.
Note that in a byte the number 0FFh is signed -1, 0FEh is signed -2 and 0F0h is signed -16 (decimal). So this sequence is decreasing. But regarding the numbers as signed means that the sequence is also becoming more negative. Once you get more used to hex numbers this is easier to follow.

One way to calculate the value of a negative signed number is to invert all the binary bits to find its complement, and add one for example

       0F0h in binary is 11110000
       its complement is 00001111
                which is    0Fh
                 add one    10h
                which is 16 decimal
The first step here (the inverting of the binary bits) is called the "one's complement". Adding the extra one is called using the "two's complement". This is why signed numbers are also sometimes called "two's complement" numbers.

One real advantage of this numbering system is that the same instructions for the processor can be used whether the number is regarded as a signed number or an unsigned number. This is because to the processor, the number is the same. It is only how the number is regarded by the programmer that distinguishes the number as a signed number. There are some instructions, however when this does not work, notably the multiply, divide and shift right instructions. In the case of the direct multiply and divide instructions the special signed versions of these are IMUL and IDIV (instead of MUL and DIV which are the unsigned versions). There are three reasons why these are needed.
Firstly, when using signed numbers, the sign of the result will depend on the sign of the operands. A positive number multiplied by a negative number should give the result as a negative number. Two negative numbers multiplied together should give the result as a positive number. IMUL and IDIV maintain the correct sign. MUL and DIV assume they are using positive numbers only.
Secondly, the result of the multiply and divide instructions are usually given in a different data size than the operands. For example the result of a 32 bit multiplication will be given in the edx:eax register combination which together make 64 bits. If the multiplication is using signed numbers, it would be necessary for the sign bit to be at bit 63 in this particular case. So the signed instructions ensure that the sign bit is in the correct place. This is called "sign extending" the number.
Finally as we shall see, the signed instructions ensure that the carry and overflow flags are correctly set to suit a signed result.

In the case of the shift right instruction, there is a special version of this (SAR) which does not alter the high bit of the operand and so maintains the correct sign in the result. This is not necessary for left shifts since the sign is self-correcting, so although there is a SAL mnemonic the opcode is the same as for SHL. This example proves this to be correct:-

MOV AL,0FEh     ;give al -2
SAR AL,1        ;divide by 2 - result in al is now 0FFh which is -1
MOV AL,0FEh     ;give al -2
SAL AL,1        ;multiply by 2 - result in al is now 0FCh which is -4
Signed numbers also have their own set of signed conditional jump instructions.


Copyright © Jeremy Gordon 2002-2006
Back to top