А р и ф м е т и ч е с к и е

к о м а н д ы

Арифметические команды.

Все арифметические команды устанавливают флаги CF, AF, SF, ZF, OF и PF в зависимости от результата операции.

Двоичные числа могут иметь длину 8, 16 и 32 бит.
Значение старшего (самого левого бита) задает знак числа:

  • 0 – положительное,
  • 1 – отрицательное.

Отрицательные числа представляются в так называемом дополнительном коде, в котором для получения отрицательного числа необходимо инвертировать все биты положительного числа и прибавить к нему 1.

Положительное: 20 = 14h 00010100b
Инверсное: 11101011b
Отрицательное: 11101100b = EC = -20
Проверка: 20 - 20 = 0
   00010100b
   11101100b

(1)00000000b

Команды сложения ADD, ADC, INC.

Командам БЕЗРАЗЛИЧНО какие числа складываются (знаковые или нет).

Если в результате сложения результат НЕ поместился в отведенное место, устанавливается флаг переноса CF=1. Команда ADC как раз и реагирует на этот флаг. Вырабатываются еще 4 флага: PF, SF, ZF, OF.

Состояние флагов после выполнения команд ADD, ADC, INC

Флаг Пояснение
CF=1 Результат сложения НЕ поместился в операнде-приемнике
PF=1 Результат сложения имеет четное число бит со значением 1
SF=1 Копируется СТАРШИЙ (ЗНАКОВЫЙ) бит результата сложения
ZF=1 Результат сложения равен НУЛЮ
OF=1 Если при сложении двух чисел ОДНОГО знака результат сложения получился БОЛЬШЕ допустимого значения. В этом случае приемник МЕНЯЕТ ЗНАК.

Команда ADD (ADDition)

Назначение: сложение двух операндов источник и приемник размерностью байт, слово или двойное слово.

Синтаксис: ADD Приемник, Источник

Алгоритм работы:

  • сложить операнды источник и приемник;
  • записать результат сложения в приемник;
  • установить флаги.

Логика работы:<Приемник> = < Приемник> + <Источник>

Применение: Команда add используется для сложения двух целочисленных операндов. Результат сложения помещается по адресу первого операнда. Если результат сложения выходит за границы операнда приемник (возникает переполнение), то учесть эту ситуацию следует путем анализа флага cf и последующего возможного применения команды adc.

Команда ADC (Addition with Carry)

Назначение: cложение двух операндов с учетом переноса из младшего разряда.

Синтаксис: ADC Приемник, Источник

Алгоритм работы:

  • сложить два операнда;
  • поместить результат в первый операнд;
  • в зависимости от результата установить флаги.

Логика работы:<Приемник> = <Приемник> + <Источник> + < CF >

Применение: Команда adc используется при сложении длинных двоичных чисел. Ее можно использовать как самостоятельно, так и совместно с командой add. При совместном использовании команды adc с командой add сложение младших байтов/слов/двойных слов осуществляется командой add, а уже старшие байты/слова/двойные слова складываются командой adc, учитывающей переносы из младших разрядов в старшие. Таким образом, команда adc значительно расширяет диапазон значений складываемых чисел.

.MODEL LARGE, C
;x1=a1+b1                 x1,a1,b1:Long
.DATA
EXTRN x1:DWORD, a1:DWORD, b1:DWORD
.CODE
PUBLIC addaL
addaL PROC FAR
MOV AX, WORD PTR a1 ; ax <== мл. часть а1
MOV BX, WORD PTR a1+2 ; bx <== ст. часть а1
MOV CX, WORD PTR b1 ; cx <== мл. часть b1
MOV DX, WORD PTR b1+2 ; dx <== ст. часть b1
ADD AX,CX ; < ax >:=< ax >+< cx > мл. часть
ADC BX,DX ; < bx >:=< bx >+< dx >+< CF > ст. часть
MOV WORD PTR x1,AX ; мл. часть х1 <== < ax >
MOV WORD PTR x1+2,BX ; ст. часть х1 <== < bx >
RET
addaL ENDP

Команда INC (INCrement operand by 1)

Назначение: увеличение значения операнда в памяти или регистре на 1.

Синтаксис: INC Операнд

Алгоритм работы: команда увеличивает операнд на единицу.

Логика работы:< Операнд > = < Операнд > + 1

Применение: Команда используется для увеличения значения байта, слова, двойного слова в памяти или регистре на единицу. При этом команда не воздействует на флаг cf.

inc ax              ; увеличить значение в ax на 1



Команды вычитания SUB, SBB, DEC и NEG

