Assembly language offers a hands-on approach to understanding how computers perform basic arithmetic at a low level. Subtraction in 8086 assembly follows the same register-first pattern as addition — but with one extra detail worth getting right: operand order. SUB AX, BX computes AX − BX, not BX − AX. Getting that backwards produces a wrong result with no assembler warning. This post walks through a working implementation in three environments: MASM/TASM, emu8086, and NASM.
Prerequisites: Familiarity with 8086 registers and the segment setup pattern. New to assembly? Read Understanding DW and DB in 8086 Assembly first.
The Problem: Subtracting 8765h from 9A88h
We want to compute 9A88h − 8765h and store the result in a memory variable. Quick mental verification: 9A88h = 39560 decimal, 8765h = 34661 decimal, 39560 − 34661 = 4899 = 1323h. The debugger output below confirms AX=1323. Always sanity-check the expected result before running — it makes debugging far faster.
Version 1 — MASM / TASM (Classic DOS Toolchain)
This is the canonical version assembled with Microsoft MASM or Borland TASM. The full assembler, linker, and debugger session is shown in the Output section below.
data segment
a dw 9A88h ; 16-bit minuend
b dw 8765h ; 16-bit subtrahend
c dw ? ; 16-bit variable to store the result
data ends
code segment
assume cs:code, ds:data
start:
mov ax, data ; Load data segment address into AX
mov ds, ax ; Initialize DS register
mov ax, a ; Load minuend (9A88h) into AX
mov bx, b ; Load subtrahend (8765h) into BX
sub ax, bx ; AX = AX - BX (result: 1323h)
mov c, ax ; Store result in 'c'
int 3 ; Halt for debugging
code ends
end start
SUB AX, BX computes AX − BX and stores the result in AX. Many students read it as "subtract AX from BX" and expect BX − AX. There is no assembler warning for this — the instruction is syntactically valid either way — so the bug only surfaces when the result looks wrong. The rule is always: destination minus source, result in destination.
Subtraction in 8086 is just addition with a negative twist — the CPU computes
AX + (~BX + 1)under the hood using two’s complement, and the Carry Flag becomes a Borrow Flag.
Related Links:
Understanding the Code
Data Segment
a dw 9A88h: Declares a 16-bit variableainitialized to9A88h— the minuend (number being subtracted from).b dw 8765h: Declares a 16-bit variablebinitialized to8765h— the subtrahend (number being subtracted).c dw ?: Declares a 16-bit variablec, uninitialized, to store the subtraction result.
Flowchart

