Subtracting two 32-bit numbers on the 16-bit 8086 uses the same two-pass approach as 32-bit addition, but with SBB (Subtract with Borrow) instead of ADC. The first pass subtracts the lower 16 bits with SUB; if that produces a borrow (CF=1), the second pass uses SBB to subtract the upper 16 bits and the borrow together. This post walks through a working implementation in three environments: MASM/TASM, emu8086, and NASM.
Prerequisites: Familiarity with 16-bit subtraction and carry/borrow flags. Read 8086 Assembly Program to Subtract Two 16-bit Numbers first.
The Problem: Subtracting 12345678h from 9ABCDEF0h
We want to compute 9ABCDEF0h − 12345678h. Manual check: lower words DEF0h − 5678h = 8878h, no borrow (CF=0). Upper words with SBB: 9ABCh − 1234h − 0 = 8888h. Full result: 88888878h. Memory dump confirms 78 88 88 88 at ghi ✓.
Version 1 — MASM / TASM (Classic DOS Toolchain)
data segment
abc dd 9ABCDEF0h ; 32-bit minuend
def dd 12345678h ; 32-bit subtrahend
ghi db 5 dup(0) ; 4 bytes result + 1 byte borrow flag
data ends
code segment
assume cs:code, ds:data
start:
mov ax, data
mov ds, ax
mov dl, 00h ; DL = borrow flag (0 = no borrow)
; Subtract lower 16 bits
mov ax, word ptr abc
mov bx, word ptr def
sub ax, bx
mov word ptr ghi, ax ; Store lower word of result
; Subtract upper 16 bits with borrow from lower subtraction
mov ax, word ptr abc+2
mov bx, word ptr def+2
sbb ax, bx ; AX = AX - BX - CF
mov word ptr ghi+2, ax ; Store upper word of result
jnc move ; If no borrow out of upper subtraction, skip
inc dl ; Record the borrow in DL
move:
mov byte ptr ghi+4, dl ; Store borrow flag
int 3
code ends
end start
SBB is to subtraction what ADC is to addition: SBB AX, BX computes AX = AX − BX − CF. The CF it reads is the borrow flag set by the previous SUB instruction. If the lower-word subtraction produced a borrow (CF=1), SBB automatically deducts that extra 1 from the upper-word subtraction. Using plain SUB for both words ignores the borrow and gives the wrong upper-word result whenever the lower words produce a borrow.
Related Links:
Step-by-Step Explanation
1. Data Segment
- abc: 32-bit minuend (
9ABCDEF0h). - def: 32-bit subtrahend (
12345678h). - ghi: 5-byte result buffer — 4 bytes for the 32-bit difference, 1 byte for the borrow flag.
2. Code Segment
mov dl, 00h: Initialize borrow register.mov ax, word ptr abc/mov bx, word ptr def: Load lower 16-bit words.sub ax, bx: Subtract lower words. Sets CF if borrow.sbb ax, bx: Subtract upper words including borrow from lower subtraction.jnc move/inc dl: Record final borrow if the upper subtraction also borrows.
Flowchart

