Search code examples
assemblyx86x86-16evaluationemu8086

Can someone explain what is wrong with my code and how my teacher's code works?


I'm trying to write some code in emu8086. I'm new to assembly so it's still a bit confusing to me. In my assignment, for one of the exercises I have to:

Make a code that solves: r=(x-y*z)/t
The values are: x = 2000; y = -500; z = 200; t = 300; r = ?

I checked the solution given in our course:

x dw 2000
y dw -500
z dw 200
t dw 300
r dw ?
____________________
mov ax, y
imul z
mov cx, dx
mov bx, ax
mov ax, x
cwd
sub ax, bx
sbb dx, cx
idiv t
mov r, ax

and I don't understand why using the instructions cwd and sbb is needed, and also the lines:

mov cx, dx 
mov bx, ax
sbb dx, cx 

This is the code that I wrote myself:

mov AX, y
imul z
    
mov BX, x
sub BX, AX
    
idiv t
mov r, BX

and these are the results I get:

x = 07D0h
y = 0FE0Ch
z = 00C8h
t = 012Ch
r = 8E70h

(https://i.sstatic.net/9CU9Q.png)

The value for r should be 0154h, which is 340 in decimal.

I guess the problem might be related to the multiplication resulting in a negative number, but I don't understand how to fix it.

Could someone explain the purpose of the fore-mentioned instructions in this code, how they affect the whole process and why my own code is wrong? Besides that, are there other ways of solving this without having to use those instructions?


Solution

  • What you are seeing is implementing 32 bit arithmetic using 16 bit registers. This is needed because the intermediate values of your calculation do not fit into 16 bits, and partially also because idiv and imul operate on 32 bit register pair dx and ax.

    The imul z result is -100000 but that does not fit into 16 bits and is already split into dx=0xFFFE=-2 and ax=0x7960=31072 automatically. To do the subtraction x-y*z x is also extended to 32 bits, using the cwd instruction. Before that the y*z temporary is moved away to the cx:bx pair. The subtraction is done by first doing the low 16 bits using plain sub and the top 16 bits using sbb to propagate the carry (borrow). The result is left in dx:ax which is the implicit input to the idiv. The quotient is then produced in ax.

    Your code is wrong because the sub BX, AX leaves the result in bx which is not used by the idiv at all since that operates on dx:ax implicitly. You then store that bx which isn't even the output of the idiv. You also only calculated using the low 16 bits, so the result is 2000-31072=-29072=0x8E70.

    Yes, you can simulate the cwd and the sbb using other instructions, for example using explicit conditional jumps. cwd just sign extends ax into dx meaning if ax is negative then dx will be set to 0xffff and to zero otherwise. You can simply do this by hand, or fill dx with the sign bit using arithmetic shift or if you are permitted to know that x is always positive then just zero dx. Instead of sbb you can just check if there was a carry (borrow) and subtract 1 from the result if so before handling the rest with a plain sub.