Команды вычитания SUB, SBB, DEC ОБРАТНЫ соответствующим командам сложения ADD, ADC и INC.
Они имеют те же самые операнды.

SUB (SUBtract — Вычитание).

SBB (SuBtract with Borrow CF — Вычитание с заемом флага переноса CF).

DEC (DECrement operand by 1 — Уменьшение значения операнда на 1).

TITLE subaL
; x1=a1-b1
DATA SEGMENT PARA PUBLIC
EXTRN x1:DWORD,a1:DWORD,b1:DWORD
DATA ENDS
CODE SEGMENT PARA PUBLIC
ASSUME CS:CODE,DS:DATA
PUBLIC suba
suba PROC FAR
MOV AX,WORD PTR a1
MOV BX,WORD PTR a1+2
MOV CX,WORD PTR b1
MOV DX, WORD PTR b1+2
SUB AX,CX
SBB BX,DX
MOV WORD PTR x1,AX
MOV WORD PTR x1+2,BX
RET
suba ENDP
CODE ENDS
END

Команда NEG (NEGate operand)

Назначение: изменение знака (получение двоичного дополнения) источника.

Синтаксис: NEG Операнд

Алгоритм работы:

  • выполнить вычитание (0 – источник) и поместить результат на место источника;
  • если источник = 0, то его значение не меняется.

Логика работы:< Операнд > = - < Операнд >

Применение: Команда используется для формирования двоичного дополнения операнда в памяти или регистре. Операция двоичного дополнения предполагает инвертирование всех разрядов операнда с последующим сложением операнда с двоичной единицей. Если операнд отрицательный, то операция neg над ним означает получение его модуля.

MOV AL,2
NEG AL ; al=0feh — число -2 в дополнительном коде


Команды умножения MUL и IMUL

MUL (MULtiply) - беззнаковое умножение

Назначение: операция умножения двух целых чисел без учета знака.

Синтаксис: MUL сомножитель_1

Алгоритм работы: Команда выполняет умножение двух целочисленных сомножителей. Один из сомножителей указан в качестве операнда. Расположение второго сомножителя указано НЕЯВНО и зависит от размера первого сомножителя.

  • если операнд, указанный в команде — байт, то второй сомножитель должен располагаться в al;
  • если операнд, указанный в команде — слово, то второй сомножитель должен располагаться в ax;
  • если операнд, указанный в команде — двойное слово, то второй сомножитель должен располагаться в eax.

От размера первого сомножителя также зависит размер результата. Результат умножения помещается также в фиксированное место, определяемое размером сомножителей:

  • при умножении байтов результат помещается в ax;
  • при умножении слов результат помещается в пару dx:ax;
  • при умножении двойных слов результат помещается в пару edx:eax.

Логика работы:<Произведение> = <Сомножитель_1> * <Сомножитель_2>

Длина Произведения всегда в ДВА раза больше, чем у Множителя. Причем старшая часть Произведения находится либо в регистре АН, либо в DX.

Эти команды тоже вырабатывают флаги. Устанавливаются флаги CF=1 и OF=1, если результат слишком велик для отведенных ему регистров назначения.

IMUL (Integer MULtiply) - умножение с учетом знака

Назначение: операция умножения двух целочисленных двоичных значений со знаком.
В отличии от MUL, команда может иметь 3 различных вида.

Синтаксис:   IMUL сомножитель_1
  IMUL сомножитель_1, сомножитель_2
  IMUL результат, сомножитель_1, сомножитель_2

Алгоритм работы: Алгоритм работы команды зависит от используемой формы команды. Форма команды с одним операндом требует явного указания местоположения только одного сомножителя, который может быть расположен в ячейке памяти или регистре. Местоположение второго сомножителя фиксировано и зависит от размера первого сомножителя:

  • если операнд, указанный в команде, — байт, то второй сомножитель располагается в al;
  • если операнд, указанный в команде, — слово, то второй сомножитель располагается в ax;
  • если операнд, указанный в команде, — двойное слово, то второй сомножитель располагается в eax.

Результат умножения для команды с одним операндом также помещается в строго определенное место, определяемое размером сомножителей:

  • при умножении байтов результат помещается в ax;
  • при умножении слов результат помещается в пару dx:ax;
  • при умножении двойных слов результат помещается в пару edx:eax.

Команды с двумя и тремя операндами однозначно определяют расположение результата и сомножителей следующим образом:

  • в команде с двумя операндами первый операнд определяет местоположение первого сомножителя. На его место впоследствии будет записан результат. Второй операнд определяет местоположение второго сомножителя;
  • в команде с тремя операндами первый операнд определяет местоположение результата, второй операнд — местоположение первого сомножителя, третий операнд может быть непосредственно заданным значением размером в байт, слово или двойное слово.

