8086 Assembly Program to Add Two 32-bit Numbers

Adding two 32-bit numbers on the 16-bit 8086 requires two passes: first add the lower 16-bit words, then add the upper 16-bit words using ADC (Add with Carry) to fold in any carry from the first pass. The carry propagation between the two halves is what makes multi-word arithmetic correct — skip it and the upper half of your result will be wrong whenever the lower addition overflows. This post walks through a working implementation in three environments: MASM/TASM, emu8086, and NASM.

Prerequisites: Familiarity with 8086 registers, the segment setup pattern, and 16-bit addition. Read 8086 Assembly Program to Add Two 16-bit Numbers first if you are new to this.


The Problem: Adding 12345678h and 9ABCDEF0h

We want to add 12345678h and 9ABCDEF0h and store the 32-bit result. Manual check: lower words 5678h + DEF0h = 13568h → low result 3568h, carry 1. Upper words 1234h + 9ABCh + 1 = ACF1h, no further carry. Full result: ACF13568h. The debugger confirms memory bytes 68 35 F1 AC at ghi ✓.

💡 Side note — ADC vs ADD for the upper word: ADD AX, BX adds two values and ignores any previous carry. ADC AX, BX adds two values plus the current Carry Flag. For multi-word addition you must use ADD for the lowest word (there is no incoming carry yet) and ADC for every subsequent word. Using ADD for both words is a silent bug — it assembles cleanly and the result looks plausible, but will be off by 1 whenever the lower word addition produces a carry.

Version 1 — MASM / TASM (Classic DOS Toolchain)

data segment
    abc dd 12345678h    ; 32-bit addend 1
    def dd 9ABCDEF0h    ; 32-bit addend 2
    ghi db 5 dup(0)     ; 32-bit result (+ 1 overflow byte at ghi+4)
data ends

code segment
assume cs:code, ds:data
start:
    mov ax, data
    mov ds, ax
    mov dl, 00h             ; DL = carry flag register (0 = no carry yet)

    ; Add lower 16 bits
    mov ax, word ptr abc
    mov bx, word ptr def
    add ax, bx
    mov word ptr ghi, ax    ; Store lower word of result

    ; Add upper 16 bits with carry from lower addition
    mov ax, word ptr abc+2
    mov bx, word ptr def+2
    adc ax, bx              ; AX = AX + BX + CF
    mov word ptr ghi+2, ax  ; Store upper word of result

    jnc move                ; If no carry out of upper addition, skip
    inc dl                  ; Else record the overflow in DL
move:
    mov byte ptr ghi+4, dl  ; Store overflow byte
    int 3
code ends
end start
⚠️ Common mistake — declaring ghi as dw instead of dd: The code writes to ghi, ghi+2, and ghi+4 — a total of 5 bytes. Declaring ghi dw ? reserves only 2 bytes; the writes to offsets 2 and 4 silently overwrite whatever memory follows in the data segment. Declare it as ghi dd ? (4 bytes for the 32-bit sum) and add a separate db ? for the overflow byte, or use db 5 dup(0) to reserve all 5 bytes explicitly.


Understanding the Code

Data Segment

  • abc dd 12345678h: 32-bit first addend.
  • def dd 9ABCDEF0h: 32-bit second addend.
  • ghi dd ?: 32-bit result variable (plus one extra byte at ghi+4 for the overflow flag).

Flowchart

Code Segment

  • mov dl, 00h: DL is used as a manual carry register. It starts at 0 and is incremented if the upper-word addition produces a carry out.
  • mov ax, word ptr abc / mov bx, word ptr def: Load the lower 16 bits of each operand. word ptr tells the assembler to treat the 32-bit variable as a 16-bit read.
  • add ax, bx: Add the lower words. May set CF if the sum exceeds FFFFh.
  • adc ax, bx: Add the upper words plus CF from the lower addition. This is the essential instruction for correct 32-bit addition.
  • jnc move / inc dl: If the upper-word addition also overflowed, record it in DL.

High-Level Overview

  1. Initialize DS and the carry register DL.
  2. Add lower 16-bit words with ADD; store result and retain CF.
  3. Add upper 16-bit words with ADC to include the carry; store result.
  4. Record any final overflow and halt.

Output

C:TASM>masm an32add.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 an32add.obj
LINK : warning L4021: no stack segment

C:TASM>debug an32add.exe
-g

AX=ACF1  BX=9ABC  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 AC PO NC
0B98:0027 CC            INT     3
-d 0B97:0000
0B97:0000  78 56 34 12 F0 DE BC 9A-68 35 F1 AC 00 00 00 00   xV4.....h5......
-q


Understanding the Memory Dump

0B97:0000  78 56 34 12 F0 DE BC 9A-68 35 F1 AC 00 00 00 00
  • 78 56 34 12: abc = 12345678h in little-endian.
  • F0 DE BC 9A: def = 9ABCDEF0h in little-endian.
  • 68 35 F1 AC: ghi = ACF13568h in little-endian. Low word 3568h stored as 68 35; high word ACF1h stored as F1 AC.
  • 00: Overflow byte at ghi+4 = 0 (no carry out of the 32-bit sum).