Output
C:TASM>masm an32sub.asm
Microsoft (R) Macro Assembler Version 5.00
Copyright (C) Microsoft Corp 1981-1985, 1987. All rights reserved.
0 Warning Errors
0 Severe Errors
C:TASM>link an32sub.obj
LINK : warning L4021: no stack segment
C:TASM>debug an32sub.exe
-g
AX=8888 BX=1234 CX=0038 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=0B97 ES=0B87 SS=0B97 CS=0B98 IP=0027 NV UP EI NG NZ NA PE NC
0B98:0027 CC INT 3
-d 0B97:0000
0B97:0000 F0 DE BC 9A 78 56 34 12-78 88 88 88 00 00 00 00 ....xV4.x.......
-q
Confirm AX=8888 (upper word of result) and DX=0000 (DL=0, no final borrow). Result 88888878h stored at ghi as 78 88 88 88 ✓.
Understanding the Memory Dump
0B97:0000 F0 DE BC 9A 78 56 34 12-78 88 88 88 00 00 00 00
| Address | Hex | Description | Value |
|---|---|---|---|
0B97:0000 | F0 DE BC 9A | Minuend abc | 9ABCDEF0h |
0B97:0004 | 78 56 34 12 | Subtrahend def | 12345678h |
0B97:0008 | 78 88 88 88 | Result ghi | 88888878h |
0B97:000C | 00 | Borrow flag — 0 means no final borrow |
SBB (like SUB) is not commutative. The code computes abc − def by loading abc into AX and def into BX, then subtracting. If you accidentally load them in reverse, you compute def − abc, get the two’s complement result, and the borrow flag fires when you did not expect it. Always double-check which variable is minuend (goes into AX) and which is subtrahend (goes into BX).
Version 2 — emu8086 (Windows Emulator)
Tested with: emu8086 v4.08 on Windows 10.
The emu8086 version allows you to easily track the borrow flag (CF) as it moves between the lower and upper word subtractions. This implementation uses the COM format, keeping the code and data in a single segment for simplicity.
; emu8086 version -- 8086 Assembly Program for Subtraction of Two 32-bit Numbers
#make_COM#
org 100h
; --- Code ---
start:
mov dl, 00h
mov ax, word ptr abc
mov bx, word ptr def
sub ax, bx
mov word ptr ghi, ax
mov ax, word ptr abc+2
mov bx, word ptr def+2
sbb ax, bx
mov word ptr ghi+2, ax
jnc move
inc dl
move:
mov byte ptr ghi+4, dl
mov ax, 4c00h
int 21h
; --- Data (after code to avoid execution as instructions) ---
abc dd 9ABCDEF0h
def dd 12345678h
ghi db 5 dup(0)
Version 3 — NASM (Modern Open-Source Assembler)
Tested with: NASM 2.16.01, DOSBox 0.74-3.
The NASM implementation is designed for those who prefer a more traditional command-line assembler. It uses standard x86 syntax and is ready to be compiled into a binary file for execution in any 16-bit DOS environment.
; NASM version -- 8086 Assembly Program for Subtraction of Two 32-bit Numbers
; nasm -f bin an32sub.asm -o an32sub.com
bits 16
org 100h
start:
mov dl, 0
mov ax, [abc]
mov bx, [def]
sub ax, bx
mov [ghi], ax
mov ax, [abc+2]
mov bx, [def+2]
sbb ax, bx
mov [ghi+2], ax
jnc move
inc dl
move:
mov [ghi+4], dl
mov ax, 4c00h
int 21h
; --- Data ---
abc dd 9ABCDEF0h
def dd 12345678h
ghi times 5 db 0
Frequently Asked Questions
Why use SBB for the upper word instead of a second SUB?
SBB AX, BX computes AX − BX − CF. The CF it reads is the borrow flag from the previous SUB. If the lower-word subtraction borrowed (CF=1), SBB automatically deducts that 1 from the upper-word result, keeping the multi-word arithmetic correct. Using SUB for both words ignores the borrow and produces an incorrect upper result in all cases where the lower words borrow.
What does DL=1 in ghi+4 mean?
It means the minuend was smaller than the subtrahend — the 32-bit subtraction produced a borrow out of the most significant bit. The stored result in ghi is the two’s complement wrap-around value, not a meaningful positive difference. Programs that need to distinguish between “result is valid” and “underflow occurred” check this byte after the operation.
Can I use more than two passes for numbers wider than 32 bits?
Yes. The pattern scales to any width: SUB the lowest word, then SBB for every subsequent word in order from least significant to most significant. A 64-bit subtraction would use SUB for the lowest 16 bits, then three SBB instructions for the remaining three words, each inheriting the borrow from the word before it.
Conclusion
32-bit subtraction on the 8086 mirrors the 32-bit addition pattern: SUB the lower words first, then SBB the upper words so the borrow propagates correctly. The key is always SBB for every word beyond the first — and making sure enough bytes are reserved for the result variable to hold both the 32-bit difference and the borrow flag.
Thank you!!
Doesn’t work in the case of smaller number – larger number:
; 32 bit subtraction
; _______________
assume cs:code, ds:data
data segment
opr1 dd 0ffffffffh
opr2 dd 00000001h
res dd 00000000h
car db 00h
data ends
code segment
org 0100h
start: mov ax, data
mov ds, ax
mov ch, 00h
mov ax, word ptr opr1
mov bx, word ptr opr2
sub ax, bx
jnc here1
neg ax
here1: mov word ptr res, ax
mov ax, word ptr opr1 + 2
mov bx, word ptr opr2 + 2
sbb ax, bx
jnc here2
neg ax
inc ch
here2: mov word ptr res+2, ax
mov byte ptr res+4, ch
mov ah, 4ch
int 21h
code ends
end start