8086 Assembly Program for Subtraction of Two 8-bit Numbers

8-bit subtraction on the 8086 follows the same register-first pattern as 16-bit subtraction — but with one extra gotcha to watch: because only AL is involved in the computation, AH retains whatever value it had from the segment setup code. When you later store the full AX into the result variable, that dirty AH comes along for the ride. The emu8086 and NASM versions below fix this with a single xor ah, ah. 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. Read 8086 Assembly Program to Subtract Two 16-bit Numbers for the 16-bit version first if needed.


The Problem: Subtracting 13h from 2Ah

We want to compute 2Ah − 13h (42 − 19 = 23) and store the result. Quick check: 2Ah = 42, 13h = 19, 42 − 19 = 23 = 17h. The debugger confirms AL=17h ✔. CF=0 (no borrow) because 2Ah > 13h.


Version 1 — MASM / TASM (Classic DOS Toolchain)

data segment
    a db 2Ah        ; 8-bit minuend (42 decimal)
    b db 13h        ; 8-bit subtrahend (19 decimal)
    c dw ?          ; 16-bit result storage
data ends

code segment
assume cs:code, ds:data
start:
    mov ax, data    ; Load data segment address
    mov ds, ax      ; Initialize DS  (AH now holds high byte of segment address)

    mov al, a       ; Load minuend into AL (2Ah)
    mov bl, b       ; Load subtrahend into BL (13h)
    sub al, bl      ; AL = AL - BL  (result: 17h)

    mov c, ax       ; Store AX into c  (note: AH contains segment address high byte)

    int 3           ; Halt for debugging
code ends
end start
⚠️ Common mistake — assuming c holds 0017h when it actually holds 0B17h: After mov ax, data / mov ds, ax, AH contains the high byte of the segment address (0Bh). The sub al, bl instruction only touches AL — AH is left at 0Bh. So mov c, ax stores 0B17h, not 0017h. The correct AL result is 17h and is easily read from the register panel, but the stored word in c has a dirty high byte. Add xor ah, ah before the store to fix this — the emu8086 and NASM versions below do exactly that.


Step-by-Step Explanation

1. Data Segment

  • a db 2Ah: Stores the minuend (42 decimal) as an 8-bit value.
  • b db 13h: Stores the subtrahend (19 decimal) as an 8-bit value.
  • c dw ?: Reserves a 16-bit word for the result. Even though the arithmetic is 8-bit, using dw lets you store the full AX and read both the result (AL) and potential borrow context.

2. Code Segment

  • mov ax, data / mov ds, ax: Initialize DS. After this, AH contains the high byte of the segment address.
  • mov al, a: Load the minuend (2Ah) into AL.
  • mov bl, b: Load the subtrahend (13h) into BL.
  • sub al, bl: AL = AL − BL = 2Ah − 13h = 17h. Only AL is modified; AH is untouched.
  • mov c, ax: Stores AX (including the dirty AH) into c.
  • int 3: Breakpoint to halt and allow debugger inspection.

Flowchart

High-Level Overview

  1. Initialize DS and load operands into AL and BL.
  2. Execute SUB AL, BL: AL = AL − BL.
  3. Optionally clear AH, then store AX into c.
  4. Halt.

Output

C:TASM>masm an8sub.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.
 
Object filename [an8sub.OBJ]:
Source listing  [NUL.LST]:
Cross-reference [NUL.CRF]:
 
  50402 + 450254 Bytes symbol space free
 
      0 Warning Errors
      0 Severe  Errors
 
C:TASM>link an8sub.obj
 
Microsoft (R) Overlay Linker  Version 3.60
Copyright (C) Microsoft Corp 1983-1987.  All rights reserved.
 
Run File [AN8SUB.EXE]:
List File [NUL.MAP]:
Libraries [.LIB]:
LINK : warning L4021: no stack segment
 
C:TASM>debug an8sub.exe
-g
 
AX=0B17  BX=0013  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 PE NC
0B98:0011 CC            INT     3
-d 0B97:0000
0B97:0000  2A 13 17 0B 00 00 00 00-00 00 00 00 00 00 00 00   *...............
-q

