Calculating ($a^2$ + $b(2c-d)^2)/(3e)$ in assembly

I made a simple assembly program to evaluate the function

$$f =\frac{a^2 + b(2c-d)^2}{3e}$$

Since this is basically my first ever, I would like to hear what I can improve and know how to use registers better.

.model small
.stack 100h
.data
a dw 4
b dw 15
c dw 86
d dw 155
e dw 8
res dw 0;
.code
mov ax,@data
mov ds,ax
mov dx,0
mov bx,0
mov cx,0
mov ax,c
shl ax,1
mov bx,d
sub ax,bx
mul ax
mul b
mov dx,ax
push dx
mov dx,0
mov ax,a
mul ax
pop cx
mov dx,ax
push dx
mov dx,0
mov ax,e
mov bx,3
mul bx
mov bx,ax
pop cx
mov ax,cx
mov cx,bx
div cx
mov ax,4c00h
int 21h
end

• Nice to see some assembly again. Could you please format you code (indent non-labels) and add some comments? See for example: oocities.org/codeteacher/x86asm/asml1008.html – RobAu Mar 9 '18 at 8:55
• Please edit to indicate what kind of assembly this is. It looks to me that it's Intel assembly for IA16 or IA32; is that right? Also, a bit more context on the requirements would be useful. Also, this appears to be all in integer arithmetic - what kind of rounding is required for the result of the division? – Toby Speight Mar 9 '18 at 9:32
• Is there a reason to avoid floating point instructions here? Also, what is the domain of the input variables? For instance, can e == 0? If so, what should happen then? – Edward Mar 11 '18 at 19:55

Some registers have a basic purpose and they supposed to be used for those but it's not that you can't use cx for storing arithmetic operation result. There are few things you could improve in your code

Comment

I would really recommend you to start putting comments. Assembly is not a difficult language but it's very context specific. Each similar looking assembly line might be doing different thing depending on the context and comments are important, even in such small application codebase as this one. Do not try to write what the instruction does in english like

add ax,cx ;adds content of cx to ax


but rather explain the meaning.

add ax,cx ;after that ax  = a^2 + b(2*c - d)^2


Assembly

As I wrote above there's nothing wrong in your usage of assembly (unless you use some register that can't be used - like segment ones), but there are few things you could optimize.

mov ax,0


could be written as xor ax,ax and saved few bytes (only if you want to). And you don't have to zero ax & cx as you don't use it before you assign any value to it.

This is a strange construct too:

mov dx,ax
push dx
mov dx,0


You are moving the result from calculation to dx to push in to the stack and then clearing the register. Why not just

push ax


Instructions at the end also looks like they are doing too much of moving around so this could be simplified as:

mov ax,e
mov bx,3
mul bx
mov bx,ax
pop ax
div bx


You are writing that you want to use registers more so those push/pop could also be removed. Also res is unsued in the code - remove it.

Final program (probably - could be even more improved)

.model small
.stack 100h
.data
a dw 4
b dw 15
c dw 86
d dw 155
e dw 8
.code
_start:
mov ax,@data
mov ds,ax

xor dx,dx
mov ax,c
shl ax,1  ; ax = 2*c

mov bx,d
sub ax,bx ; ax = 2*c - d
mul ax    ; ax = (2*c - d)^2
mul b     ; ax = b*(2*c - d)^2
mov cx,ax

xor dx,dx
mov ax,e
mov bx,3
mul bx    ; ax = 3*e
mov bx,ax

mov ax,a
mul ax    ; ax = a^2
add ax,cx ; cx = a^2 + b*(2*c - d)^2

div bx    ; ax = (a^2 + b*(2*c - d)^2)/3e

mov ax,4c00h
int 21h
end _start

• While these are litigious times, I don't believe OP was planning on 'suing' res. It might also be worth mentioning that there is no overflow checking here. If c were (say) 32768, the shl might not give the result you were expecting. – David Wohlferd Mar 10 '18 at 7:15

mov dx,0
mov bx,0
mov cx,0


It's practically never needed to zero the registers prior to using them. And if ever it would be useful to wipe a register clean then xor-ing that register with itself will produce the same result more efficiently. e.g. xor bx, bx

mov bx,d
sub ax,bx


There's little point in first moving the contents of the variable d to a register and then doing a subtraction between registers when there's a possibility to subtract the variable directly from the accumulator writing sub ax, d.

mov dx,ax
push dx
mov dx,0
mov ax,a
mul ax


Since your intent is to put the value in AX on the stack, do so in one go with push ax.
And clearing the DX register right before a mul instruction is wasteful since

• DX is not among the inputs for the multiplication
• DX receives the high word of the 32-bit resulting product anyway

You can apply this several times in your program.

pop cx
mov ax,cx
mov cx,bx
div cx


Since your intent is to put the value on the stack in AX, do so in one go with pop ax.
And moving the BX register to the CX register before the div instruction is wasteful since the division can simply operate on BX directly.

Next some improvements you can apply

• Instead of push-ing / pop-ing the result from b(2c-d)^2, you could move it directly to CX. This shaves off an instruction.

• Instead of calculating the value of 3e with a multiplication that uses AX and thus requires you to push / pop the accumulator's pre-existing content, you could evaluate 3e with as little as 3 instructions:

mov bx, e   ; bx = 1e
shl bx, 1   ; bx = 2e
add bx, e   ; bx = 3e


This shaves off 3 instructions.

• Prior to the division operation you should zero the DX register but since the last multiplication before this div instruction leaves DX=0it counts as an optimization to not write xor dx, dx here. I've commented it out in below code!

• The single most important improvement is that you start writing comments that explain what the instructions in your program accomplish.

Applying all the above

mov     ax, @data
mov     ds, ax

mov     ax, c       ; ax = c
shl     ax, 1       ; ax = 2c
sub     ax, d       ; ax = 2c - d
mul     ax          ; ax = (2c - d)^2
mul     b           ; ax = b(2c - d)^2
mov     cx, ax      ; cx = b(2c - d)^2

mov     ax, a       ; ax = a
mul     ax          ; ax = a^2

add     ax, cx      ; ax = a^2 + b(2c - d)^2

mov     bx, e       ; bx = e
shl     bx, 1       ; bx = 2e
add     bx, e       ; bx = 3e

;;; xor     dx, dx    Previous MUL made DX=0
div     bx          ; ax = (a^2 + b(2c - d)^2) / 3e

mov     ax, 4C00h   ; DOS.TerminateWithExitcode
int     21h


To enhance the readability of your programs you should never be afraid to use lots of whitespace.

Check out imul

The program that you wrote only uses 8086 instructions. Perhaps this in intentional.

But if you're interested, x86 has a very powerful imul instruction that

• allows to multiply by an immediate
• is no longer restricted to just the accumulator.

This is how the above code would look like:

mov     ax, @data
mov     ds, ax

imul    cx, c, 2    ; cx = 2c
sub     cx, d       ; cx = 2c - d
imul    cx, cx      ; cx = (2c - d)^2
imul    cx, b       ; cx = b(2c - d)^2

mov     ax, a       ; ax = a
mul     ax          ; ax = a^2

add     ax, cx      ; ax = a^2 + b(2c - d)^2

imul    bx, e, 3    ; bx = 3e

;;; xor     dx, dx    Previous MUL made DX=0
div     bx          ; ax = (a^2 + b(2c - d)^2) / 3e

mov     ax, 4C00h   ; DOS.TerminateWithExitcode
int     21h


Especially calculating 3e is now extremely simple!