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.
|