Поход на рынок, как правило, заканчивается покупкой какого-либо товара. Если мы хотим купить несколько единиц товара одного наименования, то продавец посчитает нам итоговую сумму с использованием простейшего компьютера – микрокалькулятора. В аналогии с ассемблером при умножении товаров программа микрокалькулятора будет оперировать с числами без знака, поскольку нельзя купить минус три пури или минус пять круассанов. Чтобы есть и худеть, даже если очень бы хотелось
Умножение чисел без знака
Допустим, нам требуется сделать для нашего микрокалькулятора программу умножения количества товаров и цен. Применим команду ассемблера MUL для умножения беззнаковых чисел. Работать с этой командой просто, первый множитель по умолчанию хранится в регистре AL или AX, а второй множитель команде передаётся как единственный операнд – хранимый в регистре или в памяти.
Размер операнда | Множитель | Результат |
Байт | AL | AX |
Слово | AX | DX:AX |
Как видно из таблицы, длина множителя (байт или слово) определяются по размеру переданного операнда. Если команда MUL перемножает два байтовых операнда, результатом будет слово, а если перемножаются двухбайтовые операнды, результатом будет двойное слово. При умножении в ассемблере разрядность результата будет ровно в 2 раза больше каждого из исходных значений. Говоря простым языком, если мы перемножаем числа одинаковой разрядности между собой, например 8 битное на 8 битное, то результат будет максимум из 16 бит.
1 2 | mul bl ;AX = AL * BL mul ax ;DX:AX = AX * AX |
Если умножаются два операнда размеров в байт, то результат умножения помещается в регистр AX, а при умножении двух слов, результат комбинируется в регистрах DX:AX, где старшее слово результата будет в DX, а младшее в AX. Не всегда результатом умножения двух байт будет слово или при умножением двух слов – двойное слово. Если старшая часть результата содержит ноль, то флаги CF и OF будут также равны нулю. Соответственно старшую часть результата умножения можно игнорировать, что может быть полезно в определенных случаях.
Умножение чисел со знаком
Теперь переходим к миру математики, в котором знаковые числа перемножаются не реже беззнаковых. В ассемблере для таких операций есть команда IMUL. Инструкция имеет три формы, разнящиеся числом операндов:
Вызов с одним операндом – аналогично команде MUL. Передаваемым операндом может быть регистр или значение в памяти. Операнд умножается на значение в AL (операнд – байт) или AX (операнд – слово). Результат хранится в AX или комбинируется в DX:AX соответственно.
Вызов с двумя операндами – в этой форме перезаписываемый операнд назначения (первый множитель) умножается на передаваемый операнд — источник (второй множитель). В качестве перезаписываемого операнда должен указываться регистр общего назначения, а вторым операндом может быть непосредственное значение, регистр общего назначения или область памяти. Младшая часть результата помещается в перезаписываемый операнд, старшая часть (дважды от размера операнда – источника) отсекается.
Вызов с тремя операндами – в этой форме используется операнд назначения и два операнда -источника, содержащие первый и второй множители. Первый множитель, которым может быть регистр общего назначение или область памяти, умножается на второй множитель (непосредственное значение). Итоговое произведение операндов (дважды от размера первого операнда – источника) усекается и хранится в операнде назначения (регистр общего назначения).
Примеры использования команды IMUL:
1 2 3 4 | imul bl ;AX = AL * BL imul cx ;DX:AX = AX * CX imul si,-13 ;SI = SI * -13 imul bx,si,123h ;BX = SI * 123h |
Для первой формы флаги CF = 0 и OF = 0 означают, что результат умножения поместился в младшей части, в то время как CF = OF = 1 для команды IMUL в форме с двумя или тремя операндами сигнализируют переполнение, то есть потерю старшей части результата.
Деление чисел без знака
Команда DIV используется для выполнения деления беззнаковых чисел с остатком. Если при умножении разрядность произведения всегда в два раза больше множителей, то при делении действует обратный принцип – большим разрядом является делимое, а частное всегда в два раза меньше делимого. Инструкция принимает единственный операнд – делитель, помещаемый в регистр общего назначения или в память. Размер делителя распределяет делимое, частное и остаток согласно таблице:
Размер операнда (делителя) | Делимое | Частное | Остаток |
Байт | AX | AL | AH |
Слово | DX:AX | AX | DX |
Операция деления в ассемблере может вызывать прерывание (подробнее о прерываниях будет написано в одном из следующих уроков) в ряде случаев:
- При делении на ноль;
- Когда происходит переполнение частного, то есть результат не помещается в отведенную разрядность (например, если делимое – слово (1000), а делитель – байт (2), то результат (500) не поместится в байт).
Примеры:
1 2 | div cl ;AL = AX / CL, остаток в AH div di ;AX = DX:AX / DI, остаток в DX |
Деление чисел со знаком
Команда IDIV используется для деления чисел со знаком. Вызов аналогичен команде DIV – передаётся единственный аргумент – делитель, который неявно определяет размеры делимого, частного и остатка. Прерывание генерируется, если выполняется деление на ноль или частное превышает отведенную разрядность.
Программа к уроку
Применим полученные знания о командах деления и умножения для написания простой программы вычисления общего пройденного пути, имея исходные скорость, ускорение, время и расстояние:
s= s0+ v0t + at2/2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | use16 org 100h ;===== Часть формулы: v0*t ===== mov al,[v] ;AL = v0 mov bl,[t] ;BL = t mul bl ;AX = AL * BL mov si,ax ;Произведение в SI ;===== Часть формулы: (a*t^2)/2 ===== mov al,[t] ;AL = t mul bl ;AX = AL * BL = a*t mov bl,[a] ;BX = a mov bh,0 ;BX - второй множитель, поэтому прибираемся в BH mul bx ;DX:AX = AX * BX = a*t^2 mov bx,2 ;BX = 2 div bx ;DX:AX / BX = a*t^2 / 2 ;===== Складываем все вместе ===== add ax,si ;v0*t + (a*t^2)/2 add al,[s] ;| adc ah,0 ;|... + s0 mov [r],ax ;Храним результат в [r] mov ax,4C00h ;| int 21h ;|Заверешение программы ;-------------------------------------- s db 180 v db 7 a db 4 t db 41 r dw ? |
В 8-й строке результат умножения сохраняется в регистре SI. В 14-й строке старшая часть регистра BX – BH выставляется в ноль, что корректно только для беззнаковых чисел. Попытка присвоить ноль старшей части регистра, хранящего число со знаком может привести к ошибке, если младшая часть регистра хранит отрицательное число. Обратите внимание на строках 21-22 прибавление байта из переменной [s0] к слову AX происходит в два этапа: прибавление числа к AL, и корректировкой на перенос прибавлением нуля к AH.
Упражнение к уроку (1):
z = a2 + 2ab + b2
Числа в формуле используйте 16 битные целые без знака.
Упражнение к уроку (2) (продвинутое):
Используя школьный курс сложения столбиком и команды MUL и ADD, напишите программу для умножения 32-битных чисел без знака. Результатом будет 64-битное число без знака.
7 ответов к “Урок 11. Умножение и деление”
Перейдите вместо связки dosbox, td на emu 8086 там уже все есть, не надо постоянно в конфиг dosbox лезть
Вот тоже считаю, пора подружиться с emu 86-го :)
Это тем, кто на винде. Под линукс нет этого эмулятора. Именно этого нет. Может есть аналоги, но найти не смог. Если кто подскажет, буду благодарен. Впрочем, dosbox не так уж плох, и если нет альтернативы emu8086, то это легко пережить.
use16
org 100h
mov ax,word[a]
mul ax
mov cx,ax
mov si,dx
mov ax,word[a]
mov bx,word[b]
mul bx
add ax,ax
adc dx,dx
add cx,ax
adc si,dx
mov ax,word[b]
mul ax
add cx,ax
adc si,dx
mov word[z],cx
mov word[z+2],si
mov ax,4C00h
int 21h
;——————-
a dw 0x10
b dw 0x2
z rb 4
use16
org 100h
mov ax,word[x]
mov bx,ax
mul word[y]
mov word[z],ax
mov cx,dx
mov ax,bx
mul word[y+2]
add ax,cx
adc dx,0
mov bx,ax
mov cx,dx
mov ax,word[x+2]
mul word[y]
add ax,bx
mov word[z+2],ax
adc cx,dx
mov ax,word[x+2]
mul word[y+2]
add ax,cx
adc dx,0
mov word[z+4],ax
mov word[z+6],dx
mov ax,4C00h
int 21h
;——————-
x dd 0x22222222
y dd 0x11000011
z rb 64
Для первого задание:
.model tiny
.code
org 100h
start:
; Вычисляем a²
mov ax, [a]
mul word ptr [a] ; dx:ax = a²
mov word ptr [z], ax ; Сохраняем младшее слово
mov word ptr [z+2], dx ; Сохраняем старшее слово
; Вычисляем b²
mov ax, [b]
mul word ptr [b] ; dx:ax = b²
mov word ptr [temp], ax ; Сохраняем временно
mov word ptr [temp+2], dx
; Складываем a² + b² (32-битное сложение)
mov ax, word ptr [z]
add ax, word ptr [temp] ; Младшие слова
mov word ptr [z], ax
mov ax, word ptr [z+2]
adc ax, word ptr [temp+2] ; Старшие слова с переносом
mov word ptr [z+2], ax
; Вычисляем 2ab
mov ax, word ptr [a]
mul word ptr [b] ; dx:ax = a*b
shl ax, 1 ; Умножаем на 2 (младшая часть)
rcl dx, 1 ; Старшая часть с переносом
; Прибавляем 2ab к результату (32-битное сложение)
add word ptr [z], ax ; Младшее слово
adc word ptr [z+2], dx ; Старшее слово с переносом
; Завершение программы
mov ax, 4C00h
int 21h
; Данные
a dw 1234
b dw 5678
z dd 0 ; 32-битный результат
temp dd 0 ; Временное хранение
end start
Для второго задания чо-то такое получилось:
.model tiny
.code
org 100h
start:
; — Младшая часть умножения (A_low * B_low) —
mov ax, word ptr [A_low]
mul word ptr [B_low] ; dx:ax = A_low * B_low
mov word ptr [R0], ax ; Младшее слово результата
mov word ptr [R1], dx ; Старшее слово промежуточное
; — Срединная часть 1 (A_low * B_high) —
mov ax, word ptr [A_low]
mul word ptr [B_high] ; dx:ax = A_low * B_high
add word ptr [R1], ax ; Добавляем к промежуточному
adc word ptr [R2], dx ; Учитываем перенос
adc word ptr [R3], 0 ; Распространяем перенос
; — Срединная часть 2 (A_high * B_low) —
mov ax, word ptr [A_high]
mul word ptr [B_low] ; dx:ax = A_high * B_low
add word ptr [R1], ax ; Добавляем к промежуточному
adc word ptr [R2], dx ; Учитываем перенос
adc word ptr [R3], 0 ; Распространяем перенос
; — Старшая часть (A_high * B_high) —
mov ax, word ptr [A_high]
mul word ptr [B_high] ; dx:ax = A_high * B_high
add word ptr [R2], ax ; Добавляем к старшей части
adc word ptr [R3], dx ; Учитываем перенос
; Завершение программы
mov ax, 4C00h
int 21h
; Данные
A_low dw 1111h
A_high dw 2222h
B_low dw 3333h
B_high dw 4444h
; Результат (64 бита)
R0 dw 0 ; Младшие 16 бит
R1 dw 0 ; Следующие 16 бит
R2 dw 0 ; Следующие 16 бит
R3 dw 0 ; Старшие 16 бит
end start
Учитывая прошлые уроки получилось что-то такое, хотя мозг течёт с этого ассемблера чёртово