Логика работы:<Произведение> = <Сомножитель_1> * <Сомножитель_2>

Длина Произведения всегда в ДВА раза больше, чем у Множителя. Причем старшая часть Произведения находится либо в регистре АН, либо в DX.

Эти команды тоже вырабатывают флаги. Устанавливаются флаги CF=1 и OF=1, если результат слишком велик для отведенных ему регистров назначения.

Умножение больших чисел

Чтобы умножить большие числа, придется вспомнить правила умножения десятичных чисел в столбик: множимое умножают на каждую цифру множителя, сдвигают влево на соответствующее число разрядов и затем складывают полученные результаты. В нашем случае роль цифр будут играть байты, слова или двойные слова, а сложение должно выполняться по правилам сложения чисел повышенной точности. Алгоритм умножения оказывается заметно сложнее, поэтому умножим для примера только 64-битные числа:

; беззнаковое умножение двух 64-битных чисел (X и Y) и сохранение
; результата в 128-битное число Z
MOV EAX,DWORD PTR X
MOV EBX,EAX
MUL DWORD PTR Y ; перемножить младшие двойные слова
MOV DWORD PTR Z,EAX ; сохранить младшее слово произведения
MOV ECX,EDX ; сохранить старшее двойное слово
MOV EAX,EBX ; младшее слово "X" в еах
MUL DWORD PTR Y[4] ; умножить младшее слово на старшее
ADD ЕАХ,ЕСХ
ADC EDX,0 ; добавить перенос
MOV EBX,EAX ; сохранить частичное произведение
MOV ECX,EDX
MOV EAX,DWORD PTR x[4]
MUL DWORD PTR Y ; умножить старшее слово на младшее
ADD EAX,EBX ; сложить с частичным произведением
MOV DWORD PTR z[4],EAX
ADC ECX,EDX
MOV EAX,DWORD PTR X[4]
MUL DWORD PTR Y[4] ; умножить старшие слова
ADD EAX,ECX ; сложить с частичным произведением
ADC EDX,0 ; и добавить перенос
MOV WORD PTR Z[8],EAX
MOV WORD PTR z[12],EDX

Чтобы выполнить умножение со знаком, потребуется сначала определить знаки множителей, изменить знаки отрицательных множителей, выполнить обычное умножение и изменить знак результата, если знаки множителей были разными.


Команды деления DIV и IDIV

Назначение:
DIV выполнение операции деления двух двоичных беззнаковых значений.
IDIV операция деления двух двоичных значений со знаком.

Синтаксис:   DIV делитель
  IDIV делитель

Алгоритм работы: Для команды необходимо задание двух операндов — делимого и делителя. Делимое задается неявно и размер его зависит от размера делителя, который указывается в команде:

  • если делитель размером в байт, то делимое должно быть расположено в регистре ax. После операции частное помещается в al, а остаток — в ah;
  • если делитель размером в слово, то делимое должно быть расположено в паре регистров dx:ax, причем младшая часть делимого находится в ax. После операции частное помещается в ax, а остаток — в dx;
  • если делитель размером в двойное слово, то делимое должно быть расположено в паре регистров edx:eax, причем младшая часть делимого находится в eax. После операции частное помещается в eax, а остаток — в edx.

Логика работы:< Частное: Остаток> = <Делимоё> / <Делитель>

Команда IDIV реагирует на ЗНАК обрабатываемых чисел.

Результат состоит из Частного и Остатка, которым при обычных целочисленных вычислениях пренебрегают.

Длина Делимого всегда в ДВА раза больше, чем у Делителя. Причем старшая часть Делимого находится либо в регистре АН, либо в DX.

Эти команды тоже вырабатывают флаги. Но устанавливаются флаги CF=1 и OF=1, если частное НЕ помещается в регистры AL или АХ.

Здесь, в отличие от команд умножения, может генерироваться ПРЕРЫВАНИЕ "Деление на ноль”.

Применение: Команда выполняет целочисленное деление операндов с выдачей результата деления в виде частного и остатка от деления. При выполнении операции деления возможно возникновение исключительной ситуации: 0 — ошибка деления. Эта ситуация возникает в одном из двух случаев: делитель равен 0 или частное слишком велико для его размещения в регистре eax/ax/al.


Преобразование байта в слово и слова в двойное слово

Данное преобразование для знаковых и беззнаковых данных осуществляется по разному.

