Деление ассемблер: Деление и умножение в Assembler

Деление и умножение в 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.

Скачать исходники

Команда DIV


Лучшие книги по Ассемблеру

Сделал подборку не новых, но проверенных книг по программированию на языке ассемблера. Если вы также как и я любите погружаться на низкий уровень, в те закоулки мира программирования, куда не всем путь открыт, то посмотрите. Возможно, что-то вам понравится. Подробнее…

Инструкция DIV в Ассемблере выполняет деление без знака. Использование этой инструкции похоже на работу команды MUL, хотя, конечно, имеет некоторые особенности, потому что деление — это не умножение )))

Итак, синтаксис команды DIV такой:

DIV ЧИСЛО

ЧИСЛОМ может быть один из следующих:

  • Область памяти (MEM)
  • Регистр общего назначения (REG)

Эта команда не работает с сегментными регистрами, а также не работает непосредственно с числами. То есть вот так

DIV 200 ; неправильно

делать нельзя.

А теперь алгоритм работы команды DIV:

  • Если ЧИСЛО — это БАЙТ, то AL = AX / ЧИСЛО
  • Если ЧИСЛО — это СЛОВО, то AX = (DX AX) / ЧИСЛО

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

Обратите внимание, что инструкция DIV работает либо с регистром АХ, либо с парой регистров DX AX. То есть перед выполнением этой команды нам надо записать в регистр АХ или пару регистров DX AX значение, которое требуется разделить. Сделать это можно, например, с помощью уже известной нам команды MOV.

Затем надо в область памяти или в регистр общего назначения записать делитель — то есть число, на которое будем делить.

Далее мы выполняем деление, и получаем результат либо в регистр АL (если ЧИСЛО — это байт), либо в регистр AX (если ЧИСЛО — это слово).

Остаток от деления

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

  • В регистр АН, если ЧИСЛО — это байт
  • В регистр DX, если ЧИСЛО — это слово

Никакие флаги при этом не изменяются. А если и меняются, то об этом ничего не сказано в документации, следовательно, проверять флаги нет необходимости.

Просто если есть сомнения, что деление выполнено без остатка, надо проверить содержимое регистров AL или DX в зависимости от того, какой размер имеет ЧИСЛО.

Пример деления в Ассемблере

Итак, например, нам надо 250 разделить на 150. Тогда мы делаем так:


MOV AX, 250   ; Делимое в регистр AX
MOV BL, 150   ; Делитель в регистр BL
DIV BL        ; Теперь АL = 250 / 150 = 1, AH = 100

Обратите внимание, что нам приходится два раза использовать команду MOV, так как команда DIV не работает непосредственно с числами, а только с регистрами общего назначения или с памятью.

После выполнения этого кода в регистре АL будет результат целочисленного деления числа 250 на число 150, то есть число 1, а в регистре АН будет остаток от деления — число 100 (64 в шестнадцатеричной системе).

Теперь попробуем число 50000000 разделить на 60000.


MOV DX, 762     ; Делимое - в пару регистров DX AX
MOV AX, 61568   ; (DX AX) = 50000000
MOV BX, 60000   ; Делитель в регистр BX
DIV BX          ; Теперь АХ = 50000000 / 60000 = 833 (341h)
                ; DX = 20000 (4E20h)

Для записи делителя в пару регистров DX и AX используются две команды MOV. В нашем примере в регистр DX будет записано число 762 (02FA — в шестнадцатеричной системе), а в регистр АХ — число 61568 (F080 — в шестнадцатеричной системе). А если рассматривать их как одно число (двойное слово), где в старшем слове 762, а в младшем — 61568, то это и будет 50000000 (2FAF080 — в шестнадцатеричной системе).

Затем в регистр BX мы записываем число 60000 и выполняем команду деления. В результате в регистре АХ будет число 833 (или 341 в шестнадцатеричной системе), в регистре DX — остаток от деления, который в нашем случае будет равен 20000 (или 4E20 в шестнадцатеричной системе).

В конце как обычно расскажу, почему эта команда ассемблера называется DIV. Это сокращение от английского слова DIVIDE, которое можно перевести как “разделить”.


Подписаться на Дзен-канал

Вступить в группу «Основы программирования»

