Деление и умножение в Assembler
Здравствуйте, уважаемые друзья! Продолжаем изучать нашу рубрику, на очереди тема умножения и деления в Assembler. Разберемся со всеми тонкостями этих операций, конечно же, на практическом примере.
Основные команды
- Для умножения в Assembler используют команду
mul
- Для деления в Assembler используют команду
div
Правила умножения в Assembler
Итак, как мы уже сказали, при умножении и делении в Assembler есть некоторые тонкости, о которых дальше и пойдет речь. Тонкости эти состоят в том, что от того, какой размерности регистр мы делим или умножаем многое зависит. Вот примеры:
- Если аргументом команды mul является 1-байтовый регистр (например
mul bl
), то значение этого регистра bl умножится на значение регистра al, а результат запишется в регистр ax, и так будет всегда, независимо от того, какой 1-байтовый регистр мы возьмем.bl*al = ax
- Если аргументом является регистр из 2 байт(например
mul bx
), то значение в регистре bx умножится на значение, хранящееся в регистре ax, а результат умножения запишется в регистр eax.bx*ax = eax
- Если аргументом является регистр из 4 байт(например
mul ebx
), то значение в регистре ebx умножится на значение, хранящееся в регистре eax, а результат умножения запишется в 2 регистра: edx и eax.ebx*eax = edx:eax
Правила деления в Assembler
Почти аналогично реализуется и деление, вот примеры:
- Если аргументом команды div является 1-байтовый регистр (например
div bl
), то значение регистра ax поделится на значение регистра bl, результат от деления запишется в регистр al, а остаток запишется в регистр ah.ax/bl = al, ah
- Если аргументом является регистр из 2 байт(например
div bx
), то процессор поделит число, старшие биты которого хранит регистр dx, а младшие ax на значение, хранящееся в регистре bx. Результат от деления запишется в регистр ax, а остаток запишется в регистр dx.(dx,ax)/bx = ax, dx
- Если же аргументом является регистр из 4 байт(например
div ebx
), то процессор аналогично предыдущему варианту поделит число, старшие биты которого хранит регистр edx, а младшие eax на значение, хранящееся в регистре ebx. Результат от деления запишется в регистр eax, а остаток запишется в регистр edx.(edx,eax)/ebx = eax, edx
Программа
Далее перейдем к примеру: он не должен вызвать у вас каких либо затруднений, если вы читали наши предыдущие статьи, особенно важна статья про вывод на экран, советую вам с ней ознакомиться. Ну а мы начнем:
.386 .model flat,stdcall option casemap:none include ..\INCLUDE\kernel32.inc include ..\INCLUDE\user32.inc includelib ..\LIB\kernel32.lib includelib ..\LIB\user32.lib BSIZE equ 15 .data ifmt db "%d", 0 ;строка формата stdout dd ? cWritten dd ? CRLF WORD ? .data? buf db BSIZE dup(?) ;буфер
Стандартное начало, в котором мы подключаем нужные нам библиотеки и объявляем переменные для вывода чисел на экран. Единственное о чем нужно сказать: новый для нас раздел .data? Знак вопроса говорит о том, что память будет выделяться на этапе компилирования и не будет выделяться в самом исполняемом файле с расширением .exe (представьте если бы буфер был большего размера) . Такое объявление — грамотное с точки зрения программирования.
.code start: invoke GetStdHandle, -11 mov stdout,eax mov CRLF, 0d0ah ;-------------------------деление mov eax, 99 mov edx, 0 mov ebx, 3 div ebx invoke wsprintf, ADDR buf, ADDR ifmt, eax invoke WriteConsoleA, stdout, ADDR buf, BSIZE, ADDR cWritten, 0 invoke WriteConsoleA, stdout, ADDR CRLF, 2, ADDR cWritten,0
В разделе кода, уже по традиции, считываем дескриптор экрана для вывода и задаем значения для перевода каретки. Затем помещаем в регистры соответствующие значения и выполняем деление регистра ebx, как оно реализуется описано чуть выше. Думаю, тут понятно, что мы просто делим число 99 на 3, что получилось в итоге выводим на экран консоли.
;-------------------------умножение mov bx, 4 mov ax, 3 mul bx invoke wsprintf, ADDR buf, ADDR ifmt, eax invoke WriteConsoleA, stdout, ADDR buf, BSIZE, ADDR cWritten, 0 invoke ExitProcess,0 end start
Думаю, что здесь тоже все понятно и без комментариев. Как производиться умножение в Assembler вы тоже можете прочитать чуть выше, ну и результат выводим на экран.
Просмотр консоли
Этот код я поместил в файл seventh.asm, сам файл поместил в папку BIN (она появляется при установке MASM32). Далее открыл консоль, как и всегда, с помощью команды cd
перешел в эту папку и прописал amake.bat seventh
. Скомпилировалось, затем запускаю исполняемый файл и в консоли получаются такие числа:
Как видите, мы правильно посчитали эти операции.
На этом сегодня все! Надеюсь вы научились выполнять деление и умножение на Assembler.
Скачать исходники
Assembler: div & mul. Приветствуем всех! С вами команда IT… | by ITRoot Corp
Приветствуем всех! С вами команда IT Root, и сегодня в планах у нас команды div и mul
- Для умножения в Assembler используют команду MUL
- Для деления в Assembler используют команду DIV
Умножение в Assembler
Для умножения чисел без знака предназначена команда MUL. У этой команды только один операнд — второй множитель, который должен находиться в регистре или в памяти. Местоположение первого множителя и результата задаётся неявно и зависит от размера операнда:
Отличие умножения от сложения и вычитания в том, что разрядность результата получается в 2 раза больше, чем разрядность сомножителей. Также и в десятичной системе — например, умножая двухзначное число на двухзначное, мы можем получить в результате максимум четырёхзначное. Запись «DX:AX» означает, что старшее слово результата будет находиться в DX, а младшее — в AX.
Если старшая часть результата равна нулю, то флаги CF и ОF будут иметь нулевое значение. В этом случае старшую часть результата можно отбросить. Это свойство можно использовать в программе, если результат должен быть такого же размера, как множители.
Если аргументом команды MUL является 1-байтовый регистр (например MUL bl), то значение этого регистра bl умножится на значение регистра al, а результат запишется в регистр ax, и так будет всегда, независимо от того, какой 1-байтовый регистр мы возьмем.
al * bl = ax;
Если аргументом является регистр из 2 байт(например MUL bx), то значение в регистре bx умножится на значение, хранящееся в регистре ax, а результат умножения запишется в регистр eax.
bx * ax = dx:ax
Если аргументом является регистр из 4 байт(например MUL ebx), то значение в регистре ebx умножится на значение, хранящееся в регистре eax, а результат умножения запишется в 2 регистра: edx и eax.
ebx * eax = edx:eax
Деление в Assembler
Для умножения чисел без знака предназначена команда DIV, которая относится к группе команд целочисленной арифметики и производит целочисленное деление с остатком беззнаковых целочисленных операндов.
Делимое, частное и остаток задаются неявно. Делимое является переменной в регистре (или регистровой паре) AX, DX:AX или EDX:EAX в зависимости от кода команды и размера операнда (что также определяет и разрядность делителя). Единственный явный операнд команды — операнд-источник (SRC), задающий делитель — может быть переменной в регистре или в памяти.
Целая часть частного помещается в регистр AL, AX или EAX в зависимости от заданного размера делителя (8, 16 или 32 бита). При этом остаток от целочисленного деления помещается в регистр AH, DX или EDX соответственно.
Действие команды DIV зависит от размера операнда-источника следующим образом:
Если частное, получаемое в результате деления, оказывается слишком велико, чтобы поместиться в целевом регистре-назначении (то есть имеет место переполнение), или если делитель равен нулю, то генерируется особая ситуация #DE.
Если аргументом команды div является 1-байтовый регистр (например DIV bl), то значение регистра ax поделится на значение регистра bl, результат от деления запишется в регистр al, а остаток запишется в регистр ah.
ax / bl = al:ah
Если аргументом является регистр из 2 байт(например DIV bx), то процессор поделит число, старшие биты которого хранит регистр dx, а младшие ax на значение, хранящееся в регистре bx. Результат от деления запишется в регистр ax, а остаток запишется в регистр dx.
(dx:ax) / bx = ax:dx
Если же аргументом является регистр из 4 байт(например DIV ebx), то процессор аналогично предыдущему варианту поделит число, старшие биты которого хранит регистр edx, а младшие eax на значение, хранящееся в регистре ebx. Результат от деления запишется в регистр eax, а остаток запишется в регистр edx.
(edx:eax) / ebx = eax:edx
AfterWord
На этой ноте пост подходит к концу. Теперь вы знаете про команды умножения и деления в Assembler. В одном из следующих постов мы разберём умножение и деление чисел со знаком. Спасибо за внимание и до скорых встреч!)
Не забывайте, что множество интересных статей можно найти на нашем Telegram канале и в Telegram боте, также все статьи мы публикуем в Twitter и Facebook
сборка — Разделение на ассемблере x86
спросил
Изменено 4 года назад
Просмотрено 21к раз
Мой колледж дал мне упражнение:
1. Создайте новый документ в Jasmin
2. Используйте AL-Register, чтобы добавить 9до 8.
3. Вычесть 2.
4. Разделить на 7.
Мое решение:
mov al,9 добавить аль,8 саб аль, 2
Но как мне разделить на 7? Я пробовал что-то вроде div al,7
, но это не работает.
- сборка
- x86
div
операция делит (без знака) значение в регистрах AX, DX:AX или EDX:EAX (делимое) на исходный операнд (делитель) и сохраняет результат в AX (AH:AL), DX:AX или EDX:EAX.
source
итак, чтобы разделить значение на al, нужно сделать:
mov ah, 0 # очистить ah, также можно сделать это раньше, как move ax, 9 mov bl, 7 # подготовить делитель div bl # al = ax / bl, ah = ax % bl
после этого al будет содержать частное, а ah будет содержать остаток
6
Существует инструкция DIV
, которая выполняет деление, но вам нужно сначала поместить делимое в AX
(или один из его братьев и сестер).
Инструкции div
и idiv
не имеют форм, которые принимают немедленную форму. Они принимают только один явный операнд (регистр или память), а делимое неявно в AX, или DX:AX, EDX:EAX или RDX:RAX.
См. этот ответ, чтобы узнать, как их использовать.
Но x86 в 16- и 32-битном режиме имеет инструкцию прямого деления, и на самом деле она немного быстрее, чем div r/m8
на процессорах Intel до Skylake: aam 7
разделит AL на непосредственную 7, помещая частное в AH, остаток в AL. (https://agner.org/optimize/ говорит, что на Haswell задержка на 1 такт ниже, а пропускная способность на 1 такт выше, чем у div r8
. И это 8 мопов вместо 9.)
Обратите внимание, что это отличается от mov cl, 7
/ div cl
, что берет все AX в качестве делимого и помещает
AAM недоступен в 64-битном длинном режиме , удален вместе с другими менее полезными устаревшими инструкциями BCD. Но если это сэкономит количество мопов в целом (включая mov
непосредственно к реестру), это может быть полезно. На Skylake он стоит 11 мкп против 10 у div r8
, а пропускная способность на 1с хуже , и такая же латентность.
Этот код работает только для разделения однозначных чисел.
.модель маленькая .данные msg1 db 'введите дивиденд:$' msg2 db 10,13,'введите делитель:$' результат БД 10,13,'результат:$' дивиденды дб ? делитель дб? .код .запускать мов топор,@данные мов дс, топор мов ах, 9 lea dx, msg1 через 21 час мов ах, 1 через 21 час саб аль, 30ч mov дивиденды, al мов ах, 9lea dx, msg2 через 21 час мов ах, 1 через 21 час саб аль, 30ч mov divisor , al движение, дивиденды мов бл, делитель мов ах,0 раздел бл мов ах, 9 lea dx, результат через 21 час мов ах, 2 добавить al,30h мов дл, аль через 21 час мов ах, 4ch через 21 час конец
Зарегистрируйтесь или войдите в систему
Зарегистрируйтесь с помощью Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя адрес электронной почты и пароль
Опубликовать как гость
Электронная почта
Обязательно, но не отображается
Опубликовать как гость
Электронная почта
Требуется, но не отображается
Сборка— Как разделить ввод на 2, а затем округлить
Изменено 6 лет, 10 месяцев назад
Просмотрено 4к раз
Я не совсем понимаю концепцию div в ассемблере, все, что я знаю, это то, что частное сохраняется в EAX, а остаток в EDX. Но я пытаюсь понять, как разделить пользовательский ввод на 2, и если есть остаток, округлить его. Например:
- N= 3 / 2 = 1,5 округлить до 2
- N= 9 / 2 = 4,5 Округлить до 5
- N= 4 / 2 = 2
- в сборе
- x86
Чтобы разделить на два, вы сдвигаете регистр вправо на один разряд. Младший бит смещается во флаг переноса.
Таким образом, если число четное, 0 сдвигается в CF. Если число нечетное, 1 сдвигается в CF.
Итак, чтобы разделить на два и округлить в большую сторону, вы сдвинетесь вправо на один разряд, а затем прибавите значение флага переноса:
; предположим, что значение находится в EAX шр акс, 1; разделить на 2. Младший бит в флаг переноса адк акс, 0 ; добавить значение флага переноса
1
За исключением случая, когда числитель является максимальным значением, вы можете округлить его, добавив 1 перед делением на 2. Примеры
6 / 2 = 3,0 (6 + 1) / 2 = 3 // остается прежним 7 / 2 = 3,5 (7 + 1) / 2 = 4 // округляется в большую сторону
В общем случае, если вы хотите разделить на н
и округляем, перед делением прибавляем н/2
или н >> 1
. Где n
четно, результат явно хороший. Там, где n
нечетное, это работает так же, как в этом примере деление на 5, где вы добавляете 5/2
, т.е. 2
5/5 = 1,0 (5 + 2) / 5 = 1 // остается прежним 6 / 5 = 1,2 (6 + 2) / 5 = 1 // округлить в меньшую сторону 7 / 5 = 1,4 (7 + 2) / 5 = 1 // округлить в меньшую сторону 8 / 5 = 1,6 (8 + 2) / 5 = 2 // округлить в большую сторону 9 / 5 = 1,8 (9+ 2) / 5 = 2 // округлить в большую сторону
5
Н = (3 + 2 - 1) / 2 xor эдкс, эдкс добавить eax, ecx декабрь акс раздел ecx
Или, если 2 является константой:
inc eax шр акс, 1
Здесь есть над чем подумать:
- Будет ли число переполняться, если вы добавите единицу перед делением?
- Вам нужно обрабатывать отрицательные числа?
- Операция
div
дорогая,шр
намного быстрее
shr
делится на два и пол для чисел без знака
sar
делится на два и пол для чисел со знаком .