БЕЗЗНАКОВЫЕ числа занимают всю ячейку памяти, понятие знак для них НЕ существует - они считаются ПОЛОЖИТЕЛЬНЫМИ. Поэтому при преобразовании БЕЗЗНАКОВЫХ чисел в СТАРШУЮ часть результата надо занести НОЛЬ. Это можно сделать уже известными нам командами: MOV АН,0 или MOV DX,0. Однако это НЕ эффективно, используем родную для компьютера команду сложения по модулю 2:
XOR АН,АН или XOR DX,DX.

Для ЗНАКОВЫХ данных существуют две команды распространения знака.
CBW (Convert Byte toWord — преобразовать байт, находящийся в регистре AL, в слово — регистр АХ) и
CWD (Convert Word to Double word — преобразовать слово, находящееся в регистре АХ, в двойное слово — регистры < DX:AX >).

Синтаксис:   CBW
  CWD

Операнды им НЕ нужны.

Логика работы:
CBW

Получаем старшую часть (АН) Известная младшая часть (AL)
Биты 15 - 8 Знак-бит
7
Биты 6 - 0
1111 1111 1 Информационная часть числа
0000 0000 0 Информационная часть числа
Результат AX

CWD

Получаем старшую часть (DX) Известная младшая часть (AX)
Биты 31 - 16 Знак-бит
15
Биты 14 - 0
1111 1111 1111 1111 1 Информационная часть числа
0000 0000 0000 0000 0 Информационная часть числа
Результат DX:AX

Применение:

  • Команда CBW используется для приведения операндов к нужной размерности с учетом знака. Такая необходимость может, в частности, возникнуть при программировании арифметических операций.
  • Команда CWD используется для расширения значения знакового бита в регистре ax на биты регистра dx. Данную операцию, в частности, можно использовать для подготовки к операции деления, для которой размер делимого должен быть в два раза больше размера делителя, либо для приведения операндов к одной размерности в командах умножения, сложения, вычитания.


Деление больших чисел

Общий алгоритм деления числа любого размера на число любого размера нельзя построить с использованием команды DIV — такие операции выполняются при помощи сдвигов и вычитаний и оказываются весьма сложными. Рассмотрим сначала менее общую операцию (деление любого числа на слово или двойное слово), которую можно легко выполнить с помощью команд DIV.

; деление 64-битного числа divident на 16-битное число divisor.
; Частное помещается в 64-битную переменную quotent,
; а остаток - в 16-битную переменную modulo
MOV AX,WORD PTR DIVIDENT[6]
XOR DX,DX
DIV DIVISOR
MOV WORD PTR QUOTENT[6],AX
MOV AX,WORD PTR DIVIDENT[4]
DIV DIVISOR
MOV WORD PTR QUOTENT[4],AX
MOV AX,WORD PTR DIVIDENT[2]
DIV DIVISOR
MOV WORD PTR QUOTENT[2],AX
MOV AX,WORD PTR DIVIDENT
DIV DIVISOR
MOV WORD PTR QUOTENT,AX
MOV MODULO,DX


Деление любого другого числа полностью аналогично — достаточно только добавить нужное число троек команд mov/div/mov в начало алгоритма.

Наиболее очевидный алгоритм для деления чисел любого размера на числа любого размера — деление в столбик с помощью последовательных вычитаний делителя (сдвинутого влево на соответствующее количество разрядов) из делимого, увеличивая соответствующий разряд частного на 1 при каждом вычитании, пока не останется число, меньшее делителя (остаток):

; деление 64-битного числа в EDX:EAX на 64-битное число в ЕСХ:ЕВХ.
; Частное помещается в EDX:EAX, и остаток - в ESI:EDI
MOV EBP,64 ; счетчик бит
XOR ESI,ESI
XOR EDI,EDI ; остаток = 0
bitloop:
SHL EAX,1
RCL EDX,1
RCL EDI,1 ; сдвиг на 1 бит влево 128-битного числа
RCL ESI,1 ; ESI:EDI:EDX:EAX
CMP ESI,ECX ; сравнить старшие двойные слова
JA divide
JB next
CMP EDI,EBX ; сравнить младшие двойные слова
JB next
divide:
SUB EDI,EBX
SBB ESI,ECX ; ESI:EDI = EBX:ECX
INC EAX ; установить младший бит в ЕАХ
next:
DEC EBP
; повторить цикл 64 раза
JNE bitloop

Несмотря на то что этот алгоритм не использует сложных команд, он выполняется на порядок дольше, чем одна команда DIV.

Лабораторная работа №1

Программирование на Машинно-Ориентированных Языках.
Преподаватель: Коробов С.А.