Умножение и деление выполняются по-разному для чисел со знаком и без, поэтому в системе команд процессора x86 есть отдельные команды умножения и деления для чисел со знаком и для чисел без знака.
Умножение чисел без знака
Для умножения чисел без знака предназначена команда MUL [1]. У этой команды только один операнд — второй множитель, который должен находиться в регистре или в памяти. Местоположение первого множителя и результата задаётся неявно и зависит от размера операнда:
| Размер операнда | Множитель | Результат |
|---|---|---|
| Байт | AL | AX |
| Слово | AX | DX:AX |
Отличие умножения от сложения и вычитания в том, что разрядность результата получается в 2 раза больше, чем разрядность сомножителей. Также и в десятичной системе — например, умножая двухзначное число на двухзначное, мы можем получить в результате максимум четырёхзначное. Запись «DX:AX» означает, что старшее слово результата будет находиться в DX, а младшее — в AX. Примеры:
mul bl ;AX = AL * BL
mul ax ;DX:AX = AX * AX
Если старшая часть результата равна нулю, то флаги CF и ОF будут иметь нулевое значение. В этом случае старшую часть результата можно отбросить. Это свойство можно использовать в программе, если результат должен быть такого же размера, как множители.
Умножение чисел со знаком
Для умножения чисел со знаком предназначена команда IMUL [2]. Эта команда имеет три формы, различающиеся количеством операндов:
- С одним операндом — форма, аналогичная команде MUL [1]. В качестве операнда указывается множитель. Местоположение другого множителя и результата определяется по таблице.
- С двумя операндами — указываются два множителя. Результат записывается на место первого множителя. Старшая часть результата в этом случае игнорируется. Кстати, эта форма команды не работает с операндами размером 1 байт.
- С тремя операндами — указывается положение результата, первого и второго множителя. Второй множитель должен быть непосредственным значением. Результат имеет такой же размер, как первый множитель, старшая часть результата игнорируется. Это форма тоже не работает с однобайтными множителями.
Примеры:
imul cl ;AX = AL * CL
imul si ;DX:AX = AX * SI
imul bx,ax ;BX = BX * AX
imul cx,-5 ;CX = CX * (-5)
imul dx,bx,134h ;DX = BX * 134h
CF = OF = 0, если произведение помещается в младшей половине результата, иначе CF = OF = 1. Для второй и третьей формы команды CF = OF = 1 означает, что произошло переполнение.
Деление чисел без знака
Деление целых двоичных чисел — это всегда деление с остатком! По аналогии с умножением, размер делителя, частного и остатка должен быть в 2 раза меньше размера делимого. Деление чисел без знака осуществляется с помощью команды DIV [3]. У этой команды один операнд — делитель, который должен находиться в регистре или в памяти. Местоположение делимого, частного и остатка задаётся неявно и зависит от размера операнда:
| Размер операнда (делителя) |
Делимое | Частное | Остаток |
|---|---|---|---|
| Байт | AX | AL | AH |
| Слово | DX:AX | AX | DX |
При выполнении команды DIV [3] может возникнуть прерывание (о прерываниях я подробно расскажу потом, пока старайтесь избегать таких случаев):
- если делитель равен нулю;
- если частное не помещается в отведённую под него разрядную сетку (например, если при делении слова на байт частное больше 255).
Примеры:
div cl ;AL = AX / CL, остаток в AH
div di ;AX = DX:AX / DI, остаток в DX
Деление чисел со знаком
Для деления чисел со знаком предназначена команда IDIV [4]. Единственным операндом является делитель. Местоположение делимого и частного определяется также, как для команды DIV [3]. Эта команда тоже генерирует прерывание при делении на ноль или слишком большом частном.
Пример программы
Допустим, в программе требуется вычислять координату какого-то движущегося объекта по формуле:
x = x0 + v0t + at2/2
Все числа в правой части — 8-битные целые без знака, а x — 16-битное целое и тоже без знака. Здесь нужно внимательно следить за размерами операндов.
use16 ;Генерировать 16-битный код
org 100h ;Программа начинается с адреса 100h
mov al,[v0] ;AL = v0
mov cl,[t] ;CL = t
mul cl ;AX = AL*CL = v0*t
mov bx,ax ;BX = AX = v0*t
mov al,[a] ;AL = a
mul cl ;AX = AL*CL = a*t
mov ch,0 ;Преобразуем t в слово в регистре CX
mul cx ;DX:AX = AX*CX = a*(t^2)
mov cl,2 ;CL = 2 = CX, так как CH = 0
div cx ;AX = DX:AX/2 = a*(t^2)/2
add ax,bx ;AX = AX+BX = v0*t + a*(t^2)/2
add al,[x0] ;\
adc ah,ch ;/ AX = AX+x0 = x0 + v0*t + a*(t^2)/2
mov [x],ax ;Сохраняем результат в x
mov ax,4C00h ;\
int 21h ;/ Завершение программы
;-------------------------------------------------------
x0 db 188
v0 db 7
a db 3
t db 25
x dw ?
В 7-й строке промежуточный результат сохраняется в bx. В 11-й строке происходит преобразование байта в слово, путём добавления нулевой старшей части. Такой метод подходит для чисел без знака, но приведёт к ошибке для чисел со знаком (в случае отрицательного числа). Прибавление x0 происходит в два этапа (строки 17 и 18) с учётом переноса, так как мы складываем слово и байт.
Упражнение
Напишите программу для вычисления формулы z = (x·y) / (x + y). Все числа 16-битные целые со знаком.
Сложное упражнение
Напишите программу для умножения двух 32-битных целых без знака. В результате должно получиться 64-битное целое без знака. Подсказка: используйте комбинацию умножения по частям и сложения, как в способе умножения столбиком. (Мой вариант решения вы можете посмотреть здесь [5]).