AL=17h confirms the correct subtraction result (23 decimal). AH=0Bh is the leftover high byte of the segment address — not part of the answer. The memory dump shows c = 17 0B (little-endian 0B17h) because AH was not cleared before the store.

💡 Side note — why AX=0B17 instead of 0017: The instruction sequence mov ax, data / mov ds, ax loads the full segment address into AX before setting DS. The segment address here is 0B97h, so AH = 0Bh. The subsequent mov al, a and sub al, bl only touch AL — AH stays 0Bh throughout. To get a clean 0017h in c, add xor ah, ah immediately before mov c, ax.

Understanding the Memory Dump

AddressHexDecimalExplanation
0B97:00002A42Minuend a = 2Ah
0B97:00011319Subtrahend b = 13h
0B97:000217 0B23 (AL only)Result c = 0B17h: AL=17h (correct answer), AH=0Bh (segment address leftover)

Version 2 — emu8086 (Windows Emulator)

This version adds xor ah, ah before storing the result, so c correctly holds 0017h instead of 0B17h.

Tested with: emu8086 v4.08 on Windows 10.

; emu8086 version -- 8086 Assembly Program for Subtraction of Two 8-bit Numbers
#make_COM#

org 100h

; --- Code ---
start:
    mov al, a       ; Load minuend into AL (2Ah)
    mov bl, b       ; Load subtrahend into BL (13h)
    sub al, bl      ; AL = AL - BL  (result: 17h)
    xor ah, ah      ; Clear AH for a clean 16-bit result in AX (0017h)
    mov c, ax       ; Store clean result

    mov ax, 4c00h
    int 21h

; --- Data (after code to avoid execution as instructions) ---
a db 2Ah
b db 13h
c dw 0000h

Version 3 — NASM (Modern Open-Source Assembler)

Tested with: NASM 2.16.01, DOSBox 0.74-3.

; NASM version -- 8086 Assembly Program for Subtraction of Two 8-bit Numbers
; nasm -f bin an8sub.asm -o an8sub.com

bits 16
org  100h

start:
    mov al, [a]      ; Load minuend into AL (2Ah)
    mov bl, [b]      ; Load subtrahend into BL (13h)
    sub al, bl       ; AL = AL - BL  (result: 17h)
    xor ah, ah       ; Clear AH for clean result (AX = 0017h)
    mov [res], ax    ; Store result

    mov ax, 4c00h
    int 21h

; --- Data (after code to avoid execution as instructions) ---
a   db 2Ah
b   db 13h
res dw 0

Register State After Execution

RegisterMASM/TASMemu8086 / NASMMeaning
AL1717Subtraction result: 2Ah − 13h = 17h
AH0B (leftover)00 (cleared)Zeroed by xor ah, ah in emu8086/NASM
CF00No borrow — 2Ah > 13h

Frequently Asked Questions

Why does AX show 0B17h instead of 0017h in the MASM version?

The mov ax, data / mov ds, ax pair loads the data segment address into AX before copying it to DS. This leaves AH holding the high byte of the segment address (0Bh here). The subsequent mov al, a and sub al, bl only modify AL — AH is untouched. So when mov c, ax runs, it stores the complete AX including the dirty AH. Adding xor ah, ah immediately before the store gives the clean 0017h result.

What happens if the subtrahend is larger than the minuend?

The result wraps around using two’s complement and the Carry Flag (CF) is set to 1 to signal a borrow. For example, 01h − 02h produces FFh in AL with CF=1. The stored value is the correct two’s complement representation, but programs that care about sign must check CF (or SF) after the subtraction and handle the negative case explicitly.

Can I use CL and DL instead of AL and BL?

Yes. Any pair of 8-bit registers works as operands for SUB. The choice of AL and BL is conventional — AL is the accumulator and the natural destination for arithmetic results. There is no performance difference between byte registers on the 8086.


Conclusion

8-bit subtraction on the 8086 operates entirely in AL, leaving AH untouched. The most common source of confusion is the dirty AH left over from segment setup, which causes the stored word to contain an unexpected high byte. A single xor ah, ah before storing AX fixes this cleanly. The Carry Flag signals a borrow when the result goes negative — always check it if your operands can produce an underflow.


See Also

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.