Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does ASM knows an arithmetic operation is signed or unsigned?

I am assembling using MASM 14.0, and i am confused with the output of the below code.

TITLE Exercise 4 from chapter 4

; Author : Saad Ahmed

INCLUDE Irvine32.inc

.code
main PROC

mov eax, 0
mov al, 255
add al, 1
call DumpRegs       ; Display registers

mov al, 127
add al, 1
call DumpRegs       ; Display registers

exit
main ENDP
END main

Both arithmetic operations are done on unsigned integers 255 and 127.

However, the CPU is treating the first operation 255, as an unsigned integers and setting the carry flag, which would happen when you add 1 to an unsigned 255.

The complete status flags are CF=1 SF=0 ZF=1 OF=0 AF=1 PF=1 with eax as 0

But, the second operation considers the 127 to be a signed integer as it is setting the overflow flag, which would happen if you add 1 to a +127.

The complete status flags are to CF=0 SF=1 ZF=0 OF=1 AF=1 PF=0 with eax as 0.

The question is how does the CPU decides that the first operation was done on an unsigned 255, while the other one was on a signed integer?

like image 662
Saad Avatar asked Jul 05 '16 02:07

Saad


People also ask

How does a computer know if a number is signed or unsigned?

If it is signed, the compiler uses signed operators for manipulating the variables (e.g. IDIV) and when unsigned, it uses another instruction (e.g. DIV). So the compiler makes a program which tells the CPU how to interpret the data.

What is signed and unsigned numbers in assembly language?

Signed numbers use sign flag or can be distinguish between negative values and positive values. Whereas unsigned numbers stored only positive numbers but not negative numbers.

What is the difference between signed and unsigned jump in assembly?

The main difference between a signed and an unsigned number is, well, the ability to use negative numbers. Unsigned numbers can only have values of zero or greater. In contrast, signed numbers are more natural with a range that includes negative to positive numbers.

Are registers signed or unsigned?

32-bit registers in x86 are default unsigned. For example, a move from 32-bit operand to a 64-bit register always get zero-extended under long model. But instructions each has its own interpretation of registers based on its purpose, like 'add' instruction take registers both signed or unsigned.


1 Answers

For addition (and subtraction) using twos complement there is no notion of signed or unsigned as far as the logic is concerned. Multiply and divide, yes, due to the sign extension required.

Take all combinations of 3 bit numbers from 000 to 111 and add them to all the combinations of 3 bit numbers, something manageable. Maybe write a program if you want or do it by hand. Or just do the corner cases. For each examine each operand as signed or unsigned using twos complement. You will note that the same addition works. 1 + 110 = 111. Now is that 1 + (-2) = -1 or was that 1 + 6 = 7. Both worked.

The carry flag IS the UNSIGNED overflow, the V flag is the SIGNED overflow, that is why we compute both so the user, who knows whether or not those are signed or unsigned numbers can choose the right conditional. And that is why you have signed jump if greater than or equal vs an unsigned jump if greater than or equal.

It is the beauty of twos complement that makes it all work.

Multiply (and divide) is different, because you have to sign extend. Multiply in binary has a beautiful feature, in that if you think about it

   abcd
*  0011
=======
   abcd
  abcd
 0000
0000
=======

A bit is either 1 or 0, so you are multiplying the top number by either one or zero, you either add it shifted or you dont. But also notice that very very quickly you are going to overflow. We know from grade school that n^x * n^y = n^(x+y). If your registers are Z bits wide the most significant bit positions of the operands cannot be larger than Z when added otherwise it overflows. Four bits 0010*0010 should work but 0010*1000 will overflow. The right way is the result is twice as wide as the operands.

So what if I want to multiply 1111 * 0010?

That is basically

    0000
   1111
  0000
+0000
========
 0011110

Wait was that a 15 (0b1111) or a -1 (0b1111)? -1 * 2 = -2 which is not what we got above, we did an unsigned multiply to do a signed multiply we have to sign extend and toss bits off the left

 11..1111
*00..0010
=========    
00000000
1111111
000000
00000
=========
11111110

and that gives the right answer for a signed multiply of two four bit registers 1111 and 0010.

Addition works because you only care about one column. Each column has a carry in, two operands a result and a carry out. And then you can cascade that as wide as you want. With a single bit you have 0 and 1. The zero is just zero not plus or minus zero, the 1 can either be a +1 or a -1. I find it easier to work through the combinations with more than one column, but it could be done. For addition the carry in is a 0 so I dont need to represent it operand a, operand b, carry out, and result

00 00  0 + 0 = 0
01 01  0 + 1 = 1;  0 + (-1) = -1
10 01  1 + 0 = 1;  (-1) + 0 = -1
11 10  1 + 1 = 0 unsigned overflow.  -1 + 1 = 0, 1 + -1 = 0, -1 + -1 = 0 signed overflow

Technically all the signed ones were a signed overflow in that last case, and that is the special case you deal with for any number of bits, take a three bit register 100 + 100 = 000 + carry out of 1 is that a 4 + 4 = 0 unsigned overflow or is that a -4 + -4 = 0 with a signed overflow? Which is why it is easier to see when you use a few bits and go through the combinations tossing the problem case of 1 and then all zeros.

A signed overflow is when the carry in of the most significant column does not match the carry out. an unsigned overflow is when the carry out of the msbit is a one.

Subtraction in logic is done with addition, we know from grade school that a - b = a + (-b) and we know from programming classes that to take the negative using twos complement you invert and add one. Well that works great we can just invert the second operand and invert the carry in of the lsbit making it a one or invert and add one. And that is how that works. The carry out is sometimes inverted coming out of the alu to indicate a borrow. You have to look at the flag combinations to figure this out, some processors invert the carry out some dont. Can sometimes tell from the subtract with borrow if your ISA has that instruction.

I know TL:DR...covered more than you asked.

like image 129
old_timer Avatar answered Oct 01 '22 20:10

old_timer