Code Segment
assume cs:code, ds:data: Informs the assembler thatCSpoints to the code segment andDSpoints to the data segment.mov ax, data: Loads the starting address of the data segment intoAX.mov ds, ax: Copies the address inAXto theDSregister, giving the processor access to the data segment variables.mov ax, a: Loads the value ofa(9A88h) intoAX.mov bx, b: Loads the value ofb(8765h) intoBX.sub ax, bx: SubtractsBXfromAXand stores the result back inAX. Result:9A88h − 8765h = 1323h.mov c, ax: Stores the result (1323h) into variablecin the data segment.int 3: Triggers a breakpoint interrupt to halt execution for debugging.
High-Level Overview
- Data Initialization: Variables
aandbare defined with values9A88hand8765h. Space is reserved incfor the result. - Segment Setup: The DS register is initialised so the processor can access the data segment variables.
- Load Values: The minuend and subtrahend are loaded into
AXandBXrespectively. - Perform Subtraction:
SUB AX, BXcomputesAX − BXand stores the result inAX. - Store Result: The result is saved to variable
cand the program halts.
Output
C:TASM>masm an16sub.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987. All rights reserved.
Object filename [an16sub.OBJ]:
Source listing [NUL.LST]:
Cross-reference [NUL.CRF]:
50402 + 450254 Bytes symbol space free
0 Warning Errors
0 Severe Errors
C:TASM>link an16sub.obj
Microsoft (R) Overlay Linker Version 3.60
Copyright (C) Microsoft Corp 1983-1987. All rights reserved.
Run File [AN16SUB.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
LINK : warning L4021: no stack segment
C:TASM>debug an16sub.exe
-g
AX=1323 BX=8765 CX=0022 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=0B97 ES=0B87 SS=0B97 CS=0B98 IP=0011 NV UP EI PL NZ NA PO NC
0B98:0011 CC INT 3
-d 0B97:0000
0B97:0000 88 9A 65 87 23 13 00 00-00 00 00 00 00 00 00 00 ..e.#...........
0B97:0010 B8 97 0B 8E D8 A1 00 00-8B 1E 02 00 2B C3 A3 04 ............+...
0B97:0020 00 CC 86 72 FF 77 15 8A-86 70 FF 2A E4 50 B8 FD ...r.w...p.*.P..
0B97:0030 05 50 FF 36 24 21 E8 77-63 83 C4 06 FF 36 24 21 .P.6$!.wc....6$!
0B97:0040 B8 0A 00 50 E8 47 5E 83-C4 04 5E 8B E5 5D C3 90 ...P.G^...^..]..
0B97:0050 55 8B EC 81 EC 84 00 C4-5E 04 26 80 7F 0A 00 74 U.......^.&....t
0B97:0060 3E 8B 46 08 8B 56 0A 89-46 FC 89 56 FE C4 5E FC >.F..V..F..V..^.
0B97:0070 26 8A 47 0C 2A E4 40 50-8B C3 05 0C 00 52 50 E8 &.G.*[email protected].
-q
Confirm AX=1323 — that is 9A88h − 8765h = 1323h ✔. The flag field shows NC (No Carry), meaning CF=0: no borrow occurred because 9A88h is larger than 8765h.
Understanding the Memory Dump
The memory dump starting from 0B97:0000 shows the data segment contents after the program runs:
0B97:0000 88 9A 65 87 23 13 00 00-00 00 00 00 00 00 00 00
88 9A: The valuea = 9A88hstored in little-endian format (low byte88hfirst).65 87: The valueb = 8765hstored in little-endian format (low byte65hfirst).23 13: The resultc = 1323hstored in little-endian format (low byte23hfirst).
SUB instruction, CF=1 means a borrow occurred — the result went negative in unsigned terms (subtrahend was larger than minuend). Here CF=0 because 9A88h > 8765h. If you swapped the operands and computed 8765h − 9A88h, the result would wrap around and CF would be set to 1. Many programs check CF after subtraction to detect underflow, using JC (jump if carry/borrow).
Version 2 — emu8086 (Windows Emulator)
The emu8086 version uses COM format via the #make_COM# directive, skipping the linker step entirely. No DS setup is needed because DOS pre-sets all segment registers for COM programs.
Tested with: emu8086 v4.08 on Windows 10.
; emu8086 version -- 8086 Assembly Program to Subtract Two 16-bit Numbers
; Assemble as COM file using: #make_COM#
#make_COM#
org 100h ; COM programs begin at offset 100h
; --- Code ---
start:
mov ax, a ; Load minuend into AX (9A88h)
mov bx, b ; Load subtrahend into BX (8765h)
sub ax, bx ; AX = AX - BX (result: 1323h)
mov c, ax ; Store result in c
mov ax, 4c00h ; Exit via DOS
int 21h
; --- Data declarations (after code to avoid execution as instructions) ---
a dw 9A88h ; Minuend
b dw 8765h ; Subtrahend
c dw 0000h ; Result
How to Run in emu8086
- Open emu8086, paste the code and click Compile and Run (or press F5).
- After execution stops, check the Registers panel. You should see
AX = 1323and CF=0. - Open the Memory panel and navigate to variable
c. You should see bytes23 13— that is1323hin little-endian order.
Version 3 — NASM (Modern Open-Source Assembler)
The NASM version targets a 16-bit flat binary COM file. Memory operands require square brackets — mov ax, [a] not mov ax, a.
Tested with: NASM 2.16.01, executed in DOSBox 0.74-3.
; NASM version -- 8086 Assembly Program to Subtract Two 16-bit Numbers
; Assemble: nasm -f bin an16sub.asm -o an16sub.com
; Run: Place an16sub.com in DOSBox and execute it
bits 16 ; 16-bit mode
org 100h ; COM file origin
; --- Code ---
start:
mov ax, [a] ; Load minuend into AX (9A88h)
mov bx, [b] ; Load subtrahend into BX (8765h)
sub ax, bx ; AX = AX - BX (result: 1323h)
mov [c], ax ; Store result in c
mov ax, 4c00h ; Exit via DOS
int 21h
; --- Data ---
a dw 9A88h ; Minuend
b dw 8765h ; Subtrahend
c dw 0000h ; Result storage
Build and Run Steps
# Assemble to flat binary COM format
nasm -f bin an16sub.asm -o an16sub.com
# Run inside DOSBox
# Inside DOSBox:
# mount c .
# c:
# an16sub.com
Register State After Execution (All Three Versions)
| Register | Value | Meaning |
|---|---|---|
| AX | 1323 | Result of 9A88h − 8765h |
| BX | 8765 | Subtrahend — unchanged after SUB |
| CF (Carry/Borrow) | 0 | No borrow — minuend was larger than subtrahend |
| ZF (Zero Flag) | 0 | Result is not zero |
| SF (Sign Flag) | 0 | Result is positive (bit 15 = 0) |
| OF (Overflow Flag) | 0 | No signed overflow |
Frequently Asked Questions
What happens if the subtrahend is larger than the minuend?
The result wraps around in unsigned arithmetic and the Carry Flag (CF) is set to 1 to signal a borrow. For example, 0001h − 0002h produces FFFFh in AX with CF=1. This is not a crash — it is the correct two’s complement result. Programs that care about the sign of the result check CF (or SF and OF together for signed subtraction) immediately after SUB using conditional jumps like JC, JB, or JS.
Can I subtract directly from memory without using a register?
Partially. The 8086 allows one memory operand per SUB instruction, but not two. You can write SUB AX, b (subtract the memory variable b directly from AX) without loading b into BX first. What you cannot do is SUB a, b — both operands cannot be memory locations in the same instruction. In most lab programs the two-register approach is clearest and the assembler generates the same machine code either way.
What is the difference between SUB and SBB?
SUB performs a plain subtraction: dest = dest − src. SBB (Subtract with Borrow) performs dest = dest − src − CF — it includes the current Carry Flag as an extra borrow bit. SBB is used when subtracting numbers wider than 16 bits across multiple instructions. For example, to subtract two 32-bit numbers stored as two 16-bit word pairs, you would SUB the low words first, then SBB the high words so the borrow from the low subtraction is automatically folded in.
Conclusion
16-bit subtraction in 8086 assembly is structurally identical to addition — load operands into registers, compute, store. The one extra thing to keep in mind is operand order: SUB AX, BX always means AX − BX, result in AX. The Carry Flag doubles as a Borrow Flag and is the right signal to check when the result might be negative. Get those two habits right and subtraction is as straightforward as any other arithmetic instruction in the 8086 repertoire.
See Also
- Understanding DW and DB in 8086 Assembly — data declaration reference
- 8086 Assembly Program to Add Two 16-bit Numbers — the addition counterpart
- 8086 Assembly Program to Divide Two 16-bit Numbers — more complex arithmetic