Version 2 — emu8086 (Windows Emulator)

Tested with: emu8086 v4.08 on Windows 10.

The emu8086 implementation provides a clean, visual way to observe how the carry flag propagates between the two addition passes. It follows the standard COM file structure with data placed at the end of the source.

; emu8086 version -- 8086 Assembly Program to Add Two 32-bit Numbers
#make_COM#

org 100h

; --- Code ---
start:
    mov dl, 00h

    mov ax, word ptr abc
    mov bx, word ptr def
    add ax, bx
    mov word ptr ghi, ax

    mov ax, word ptr abc+2
    mov bx, word ptr def+2
    adc 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 12345678h
def dd 9ABCDEF0h
ghi db 5 dup(0)     ; 4 bytes result + 1 byte overflow flag

Version 3 — NASM (Modern Open-Source Assembler)

Tested with: NASM 2.16.01, DOSBox 0.74-3.

The NASM version is highly portable and follows modern x86 assembly conventions. It explicitly handles memory addressing with square brackets, making the code clear and easy to integrate into larger 16-bit projects.

; NASM version -- 8086 Assembly Program to Add Two 32-bit Numbers
; nasm -f bin an32add.asm -o an32add.com

bits 16
org  100h

start:
    mov dl, 0

    mov ax, [abc]
    mov bx, [def]
    add ax, bx
    mov [ghi], ax

    mov ax, [abc+2]
    mov bx, [def+2]
    adc ax, bx
    mov [ghi+2], ax

    jnc move
    inc dl
move:
    mov [ghi+4], dl

    mov ax, 4c00h
    int 21h

; --- Data ---
abc dd 12345678h
def dd 9ABCDEF0h
ghi times 5 db 0    ; 4 bytes result + 1 byte overflow

Frequently Asked Questions

Why use ADC for the upper word instead of a second ADD?

ADC (Add with Carry) adds its two operands plus the current CF. When the lower-word ADD overflows, CF is set to 1. The upper-word addition must include this carry or the result will be wrong by 1 for every case where the lower half overflows. ADD ignores CF entirely, so using it for the upper word silently drops the carry.

What does word ptr do when reading from a dd variable?

dd declares a 32-bit (4-byte) variable. When you write mov ax, word ptr abc, the word ptr override tells the assembler to read only 2 bytes from the address of abc — the lower 16 bits. Adding +2 to the address skips the first word and reads the upper 16 bits. Without word ptr, the assembler would see a size mismatch between a dd variable and a 16-bit register and refuse to assemble.

Can the 32-bit sum ever exceed 32 bits?

Yes. The maximum 32-bit sum is FFFFFFFFh + FFFFFFFFh = 1FFFFFFFEh, which needs 33 bits. The program handles this with the DL register — if the upper-word ADC produces a carry, DL is incremented to 1 and stored at ghi+4. In the current example both values sum to ACF13568h, which fits in 32 bits, so DL stays 0.


Conclusion

32-bit addition on the 8086 is the 16-bit pattern applied twice: ADD the lower words, then ADC the upper words. The one rule to never forget is using ADC for every word beyond the first so that carry propagates correctly across the word boundary.


See Also

7 thoughts on “8086 Assembly Program to Add Two 32-bit Numbers”

  1. mov ax, word ptr abc + 2
    mov bx, word ptr def + 2
    adc ax, bx ; makes no sense

    mov byte ptr ghi+4, dl ; WTF ghi + 4 actually points outside of the data segment.

    here’s how it’s done:
    jmp @f
    extra dw 0
    n1 dd 0FFFFFFFFh
    n2 dd 0FFFFFFFFh
    @@:
    mov ax, word ptr [n1]
    mov bx, word ptr [n1+2]
    mov cx, word ptr [n2]
    mov dx, word ptr [n2+2]
    add ax, cx
    adc bx, dx
    adc [extra], 0

  2. ; ADDs two 32-bit integers into a 64-bit integer using 16-bit CPU (8086-80286)
    ;
    ; ML ADD.ASM
    ; DEBUG ADD.EXE
    ; -g
    ; -d DS:0 F
    ; -q

    ; NOTE: x86 integers are Little Endian and so appear inverted in memory

    data segment
    a1 dd 0FFFFFFFFh ; 0xFFFFFFFF +
    a2 dd 2h ; 0x00000002 =
    res dq ? 0x0000000100000001
    data ends

    code segment
    assume cs:code, ds:data
    start:
    mov ax, data
    mov ds, ax
    xor dl, dl
    mov ax, word ptr a1 ; add 1st word and store result
    mov bx, word ptr a2
    add ax, bx
    mov word ptr res, ax
    mov ax, word ptr a1+2 ; add 2nd word *with carry* and store result
    mov bx, word ptr a2+2
    adc ax, bx
    mov word ptr res+2, ax
    jnc move
    inc dl ; in case of an overflow, result’s 3rd word becomes 1
    move:
    mov byte ptr res+4, dl
    int 3 ; breaks DEBUG
    mov ax, 4C00h ; terminates DOS program (if called from prompt)
    int 21h
    code ends
    end start

Leave a Reply

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