Подписаться на рассылки по программированию


Первые шаги в программирование

Главный вопрос начинающего программиста – с чего начать? Вроде бы есть желание, но иногда «не знаешь, как начать думать, чтобы до такого додуматься». У человека, который никогда не имел дело с информационными технологиями, даже простые вопросы могут вызвать большие трудности и отнять много времени на решение. Подробнее…


сборка — Разделение на ассемблере x86

спросил

Изменено 4 года, 4 месяца назад

Просмотрено 26 тысяч раз

Мой колледж дал мне упражнение:

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 в качестве делимого и помещает частное в AL, остаток в AH .

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

Зарегистрируйтесь, используя адрес электронной почты и пароль

Опубликовать как гость

Электронная почта

Обязательно, но не отображается

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Деление в х86 сборке ГАЗ

Задавать вопрос

спросил

Изменено 6 лет, 7 месяцев назад

Просмотрено 18 тысяч раз

Я еще не совсем уверен, как деление работает в ассемблере x86 (синтаксис GAS AT&T). Что я хочу сделать, так это разделить два длинных числа, а затем умножить частное на делитель, чтобы увидеть, равно ли новое число исходному числу (n/m * m = n).

 мовл %ebx, %eax
мовл %ecx, %edx
идивл %edx
%ebx, %edx
cmp %edx, %ebx
je .равный
 

Приведенный выше код представляет собой фрагмент кода, в котором я выполняю деление. ebx и ecx — это два счетчика, которые я хочу разделить, правильно ли, что регистр eax используется в качестве делителя? поэтому, когда я пишу idivl %edx, я делю edx на eax и получаю целое число, ближайшее к 0? Типа 7/2 = 3? Я читал в одном месте, что частное хранится в регистре edx, а остаток в регистре ah, но мне также сказали, что частное хранится в регистре eax, а остаток в регистре edx, что сбило меня с толку.

Хотя главный вопрос здесь таков: я хочу разделить значение регистра ebx на значение регистра ecx, как мне поступить?

Спасибо!

РЕДАКТИРОВАТЬ: приведенный выше код дает исключение с плавающей запятой

  • сборки
  • x86
  • att

7

Инструкция idiv принимает 2 аргумента в 3 регистрах.
Первый неявный аргумент — это делимое, 64-битный аргумент в edx:eax
Младшие 32 бита в eax , старшие 32 бита в edx .
Второй явный аргумент — это делитель, 32-битный аргумент в одном регистре.
По понятным причинам делитель , а не должен быть edx или eax .
Результат возвращается в формате eax = частное, edx = остаток.

Зная это, правильная настройка должна быть следующей:

 Синтаксис Intel Синтаксис pdp-11
-------------------------------------------------- ------
.intel_syntax без префикса
mov ebx, дивиденды
mov ecx, делитель
mov eax,ebx movl %ebx, %eax
cdq cdq //edx:eax = 32-битное делимое
idiv ecx idivl %ecx //делим edx:eax на ecx
imul eax, ecx imull %ecx, %eax //умножить результат на делимое
cmp ebx, eax cmpl %eax, %ebx
je .равно je .равно
add eax,edx addl %edx, %eax //добавить остаток
cmp ebx,eax cmpl %eax,%ebx //теперь должно быть равно
je . equal2 je .equal2
 

Следует помнить, что div/idiv выполняет целочисленное деление!
Результатом всегда является целое число с остатком (который может быть равен нулю).

Не работает с плавающей запятой. Исключение генерируется только в том случае, если результат слишком велик, чтобы уместиться в 32 бита, или если вы делите на ноль, и в этом случае вы получаете ошибку #DE Division.
Причина, по которой вы получаете ошибку целочисленного деления , заключается в том, что вы ошибочно используете edx в качестве делителя, и потому что ваше делимое 32-битное, старшие 32 бита (хранящиеся в edx ) всегда равны нулю, и поэтому у вас есть деление на ноль.
Никогда не используйте одни и те же регистры для делимого и делителя!
Вы получите ту же ошибку, если edx:eax idiv ecx не умещается в 32 бита (т. е. если edx:eax слишком велик по сравнению с ecx ).

См.

Оставить комментарий

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *