Деление на ассемблере: Деление и умножение в 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.

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

Поделиться ссылкой:

Похожее

Assembler: div & mul. Приветствуем всех! С вами команда IT… | by ITRoot Corp

Деление в 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

Команда DIV







ТВ онлайн: более 100 каналов в HD-качестве

1) Большинство каналов бесплатные. 2) Можно смотреть как на сайте, так и в приложениях для iOS, Android и Smart TV. 3) Множество телесериалов и фильмов бесплатно или за 1 рубль. 4) Несколько платных подписок, в том числе 18+ и телеканал «Дождь». 5) А также записи телепередач, новости, ТВ-шоу и др.
Подробнее…




Инструкция 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, которое можно перевести как “разделить”.





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

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


Деление в ассемблере примеры | Gadget-apple.ru

Что такое JavaScript

Если вы интересуетесь программированием вообще, и сайтостроением в частности, то вы наверняка слышали слово JavaScript. И, если вы до сих пор не узнали толком, что же это такое, то пришло время сделать это. Подробнее.

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

Итак, синтаксис команды 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, так как команда DIV не работает непосредственно с числами, а только с регистрами общего назначения или с памятью.

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

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

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

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

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

Здравствуйте, уважаемые друзья! Продолжаем изучать нашу рубрику, на очереди тема умножения и деления в 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

Программа

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

Стандартное начало, в котором мы подключаем нужные нам библиотеки и объявляем переменные для вывода чисел на экран. Единственное о чем нужно сказать: новый для нас раздел .data? Знак вопроса говорит о том, что память будет выделяться на этапе компилирования и не будет выделяться в самом исполняемом файле с расширением .exe (представьте если бы буфер был большего размера) . Такое объявление — грамотное с точки зрения программирования.

В разделе кода, уже по традиции, считываем дескриптор экрана для вывода и задаем значения для перевода каретки. Затем помещаем в регистры соответствующие значения и выполняем деление регистра ebx, как оно реализуется описано чуть выше. Думаю, тут понятно, что мы просто делим число 99 на 3, что получилось в итоге выводим на экран консоли.

Думаю, что здесь тоже все понятно и без комментариев. Как производиться умножение в Assembler вы тоже можете прочитать чуть выше, ну и результат выводим на экран.

Просмотр консоли

Этот код я поместил в файл seventh.asm, сам файл поместил в папку BIN (она появляется при установке MASM32). Далее открыл консоль, как и всегда, с помощью команды cd перешел в эту папку и прописал amake.bat seventh . Скомпилировалось, затем запускаю исполняемый файл и в консоли получаются такие числа:

Как видите, мы правильно посчитали эти операции.

На этом сегодня все! Надеюсь вы научились выполнять деление и умножение на Assembler.

Мнемониками DIV , IDIV записывают команды процессору, обозначающие соответственно беззнаковое деление и деление чисел со знаком.

DIV делитель IDIV делитель

где делитель — это 8− , 16− , 32-битный регистр или 8− , 16− , 32-битная переменная.

Местоположение делимого в командах DIV , IDIV не указывается, оно жестко определено и зависит от размерности (байт, слово, двойное слово) делителя. По этой причине делителем не может быть явно заданное (в команде) число, так как явно заданное число не имеет размерности.

Делимое должно быть помещено:
в AX — если делитель имеет размерность байт, тогда после деления частное находим в регистре AL , остаток от деления — в регистре AH ;
в DX:AX — если делитель имеет размерность слово (2 байта), тогда после деления частное находим в регистре AX , остаток от деления — в регистре DX ;;
в EDX:EAX — если делитель имеет размерность двойное слово (4 байта), тогда после деления частное находим в регистре EAX , остаток от деления — в регистре EDX .

При делении на слово или двойное слово делимое должно быть «расписано» на два регистра. Запись DX:AX означает делимое в виде двойного слова, два старших байта которого помещены в DX , два младших байта — в AX . Запись EDX:EAX означает делимое в виде учетверенного слова (8 байт), четыре старших байта которого помещены в EDX , четыре младших байта — в EAX .

В беззнаковом делении, если делимое не имеет разрядов в той части, которая должна быть помещена в DX или EDX , регистр DX или EDX просто обнуляют. В знаковом делении используют команды cbw , cwd , cdq , при выполнении которых все старшие биты заполняются нулями — для положительных чисел, или единицами — для отрицательных чисел. Команда cbw знаково преобразует (расширяет) содержимое регистра AL до AX , команда cwd преобразует AX до DX:AX , команда cwde преобразует AX до EAX , команда cdq преобразует EAX до EDX:EAX .

В случае превышения (overflow) результатом деления размера того регистра, куда должен быть помещен этот результат, дальнейшее выполнение программы прерывается операционной системой. При беззнаковом делении на байт максимально допустимое частное составляет 255, при делении на слово — 65535. Ввиду малого диапазона обрабатываемых чисел и самого результата, деление на байт или слово, как операция, поддерживаемая современными процессорами Intel, имеет значение, в основном, для обеспечения совместимости с предыдущими версиями процессоров и соответствующими (устаревшими) программами. Однако, чем меньше размерность делителя, тем быстрее выполняется команда, и деление на байт и слово могут и теперь (при подходящих условиях) сослужить пользу в алгоритмах с большими объемами вычислений и критичных ко времени исполнения. Так, для 386-процессоров выполнение деления на двойное слово требует 38 тактов процессора, на слово — 22 такта, на байт — 14 тактов.

Примеры приведены ниже. О функциях PrintLine, PrintText, PrintDec см. VKDEBUG

Метки:  


НАШ САЙТ РЕКОМЕНДУЕТ:

Делись, рыбка, быстро и нацело / Хабр

Деление — одна из самых дорогих операций в современных процессорах. За доказательством далеко ходить не нужно: Agner Fog[

1

] вещает, что на процессорах Intel / AMD мы легко можем получить Latency в 25-119 clock cycles, а reciprocal throughput — 25-120. В переводе на Русский —

МЕДЛЕННО

! Тем не менее, возможность избежать инструкции деления в вашем коде — есть. И в этой статье, я расскажу как это работает, в частности в современных компиляторах(они то, умеют так уже лет 20 как), а также, расскажу как полученное знание можно использовать для того чтобы сделать код лучше, быстрее, мощнее.

Собственно, я о чем: если делитель известен на этапе компиляции, есть возможность заменить целочисленное деление умножением и логическим сдвигом вправо (а иногда, можно обойтись и без него вовсе — я конечно про реализацию в Языке Программирования). Звучит весьма обнадеживающе: операция целочисленного умножения и сдвиг вправо на, например, Intel Haswell займут не более 5 clock cycles. Осталось лишь понять, как, например, выполняя целочисленное деление на 10, получить тот же результат целочисленным умножением и логическим сдвигом вправо? Ответ на этот вопрос лежит через понимание… Fixed Point Arithmetic (далее FPA). Чуть-чуть основ.

При использовании FP, экспоненту (показатель степени 2 => положение точки в двоичном представлении числа) в числе не сохраняют (в отличие от арифметики с плавающей запятой, см. IEE754), а полагают ее некой оговоренной, известной программистам величиной. Сохраняют же, только мантиссу (то, что идёт после запятой). Пример:

0.1 — в двоичной записи имеет ‘бесконечное представление’, что в примере выше отмечено круглыми скобками — именно эта часть будет повторяться от раза к разу, следуя друг за другом в двоичной FP записи числа 0.1.

В примере выше, если мы используем для хранения FP чисел 16-битные регистры, мы не сможем уместить FP представление числа 0.1 в такой регистр не потеряв в точности, а это в свою очередь скажется на результате всех дальнейших вычислений в которых значение этого регистра участвует.

Пусть дано 16-битное целое число A и 16-битная Fraction часть числа B. Произведение A на B результатом дает число с 16 битами в целой части и 16-тью битами в дробной части. Чтобы получить только целую часть, очевидно, нужно сдвинуть результат на 16 бит вправо.

Поздравляю, вводная часть в FPA окончена.

Формируем следующую гипотезу: для выполнения целочисленного деления на 10, нам нужно выполнить умножение Числа Делимого на FP представление числа 0.1, взять целую часть и дело в шля… минуточку… А будет ли полученный результат точным, точнее его целая часть? — Ведь, как мы помним, в памяти у нас хранится лишь приближенная версия числа 0.1. Ниже я выписал три различных представления числа 0.1: бесконечно точное представление числа 0.1, обрезанное после 16-ого бита без округления представление числа 0.1 и обрезанное после 16 ого бита с округлением вверх представление числа 0.1.

Оценим погрешности truncating представлений числа 0.1:

Чтобы результат умножения целого числа A, на Аппроксимацию числа 0.1 давал точную целую часть, нам нужно чтобы:

, либо

Удобнее использовать первое выражение: при

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

. То есть, умножая любое 14-битное число A на truncating with rounding up представление числа 0.1, мы всегда получим точную целую часть, которую бы получили умножая бесконечно точно 0.1 на A. Но, по условию у нас умножается 16-битные число, а значит, в нашем случае ответ будет неточным и довериться простому умножению на truncating with rounding up 0.1 мы не можем. Вот если бы мы могли сохранить в FP представлении числа 0.1 не 16 бит, а, скажем 19, 20 — то все было бы ОК. И ведь можем же!

Внимательно смотрим на двоичное представление — truncating with rounding up 0.1: старшие три бита — нулевые, а значит, и никакого вклада в результат умножения не вносят (новых бит).

Следовательно, мы можем сдвинуть наше число влево на три бита, выполнить округление вверх и, выполнив умножение и логический сдвиг вправо сначала на 16, а затем на 3 (то есть, за один раз на 19 вообще говоря) — получим нужную, точную целую часть. Доказательство корректности такого ’19’ битного умножения аналогично предыдущему, с той лишь разницей, что для 16-битных чисел оно работает правильно. Аналогичные рассуждения верны и для чисел большей разрядности, да и не только для деления на 10.

Ранее я писал, что вообще говоря, можно обойтись и без какого-либо сдвига вовсе, ограничившись лишь умножением. Как? Ассемблер x86 / x64 на барабане:

В современных процессорах, есть команда MUL (есть еще аналоги IMUL, MULX — BMI2), которая принимая один, скажем 32 / 64 -битный параметр, способна выполнять 64 / 128 битное умножение, сохраняя результат частями в два регистра (старшие 32 / 64 бита и младшие, соответственно):

MUL RCX	; умножить RCX на RAX, а результат (128 бит) сохранить в RDX:RAX

В регистре RCX пусть хранится некое целое 62-битное A, а в регистре RAX пускай хранится 64 битное FA представление truncating with rounding up числа 0.1 (заметь, никаких сдвигов влево нету). Выполнив 64-битное умножение получим, что в регистре RDX сохранятся старшие 64 бита результата, или, точнее говоря — целая часть, которая для 62 битных чисел, будет точной. То есть, сдвиг вправо (SHR, SHRX) не нужен. Наличие же такого сдвига нагружает Pipeline процессора, вне зависимости поддерживает ли он OOOE или нет: как минимум появляется лишняя зависимость в, скорее всего и без того длинной цепочке таких зависимостей (aka Dependency Chain). И вот тут, очень важно упомянуть о том, что современные компиляторы, видя выражение вида some_integer / 10 — автоматически генерируют ассемблерный код для всего диапазона чисел Делимого. То есть, если вам известно что числа у вас всегда 53-ех битные (в моей задаче именно так и было), то лишнюю инструкцию сдвига вы получите все равно. Но, теперь, когда вы понимаете как это работает, можете сами с легкостью заменить деление — умножением, не полагаясь на милость компилятору. К слову сказать, получение старших битов 64 битного произведения в C++ коде реализуется интринсиком (something like mulh), что по Asm коду, должно быть равносильно строчкам инструкции {I}MUL{X} выше.

Возможно, с появлением контрактов (в С++ 20 не ждем) ситуация улучшится, и в каких-то кейсах, мы сможем довериться машине! Хотя, это же С++, тут за все отвечает программист — не иначе.

Рассуждения описанные выше — применимы к любым делителям константам, ну а ниже список полезных ссылок:

[1] https://www.agner.org/optimize/instruction_tables.pdf
[2] Круче чем Агнер Фог
[3] Телеграмм канал, с полезной информацией о оптимизациях под Intel / AMD / ARM
[4] Про деление нацело, но по Английски

Лекция 7. Арифметические команды языка Ассемблер: аддитивные и мультипликативные команды целочисленных операций.

7.1. Сложение и вычитание.

7.1.1. ADD – команда для сложения двух
чисел. Она работает как с числами со знаком, так и без знака.

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

Логика
работы команды:

<Приемник> = <Приемник> + <Источник>

Возможные
сочетания операндов для этой команды аналогичны команде MOV.

По
сути дела, это – команда сложения с присвоением, аналогичная принятой в языке C/C++:

Приемник += Источник;

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

После
выполнения команды изменяются флаги, по которым можно определить характеристики
результата:

  1. Флаг CF устанавливается, если
    при сложении произошёл перенос из старшего разряда. Для беззнаковых чисел
    это будет означать, что произошло переполнение и результат получился
    некорректным.
  2. Флаг OF обозначает
    переполнение для чисел со знаком.
  3. Флаг SF равен знаковому биту
    результата (естественно, для чисел со знаком, а для беззнаковых он равен
    старшему биту и особо смысла не имеет).
  4. Флаг ZF устанавливается, если
    результат равен 0.
  5. Флаг PF — признак чётности,
    равен 1, если результат содержит нечётное число единиц.

Примеры:

add ax,5     ;AX = AX + 5

add dx,cx    ;DX = DX + CX

add dx,cl 
  ;Ошибка: разный размер
операндов.

7.1.2. SUB — команда для вычитания одного
числа из другого. Она работает как с числами со знаком, так и без знака.

SUB
Приемник,
Источник

Логика
работы команды:

<Приемник> = <Приемник> — <Источник>

Возможные
сочетания операндов для этой команды аналогичны команде MOV.

По
сути дела, это – команда вычитания с присвоением, аналогичная принятой в языке C/C++:

Приемник -= Источник;

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

На самом деле
вычитание в процессоре реализовано с помощью сложения. Процессор меняет знак
второго операнда на противоположный, а затем складывает два числа.

Примеры:

sub ax,13     ;AX = AX — 13

sub ax,bx    ;AX = AX + BX

sub bx,cl    ;Ошибка: разный размер операндов.

7.1.3. Инкремент и декремент. Очень часто в программах используется операция прибавления
или вычитания единицы. Прибавление единицы называется инкрементом, а вычитание
— декрементом. Для этих операций существуют специальные команды процессора: INC
и DEC. Эти команды не изменяют значение флага CF.

Эти команды
содержит один операнд и имеет следующий синтаксис:

INC
Операнд

DEC
Операнд

Логика работы
команд:

INC: <Операнд>
= < Операнд > + 1

DEC: <Операнд>
= < Операнд > — 1

В качестве
инкремента допустимы регистры и память: reg, mem.

Примеры:

inc ax    
;AX = AX + 1

dec ax    
;AX = AX — 1

7.1.4. NEG – команда для изменения знака
операнда.

Синтаксис:

NEG
Операнд

Логика работы
команды:

<Операнд> = – < Операнд >

В качестве
декремента допустимы регистры и память: reg, mem.

Примеры:

neg ax       ;AX = -AX

7.2. Сложение и вычитание с
переносом.

В системе команд
процессоров x86 имеются специальные команды сложения и вычитания с учётом флага
переноса (CF). Для сложения с учётом переноса предназначена команда ADC, а для вычитания — SBB. В общем, эти команды работают
почти так же, как ADD и SUB, единственное отличие в том, что к младшему разряду
первого операнда прибавляется или вычитается дополнительно значение флага CF.

Они позволяют
выполнять сложение и вычитание многобайтных целых чисел, длина которых больше,
чем разрядность регистров процессора (в нашем случае 16 бит). Принцип
программирования таких операций очень прост — длинные числа складываются
(вычитаются) по частям. Младшие разряды складываются(вычитаются) с помощью
обычных команд ADD и SUB, а затем последовательно складываются(вычитаются)
более старшие части с помощью команд ADC и SBB. Так как эти команды учитывают
перенос из старшего разряда, то мы можем быть уверены, что ни один бит не
потеряется. Этот способ похож на сложение(вычитание) десятичных чисел в
столбик.

На следующем
рисунке показано сложение двух двоичных чисел командой ADD:

При сложении
происходит перенос из 7-го разряда в 8-й, как раз на границе между байтами.
Если мы будем складывать эти числа по частям командой ADD, то перенесённый бит
потеряется и в результате мы получим ошибку. К счастью, перенос из старшего
разряда всегда сохраняется в флаге CF. Чтобы прибавить этот перенесённый бит,
достаточно применить команду ADC:

 

Пример:

#include <iostream.h>

#include <iomanip.h>

void main()

{

  //Сложение двух чисел с учетом переноса: FFFFFFAA + FFFF

  int a, b;

  asm {

      mov eax, 0FFFFFFAAh

      mov ebx, 0FFFFh

      mov edx, 0

      mov ecx, 0

      add eax, ebx

      adc edx, ecx

      mov a, edx

      mov b, eax

   }

   cout << hex << a
<< setw(8) << setfill(‘0’) << b; //10000ffa9

}

7.3. Умножение и деление.

7.3.1. MUL – команда умножения чисел без
знака. У этой команды только один операнд — второй множитель, который должен
находиться в регистре или в памяти. Местоположение первого множителя и
результата задаётся неявно и зависит от размера операнда:

Размер операнда

Множитель

Результат

1 байт

AL

AX

2 байта

AX

DX:AX

4 байта

EAX

EDX:EAX

Отличие умножения
от сложения и вычитания в том, что разрядность результата получается в 2 раза
больше, чем разрядность сомножителей.

Примеры:

mul bl   
;AX = AL * BL

mul ax   
;DX:AX = AX * AX

Если
старшая часть результата равна нулю, то флаги CF и ОF будут иметь нулевое
значение. В этом случае старшую часть результата можно отбросить.

7.3.2. IMUL – команда умножения чисел со
знаком. Эта команда имеет три формы, различающиеся количеством операндов:

1.       С одним операндом — форма,
аналогичная команде MUL. В качестве операнда указывается множитель.
Местоположение другого множителя и результата определяется по таблице.

2.       С двумя операндами — указываются
два множителя. Результат записывается на место первого множителя. Старшая часть
результата в этом случае игнорируется. Кстати, эта форма команды не работает с
операндами размером 1 байт.

3.       С тремя операндами — указывается
положение результата, первого и второго множителя. Второй множитель должен быть
непосредственным значением. Результат имеет такой же размер, как первый
множитель, старшая часть результата игнорируется. Это форма тоже не работает с
однобайтными множителями.

Примеры:

imul cl           ;AX = AL * CL

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 означает, что
произошло переполнение.

7.3.3. DIV
– команда деления чисел без знака. У этой команды один операнд — делитель,
который должен находиться в регистре или в памяти. Местоположение делимого,
частного и остатка задаётся неявно и зависит от размера операнда:

Размер
операнда
(делителя)

Делимое

Частное

Остаток

1 байт

AX

AL

AH

2 байта

DX:AX

AX

DX

4 байта

EDX:EAX

EAX

EDX

При
выполнении команды DIV может возникнуть прерывание (в данном курсе
прерывания мы рассматривать не будем поэтому старайтесь избегать таких
случаев):

  • если делитель равен нулю;
  • если частное не помещается в
    отведённую под него разрядную сетку (например, если при делении слова на
    байт частное больше 255).

Примеры:

div cl   ;AL = AX / CL, остаток в AH

div di   ;AX = DX:AX / DI, остаток в
DX

7.3.4. IDIV
– команда деления чисел со знаком. Единственным операндом является делитель.
Местоположение делимого и частного определяется также, как для команды DIV. Эта
команда тоже генерирует прерывание при делении на ноль или слишком большом
частном.

7.3.5. NOP
– ничего не делающая команда.

Синтаксис:

NOP

Примеры:

nop

 

Пример. (5 + 8) / (2 * 3)

#include <iostream.h>

void main()

{

   asm {

        mov bx, 5 //BL = 5

      add bx, 8
//BL = BL + 8  |  13

      sub bx, 1
//BL = BL — 1  |  12

      mov al, 2
//AL = 2

      mov cl, 3
//CL = 3

      mul
cl    //AX = AL * CL 
|  6

      //AX = 6,
BL = 12

      xchg bx,
ax //AX = 12, BX = 6

      mov dx, 0

      div bx

   }

}

 

Ассемблер. Арифметические инструкции | Уроки Ассемблера

  Обновл. 9 Мар 2021  | 

На этом уроке мы будем разбираться с арифметическими инструкциями в ассемблере на примере INC, DEC, ADD, SUB и пр.

Инструкция INC

Инструкция INC (от англ. «INCREMENT») используется для увеличения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.

Синтаксис инструкции INC:

INC место_назначения

Операндом место_назначения может быть 8-битный, 16-битный или 32-битный операнд.

Пример:

INC EBX ; выполняем инкремент 32-битного регистра
INC DL ; выполняем инкремент 8-битного регистра
INC [count] ; выполняем инкремент переменной count

INC EBX      ; выполняем инкремент 32-битного регистра

INC DL       ; выполняем инкремент 8-битного регистра

INC [count]  ; выполняем инкремент переменной count

Инструкция DEC

Инструкция DEC (от англ. «DECREMENT») используется для уменьшения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.

Синтаксис инструкции DEC:

DEC место_назначения

Операндом место_назначения может быть 8-битный, 16-битный или 32-битный операнд.

Пример:

segment .data
count dw 0
value db 15

segment .text
inc [count]
dec [value]

mov ebx, count
inc word [ebx]

mov esi, value
dec byte [esi]

segment .data

   count dw  0

   value db  15

segment .text

   inc [count]

   dec [value]

   mov ebx, count

   inc word [ebx]

   mov esi, value

   dec byte [esi]

Инструкции ADD и SUB

Инструкции ADD и SUB используются для выполнения простого сложения/вычитания двоичных данных размером один byte, word или doubleword, то есть для сложения или вычитания 8-битных, 16-битных или 32-битных операндов, соответственно.

Синтаксис инструкций ADD и SUB:

ADD/SUB      место_назначения, источник

Инструкции ADD/SUB могут выполняться между:

   регистром и регистром;

   памятью и регистром;

   регистром и памятью;

   регистром и константами;

   памятью и константами.

Однако, как и для других инструкций, операции типа память-в-память невозможны с использованием инструкций ADD/SUB. Операции ADD или SUB устанавливают или сбрасывают флаги переполнения и переноса.

В следующем примере мы спрашиваем у пользователя два числа, сохраняем их в регистрах EAX и EBX, затем выполняем операцию сложения, сохраняем результат в ячейке памяти res и выводим его на экран:

SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1

segment .data

msg1 db «Enter a digit «, 0xA,0xD
len1 equ $- msg1

msg2 db «Please enter a second digit», 0xA,0xD
len2 equ $- msg2

msg3 db «The sum is: «
len3 equ $- msg3

segment .bss

num1 resb 2
num2 resb 2
res resb 1

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80

mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80

mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80

mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80

mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80

; перемещаем первое число в регистр EAX, а второе число — в регистр EBX
; и вычитаем ASCII ‘0’ для конвертации в десятичное число

mov eax, [num1]
sub eax, ‘0’

mov ebx, [num2]
sub ebx, ‘0’

; складываем eax и ebx
add eax, ebx

; добавляем ‘0’ для конвертации суммы из десятичной системы в ASCII
add eax, ‘0’

; сохраняем сумму в ячейке памяти res
mov [res], eax

; выводим сумму
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80

exit:

mov eax, SYS_EXIT
xor ebx, ebx
int 0x80

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

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

SYS_EXIT  equ 1

SYS_READ  equ 3

SYS_WRITE equ 4

STDIN     equ 0

STDOUT    equ 1

 

segment .data

 

   msg1 db «Enter a digit «, 0xA,0xD

   len1 equ $- msg1

 

   msg2 db «Please enter a second digit», 0xA,0xD

   len2 equ $- msg2

 

   msg3 db «The sum is: «

   len3 equ $- msg3

 

segment .bss

 

   num1 resb 2

   num2 resb 2

   res resb 1    

 

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

   mov eax, SYS_WRITE        

   mov ebx, STDOUT        

   mov ecx, msg1        

   mov edx, len1

   int 0x80                

 

   mov eax, SYS_READ

   mov ebx, STDIN  

   mov ecx, num1

   mov edx, 2

   int 0x80            

 

   mov eax, SYS_WRITE        

   mov ebx, STDOUT        

   mov ecx, msg2          

   mov edx, len2        

   int 0x80

 

   mov eax, SYS_READ  

   mov ebx, STDIN  

   mov ecx, num2

   mov edx, 2

   int 0x80        

 

   mov eax, SYS_WRITE        

   mov ebx, STDOUT        

   mov ecx, msg3          

   mov edx, len3        

   int 0x80

 

   ; перемещаем первое число в регистр EAX, а второе число — в регистр EBX

   ; и вычитаем ASCII ‘0’ для конвертации в десятичное число

   mov eax, [num1]

   sub eax, ‘0’

   mov ebx, [num2]

   sub ebx, ‘0’

 

   ; складываем eax и ebx

   add eax, ebx

 

   ; добавляем ‘0’ для конвертации суммы из десятичной системы в ASCII

   add eax, ‘0’

 

   ; сохраняем сумму в ячейке памяти res

   mov [res], eax

 

   ; выводим сумму

   mov eax, SYS_WRITE        

   mov ebx, STDOUT

   mov ecx, res        

   mov edx, 1        

   int 0x80

 

exit:    

  

   mov eax, SYS_EXIT  

   xor ebx, ebx

   int 0x80

Результат выполнения программы:

Enter a digit:
3
Please enter a second digit:
4
The sum is:
7

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

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку
mov eax,’3’
sub eax, ‘0’

mov ebx, ‘4’
sub ebx, ‘0’
add eax, ebx
add eax, ‘0’

mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov ecx,sum
mov edx, 1
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db «The sum is:», 0xA,0xD
len equ $ — msg
segment .bss
sum resb 1

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

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

   mov eax,’3′

   sub     eax, ‘0’

   mov ebx, ‘4’

   sub     ebx, ‘0’

   add eax, ebx

   add eax, ‘0’

   mov [sum], eax

   mov ecx,msg

   mov edx, len

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov ecx,sum

   mov edx, 1

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov eax,1 ; номер системного вызова (sys_exit)

   int 0x80 ; вызов ядра

section .data

   msg db «The sum is:», 0xA,0xD

   len equ $ — msg  

   segment .bss

   sum resb 1

Результат выполнения программы:

The sum is:
7

Инструкции MUL и IMUL

Есть 2 инструкции для умножения двоичных данных:

   инструкция MUL (от англ. «MULTIPLY») обрабатывает данные unsigned;

   инструкция IMUL (от англ. «INTEGER MULTIPLY») обрабатывает данные signed.

Обе инструкции влияют на флаги переноса и переполнения.

Синтаксис инструкций MUL/IMUL:

MUL/IMUL множитель

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

Рассмотрим 3 разных сценария:

Сценарий №1: Перемножаются 2 значения типа byte. Множимое находится в регистре AL, а множителем является значение типа byte в памяти или в другом регистре. Результат произведения находится в AX. Старшие 8 бит произведения хранятся в AH, а младшие 8 бит хранятся в AL:

Сценарий №2: Перемножаются 2 значения типа word. Множимое должно находиться в регистре AX, а множителем является значение типа word в памяти или в другом регистре. Например, для такой инструкции, как MUL DX, вы должны сохранить множитель в DX, а множимое — в AX. В результате получится значение типа doubleword, для которого понадобятся два регистра. Часть высшего порядка (крайняя слева) сохраняется в DX, а часть нижнего порядка (крайняя справа) сохраняется в AX:

Сценарий №3: Перемножаются 2 значения типа doubleword. Множимое должно находиться в EAX, а множителем является значение типа doubleword, хранящееся в памяти или в другом регистре. Результат умножения сохраняется в регистрах EDX и EAX. Биты старшего порядка сохраняются в регистре EDX, а биты младшего порядка сохраняются в регистре EAX:

Пример:

MOV AL, 10
MOV DL, 25
MUL DL

MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL

MOV AL, 10

MOV DL, 25

MUL DL

MOV DL, 0FFH ; DL= -1

MOV AL, 0BEH ; AL = -66

IMUL DL

А в следующем примере мы 3 умножаем на 2 и выводим результат:

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку

mov al,’3’
sub al, ‘0’

mov bl, ‘2’
sub bl, ‘0’
mul bl
add al, ‘0’

mov [res], al
mov ecx,msg
mov edx, len
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov ecx,res
mov edx, 1
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db «The result is:», 0xA,0xD
len equ $- msg
segment .bss
res resb 1

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

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

 

   mov al,’3′

   sub     al, ‘0’

   mov bl, ‘2’

   sub     bl, ‘0’

   mul bl

   add al, ‘0’

   mov [res], al

   mov ecx,msg

   mov edx, len

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov ecx,res

   mov edx, 1

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov eax,1 ; номер системного вызова (sys_exit)

   int 0x80 ; вызов ядра

 

section .data

msg db «The result is:», 0xA,0xD

len equ $- msg  

segment .bss

res resb 1

Результат выполнения программы:

The result is:
6

Инструкции DIV и IDIV

Операция деления генерирует два элемента: частное и остаток. В случае умножения переполнения не происходит, так как для хранения результата используются регистры двойной длины. Однако в случае деления, переполнение может произойти. Если это произошло, то процессор генерирует прерывание.

Инструкция DIV (от англ. «DIVIDE») используется с данными unsigned, а инструкция IDIV (от англ. «INTEGER DIVIDE») используется с данными signed.

Синтаксис инструкций DIV/IDIV:

DIV/IDIV     делитель

Делимое находится в аккумуляторе. Обе инструкции могут работать с 8-битными, 16-битными или 32-битными операндами. Данная операция влияет на все 6 флагов состояния.

Рассмотрим следующие 3 сценария:

Сценарий №1: Делителем является значение типа byte. Предполагается, что делимое находится в регистре AX (16 бит). После деления частное переходит в регистр AL, а остаток переходит в регистр AH:

Сценарий №2: Делителем является значение типа word. Предполагается, что делимое имеет длину 32 бита и находится в регистрах DX и AX. Старшие 16 бит находятся в DX, а младшие 16 бит — в AX. После деления 16-битное частное попадает в регистр AX, а 16-битный остаток — в регистр DX:

Сценарий №3: Делителем является значение типа doubleword. Предполагается, что размер делимого составляет 64 бита и оно размещено в регистрах EDX и EAX. Старшие 32 бита находятся в EDX, а младшие 32 бита — в EAX. После деления 32-битное частное попадает в регистр EAX, а 32-битный остаток — в регистр EDX:

В следующем примере мы делим 8 на 2. Делимое 8 сохраняется в 16-битном регистре AX, а делитель 2 — в 8-битном регистре BL:

section .text
global _start ; должно быть объявлено для использования gcc

_start: ; сообщаем линкеру входную точку
mov ax,’8’
sub ax, ‘0’

mov bl, ‘2’
sub bl, ‘0’
div bl
add ax, ‘0’

mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov ecx,res
mov edx, 1
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра

mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра

section .data
msg db «The result is:», 0xA,0xD
len equ $- msg
segment .bss
res resb 1

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

section .text

   global _start    ; должно быть объявлено для использования gcc

_start:             ; сообщаем линкеру входную точку

   mov ax,’8′

   sub     ax, ‘0’

   mov bl, ‘2’

   sub     bl, ‘0’

   div bl

   add ax, ‘0’

   mov [res], ax

   mov ecx,msg

   mov edx, len

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov ecx,res

   mov edx, 1

   mov ebx,1 ; файловый дескриптор (stdout)

   mov eax,4 ; номер системного вызова (sys_write)

   int 0x80 ; вызов ядра

   mov eax,1 ; номер системного вызова (sys_exit)

   int 0x80 ; вызов ядра

section .data

msg db «The result is:», 0xA,0xD

len equ $- msg  

segment .bss

res resb 1

Результат выполнения программы:

The result is:
4

Оценить статью:

Загрузка…

Поделиться в социальных сетях:

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

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

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

  синтаксис Intel синтаксис pdp-11
-------------------------------------------------- ------
.intel_syntax noprefix
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. равный
добавить 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 ).

См .: http://www.felixcloutier.com/x86/IDIV.html и http://www.felixcloutier.com/x86/DIV.html

Я горячо ненавижу синтаксис ATT или PDP, который бессмысленен и сломан.
Вы можете восстановить работоспособность и использовать синтаксис Intel в Gas с помощью псевдо-инструкции .intel_syntax noprefix .
См .: Могу ли я использовать синтаксис Intel сборки x86 с GCC?

О языке ассемблера — подраздел

Язык ассемблера — Подразделение

Сокращенный набор команд для всех микросхем в семействе ARM — от ARM2 до StrongARM —
включает в себя странные и замечательные инструкции, такие как MLA (Умножение на Накопление: умножение на два
регистров и добавить к результату содержимое трети) и ASL (арифметический сдвиг влево:
полностью идентична инструкции Logical Shift Left).Более поздние фишки содержат несколько
Дополнительные указания. Однако, насколько мне известно, места в
набор инструкций для чего-то, что было бы очень полезно — инструкции DIV.

Принципы деления

Деление в машинном коде точно такое же, как деление любым другим методом — это просто
дело повторного вычитания. Когда мы делим двенадцать на четыре, мы действительно хотим знать
сколько раз число 4 уместится в число 12, другими словами, сколько раз мы можем
вычтите меньшее число из большего числа, пока большее число не достигнет нуля.В
ответ в этом случае, конечно, будет трижды:

12 - 4 = 8
8 - 4 = 4
4-4 = 0
 

Когда я был совсем маленьким, мне разрешили играть со старомодными механическими счетчиками.
машина. На передней панели машины было множество вертикальных циферблатов, подобных тем, что на
кодовый замок, на котором вы устанавливаете числа, которые хотите вычислить, и был
ручка на одной стороне, которая была намотана от вас, чтобы прибавить текущий номер к
итоговое значение на дисплее или по направлению к вам, чтобы вычесть его.Чтобы осуществить разделение,
нужно было установить первое число, а затем вычесть из него второе число
несколько раз (считая, сколько раз вы поворачивали ручку!), как и в приведенной выше сумме.
Очевидно, однако, что это могло бы быть очень медленным, если бы вы делали сумму вроде 128 ÷ 4.
поскольку ответ — 32, это количество раз, которое вам придется повернуть ручку!

Было бы вполне возможно осуществить разделение в коде ARM, используя эту простую технику,
построив такой цикл:

 MOV R1, # 128; разделить R1
 MOV R2, # 4; от R2
 MOV R0, # 0; инициализировать счетчик
.вычесть
 SUBS R1, R1, R2; вычесть R2 из
                   ; R1 и магазин
                   ; результат обратно в
                   ; R1 установка флагов
 ADD R0, R0, # 1; добавить 1 к счетчику,
                   ; НЕ устанавливаем флаги
 BHI вычесть; перейти к началу
                   ; цикл по условию
                   ; Выше, т.е. R1 равно
                   ; еще больше, чем
                   ; R2. Ответьте сейчас в R0
 

Поскольку даже самый медленный процессор ARM намного быстрее, чем ребенок, поворачивающий ручку, прикрепленную к
жесткий набор шестерен, это может быть даже приемлемым решением.Но есть уловка
обычно используется для сохранения работы на вычислительной машине — и вы также можете использовать ее на компьютере.

Перемещение по

На практике, разделив большое число на гораздо меньшее, вы сместите
меньшее число на один или несколько мест слева, установив его на дисках слева от
там, где он обычно был бы, оставив правые циферблаты на нуле. Степень, в которой вы
сдвиг будет продиктован здравым смыслом и мысленной арифметикой — если вы не можете вычесть это
хоть раз с другого номера, значит, вы зашли слишком далеко! Затем вы вычтите это
сдвинул число столько раз, сколько возможно, чтобы получить первую цифру результата, сбросить все
ваши циферблаты, чтобы сдвинуть его обратно вправо, несколько раз вычитайте снова, чтобы найти следующую цифру,
и продолжайте этот процесс, пока снова не достигнете исходного числа и больше не сможете вычитать
даже то, что.

Используя наш пример 128 ÷ 4, вы в конечном итоге сделаете следующее:

  • Сдвинуть 4 на одну позицию влево — станет 40.
  • Shift 4 другое место влево — становится 400. Это больше 128, так что мы зашли слишком далеко. Начнем с предыдущего результата — 40. Итак:
  • 128-40 = 88 (поворот ручки один раз)
  • 88-40 = 48 (дважды повернув)
  • 48-40 = 8 (трижды повернув)
  • Мы больше не можем вычесть 40, поэтому теперь мы обнаружили, что первая цифра ответа — 3.
  • Теперь сдвигаем наше число назад вправо, чтобы оно снова стало 4, и снова начинаем считать обороты ручки.
  • 8 — 4 = 4 (однократный поворот ручки)
  • 4-4 = 0 (дважды повернув)

Мы больше не можем вычесть 4 (наше исходное число), поэтому мы нашли последнюю цифру числа
ответ будет 2. Другими словами, ответ 32, результат, который мы получили раньше, но у нас было
обернуть ручку только пять раз, чтобы получить ее, а не тридцать два раза!

Вы могли заметить, что это почти тот же метод, который используется при делении с использованием
ручка и бумага… «четыре в один не пойдут … четыре в двенадцать входят три раза … четыре в
восемь идет дважды … ответ тридцать два «. Разница в том, что мы знаем по опыту,
что 12 — это 4 × 3, а 8 — это 4 × 2. Машины, даже электронные, такие как компьютеры,
не имеют этого преимущества — они не содержат справочного набора таблиц умножения, поэтому
они должны делать это трудным путем.

Двоичное деление

Компьютеры используют двоичные числа. Вы не поверите, но это на самом деле делает жизнь намного проще, когда
приходит к написанию процедуры машинного кода для выполнения деления одного регистра на другой!
Поскольку значение каждой двоичной цифры ответа может быть только 0 или 1, мы можем избежать
упомянутая выше «проблема таблицы умножения».

Каждый раз, когда мы сдвигаем нашу цифру 1 вправо, чтобы получить следующую цифру ответа,
мы знаем, что сможем вычесть его либо ровно один раз, либо вообще не сможем. Нам только нужно
выполнять одно вычитание за смену; недостатком является то, что, поскольку двоичные числа имеют много
больше цифр, чем их десятичные эквиваленты, мы должны выполнить гораздо больше сдвигов.

Деление на два

Здесь я воспользуюсь новым примером. Причина этого в том, что в двоичной системе деление на
четыре или любая другая степень двойки чрезвычайно проста; ни один здравомыслящий программист не напишет
сложная процедура деления, когда все, что ему нужно сделать, это использовать одну инструкцию для
сдвинуть соответствующий регистр вправо, не больше, чем он достанет ручку и бумагу, чтобы
вычислить ответ на сумму «460 ÷ 10» вместо того, чтобы мысленно сбивать
ноль, чтобы получить правильный ответ.Разумная программа для деления 128 на 4 выглядела бы так:

 MOV R1, # 128
 MOV R0, R1, LSR # 2; смена R1 2 разряда
                  ;Направо &
                  ; хранить в R0
                  ; ответь сейчас в R0
 

Поскольку 4 равно 2 × 2, все, что нам нужно сделать, чтобы разделить на 4 в двоичном формате, — это сдвинуть регистр на два.
местами вправо, так же как все, что нам нужно сделать, чтобы разделить на 100 (10 × 10) в десятичной дроби, это
сдвинуть на два места вправо — например, от 600 пенсов получаем 6 фунтов.

Разделение на другие числа

Однако этот подход работает только тогда, когда мы хотим разделить на фиксированное значение, которое, как нам известно, является
кратно двум.Для более общего использования нам нужно использовать подход, основанный на вычитании, описанный в общих чертах.
выше.

Чтобы разделить 50 (% 110010) на 10 (% 1010) в двоичном формате:

  • Мы сдвигаем 10 насколько возможно (на два места) влево, пока оно не станет% 101000, и вычтем это из% 110010, чтобы получить первая цифра.
  • % 110010-% 101000 =% 1010 (первая цифра 1)
  • Теперь мы сдвигаем% 101000 назад на одну позицию вправо и пытаемся вычесть% 10100 из того, что осталось от 25, с которых мы начали.
  • % 1010 -% 10100 (Следующая цифра 0 — не пойдет!)
  • Сдвиньте вправо, получите% 1010 и попробуйте еще раз:
  • % 1010 -% 1010 =% 0 (Успешное вычитание — следующая цифра 1)

Наша цифра «10» теперь сдвинута на две позиции вправо, возвращая ее к исходному значению, которое является нашим сигналом к ​​остановке и подсчету цифр в нашем ответе -% 101 в двоичном формате или «5» в десятичном, w
ich, конечно, правильный ответ.

Реализация процедуры в машинном коде

Показав, что у нас есть рабочий алгоритм двоичного деления, теперь нам нужно перевести его
в настоящие инструкции ассемблера.Я собираюсь разделить R1 на R2; нам также нужно будет использовать
регистры R0 и R4. Прежде чем мы начнем, необходимо выполнить всего одну обязательную проверку .
сделал….

 CMP R2, # 0
 BEQ Diviv_end
 ; проверьте деление на ноль!
 

Настройка дивизиона

Чтобы разделить R1 на R2, первое, что нам нужно сделать, это сдвинуть R2 влево на необходимое
количество мест. Самый простой способ сделать это — просто методом проб и ошибок — сдвигать до тех пор, пока
мы обнаруживаем, что R2 стал слишком большим, и останавливаемся.

 MOV R0, # 0; очистить R0 для накопления результата
 MOV R3, # 1; установить бит 0 в R3, который будет
                    ; сдвинут влево, затем вправо
.Начните
 CMP R2, R1
 MOVLS R2, R2, LSL №1
 MOVLS R3, R3, LSL # 1
 BLS начало
 ; сдвиньте R2 влево, пока
 ; быть больше R1
 ; сдвиньте R3 влево параллельно по порядку
 ; чтобы отметить, как далеко нам нужно пройти
 

R0 будет использоваться для хранения результата. Роль R3 более сложная.

Фактически, мы используем R3, чтобы отметить, где должен находиться правый конец R2 — если мы сдвинем R2
осталось три места, это будет обозначено значением% 1000 в R3.Однако мы также добавляем его в
R0 каждый раз, когда нам удается успешно вычитать, так как он отмечает позицию цифры
в настоящее время рассчитывается в ответе. В приведенном выше двоичном примере (50 ÷ 10) мы
сдвинул «10» на два места влево, поэтому во время первого вычитания R3 был бы
% 100, во время второго (который потерпел неудачу) это было бы% 10, а во время
третий% 1. Добавление его к R0 после каждого успешного вычитания снова дало бы нам
ответ% 101!

Код ARM не имеет конкретных инструкций сдвига и поворота, присутствующих в не-RISC
наборы инструкций.Вместо этого у него есть концепция «барреля-переключателя», который можно использовать для
изменить значение, указанное в правом регистре для почти любой инструкции ,
без изменения самого регистра. Например, инструкция ADD R0, R1, R2, LSL # 2 будет
сложите R1 и (R2 << 2) и загрузите результат в R0, не влияя на значение R2 никак. Это может быть очень полезно, но это означает, что если мы действительно хотим изменить значение R2, сдвигая его, как мы это делаем здесь, мы должны прибегнуть к перемещению его в себя через Переключатель: MOV R2, R2, LSL # 1.

Петля вычитания

Теперь о цикле, который действительно выполняет работу:

.следующий
 CMP R1, R2; перенос установлен, если R1> R2 (не спрашивайте почему)
 SUBCS R1, R1, R2; вычтите R2 из R1, если это
                      ; дать положительный ответ
 ADDCS R0, R0, R3; и добавить текущий бит в R3 к
                      ; накапливающийся ответ в R0
 

При вычитании кода ARM (инструкция CMP имитирует вычитание для установки флагов),
если R1 — R2 дает положительный ответ и «заимствование» не требуется, флаг переноса — , установленный .Это необходимо для правильной работы SBC (вычитание с переносом) при использовании для переноски.
получается 64-битное вычитание, но это сбивает с толку!

В данном случае мы используем это в своих интересах. Флаг переноса устанавливается, чтобы указать, что
возможно успешное вычитание, т. е. такое, которое не дает отрицательного результата, и
две следующие инструкции выполняются только при выполнении условия Carry Set. Обратите внимание, что
‘S’ в конце этих инструкций является частью кода условия ‘CS’ и означает , а не .
значит, что они устанавливают флаги!

 MOVS R3, R3, LSR # 1; Сдвинуть R3 вправо во флаг переноса
 MOVCC R2, R2, LSR # 1; и если бит 0 R3 был равен нулю, также
                           ; сдвинуть R2 вправо
 BCC следующий; если перенос не очищен, R3 сдвинулся
                           ; назад туда, где все началось, и мы
                           ; может закончиться

.Divide_end
 MOV R25, R24; выход из программы
 

Следующие две инструкции сдвигают вправо R3, регистр «счетчик», и R2, который содержит
число, на которое мы делим. Мы специально устанавливаем флаги, используя суффикс ‘S’ при переключении
R3, поскольку мы хотим знать, когда бит, хранящийся в этом регистре, достигает правой части. В течение
сдвиг вправо, бит 0 передается во флаг переноса, в то время как остальные биты перемещаются
вместе. Поскольку установлен только один бит R3 (изначально он был загружен с% 1 перед сдвигом)
влево, а затем вправо), когда установлен флаг переноса, он указывает, что перед сдвигом значение
R3 было% 1, т.е.е. мы вернулись туда, откуда начали, и теперь R0 должен держать правую
отвечать.

Сводка

В конце подпрограммы R1 сохраняет остаток, если таковой имеется, R2 вернул значение, которое он удерживал.
при входе в процедуру R0 сохраняет результат, а R3 сохраняет ноль. И нулевой, и переносящий флаги
установленный. Эта процедура не работает для отрицательных значений R1 или R2.

Как и в случае с результатами целочисленного деления в Basic, значение в R0 всегда будет округляться до
следующий наименьшее целое число , а не до ближайшего числа.Например, 1156 ÷
19 дает результат «60 остатков 16», который на самом деле ближе к 61, чем к 60.



Пример макроса беззнакового целочисленного деления

Главная / Руководство пользователя Assembler

4,23 Целое число без знака
пример макроса деления

Вы можете использовать макрос для выполнения беззнакового целочисленного деления.

Макрос принимает следующие параметры:

$ Бот

Регистр, содержащий делитель.

$ Верхняя

Регистр, в котором хранится дивиденд перед
инструкции выполняются. После выполнения инструкций
он содержит остаток.

$ Div

Регистр частного деления
размещен. Это может быть NULL ( "" )
если требуется только остаток.

$ Темп.

Временный регистр, используемый во время расчета.

Пример беззнакового целочисленного деления с помощью макроса

 МАКРОС
$ Lab DivMod $ Div, $ Top, $ Bot, $ Temp
        ASSERT $ Top <> $ Bot; Вывести сообщение об ошибке, если
        ASSERT $ Top <> $ Temp; поставляемые регистры
        ASSERT $ Bot <> $ Temp; не все разные
        ЕСЛИ "$ Div" <> ""
            ASSERT $ Div <> $ Top; Эти три значения имеют значение, только если $ Div
            ASSERT $ Div <> $ Bot; не является нулем ("")
            ASSERT $ Div <> $ Temp;
        ENDIF
$ Lab
        MOV $ Temp, $ Bot; Поместите делитель в $ Temp
        CMP $ Temp, $ Top, LSR # 1; удвойте это, пока
90 MOVLS $ Temp, $ Temp, LSL # 1; 2 * $ Temp> $ Top
        CMP $ Temp, $ Top, LSR # 1
        BLS% b90; Буква b означает поиск в обратном направлении
        ЕСЛИ "$ Div" <> ""; Пропустите следующую инструкцию, если $ Div
                                    ; нулевой
            MOV $ Div, # 0; Инициализировать частное
        ENDIF
91 CMP $ Top, $ Temp; Можем ли мы вычесть $ Temp?
        SUBCS $ Top, $ Top, $ Temp; Если сможем, сделай так
        ЕСЛИ "$ Div" <> ""; Пропустите следующую инструкцию, если $ Div
                                    ; нулевой
            ADC $ Div, $ Div, $ Div; Двойной $ Div
        ENDIF
        MOV $ Temp, $ Temp, LSR # 1; Уменьшить вдвое $ Temp,
        CMP $ Temp, $ Bot; и цикл до тех пор, пока
        BHS% b91; меньше делителя
        MEND 

Макрос проверяет, что никакие два параметра не используют один и тот же регистр.Также оптимизирует код
производится, если требуется только остаток.

Чтобы избежать множественных определений меток, если DivMod используется более одного раза
в исходном коде ассемблера макрос использует числовые локальные метки (90, 91).

В следующем примере показан код, который создает этот макрос, если он вызывается как
следующее:

Соотношение

 DivMod R0, R5, R4, R2 

Вывод из примера макроса деления

 ASSERT r5 <> r4; Выдает ошибку, если
        ASSERT r5 <> r2; поставляемые регистры
        ASSERT r4 <> r2; не все разные
        ASSERT r0 <> r5; Эти три значения имеют значение, только если $ Div
        ASSERT r0 <> r4; не является нулем ("")
        ASSERT r0 <> r2;
соотношение
        MOV r2, r4; Поместите делитель в $ Temp
        CMP r2, r5, LSR # 1; удвойте это, пока
90 MOVLS r2, r2, LSL # 1; 2 * r2> r5
        ЦМП r2, r5, ЛСР №1
        BLS% b90; Буква b означает поиск в обратном направлении
        MOV r0, # 0; Инициализировать частное
91 СМП r5, r2; Можем ли мы вычесть r2?
        SUBCS r5, r5, r2; Если сможем, сделай так
        АЦП r0, r0, r0; Двойной r0
        MOV r2, r2, LSR # 1; Разрежьте вдвое r2,
        CMP r2, r4; и цикл до тех пор, пока
        BHS% b91; меньше делителя 

Руководства по AVR — 8-битное деление

В этом руководстве мы рассмотрим процедуры 8-битного деления в сборке AVR.AVR не имеет возможности выполнять аппаратное разделение (т.е. нет инструкций по разделению), поэтому подпрограммы должны быть написаны с использованием других инструкций для выполнения этой задачи.

Примечание по применению Atmel AVR200 предоставляет полный список подпрограмм разделения — вместо того, чтобы изобретать велосипед, мы рассмотрим, как они работают.

8-битное беззнаковое деление

Подпрограмма 8-битного беззнакового деления от Atmel показана ниже

 ; ********************************************** ****************************
; *
; * "div8u" - 8/8 битное беззнаковое деление
; *
; * Эта подпрограмма делит две регистровые переменные "dd8u" (делимое) и
; * "dv8u" (делитель).Результат помещается в "dres8u", а остаток в
; * "drem8u".
; *
; * Количество слов: 14
; * Количество циклов: 97
; * Используемые младшие регистры: 1 (drem8u)
; * Использовано старших регистров: 3 (dres8u / dd8u, dv8u, dcnt8u)
; *
; *********************************************** **************************

; ***** Переменные регистра подпрограммы

.def drem8u = r15; остаток
.def dres8u = r16; результат
.def dd8u = r16; дивиденд
.def dv8u = r17; делитель
.def dcnt8u = r18; счетчик циклов

; ***** Код

div8u: sub drem8u, drem8u; очистить остаток и перенести
ldi dcnt8u, 9; счетчик цикла инициализации
d8u_1: rol dd8u; сдвинуть дивиденд влево
dec dcnt8u; счетчик уменьшения
brne d8u_2; если сделано
ret; возвращение
d8u_2: rol drem8u; сдвинуть делимое в остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_3; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_1; иначе
d8u_3: сек; установить перенос, который будет перемещен в результат
rjmp d8u_1  

В этой подпрограмме делитель вводится в регистр dd8u (r16), а делитель помещается в регистр dv8u (r17).По завершении результат будет сохранен в dd8u (r16), а остаток будет в drem8u (r15).

Подпрограмма начинается с очистки регистра, в котором будет храниться остаток от деления, drem8u, с использованием инструкции sub.

  div8u: sub drem8u, drem8u; очистить остаток и перенести  

Вы можете спросить, а почему бы просто не использовать вместо этого инструкцию clr? Что ж, этой подпрограмме необходимо очистить Carry Flag перед выполнением, и clr оставит его неизмененным.Если бы вместо этого использовался clr, clc должен был бы следовать за ним, например

  div8u: clr drem8u; очистить остаток
clc; чистый перенос
  

Вместо этого использование sub очищает регистр и флаг переноса в одной инструкции. Довольно хитро!

Затем dcnt8u инициализируется как счетчик цикла со значением 9.

  ldi dcnt8u, 9; счетчик цикла инициализации  

A For Loop устанавливается на этикетке d8u_1

  d8u_1: rol dd8u; сдвинуть дивиденд влево
dec dcnt8u; счетчик уменьшения
brne d8u_2; если сделано
ret; возврат  

В каждом цикле dcnt8u будет уменьшаться, давая 8 итераций, по одной для каждого бита делимого.Подпрограмма вернется, когда она достигнет нуля.

Затем старший значащий бит делителя сдвигается в младший значащий бит остатка. Мы проверяем, переходит ли делитель в остаток, используя вспомогательную инструкцию. Мы вычитаем делитель из этого значения, и если он отрицательный (проверяется инструкцией brcc), мы знаем, что делитель входит в остаток меньше одного раза. В противном случае мы знаем, что он входит хотя бы один раз.

  d8u_2: rol drem8u; сдвинуть делимое в остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_3; если результат отрицательный  

Если делитель , а не входит в остаток, нам нужно восстановить его (так как он был изменен с помощью вспомогательной инструкции) и сдвинуть ноль в наш результат.

drem8u восстанавливается путем добавления к нему dv8u, флаг переноса очищается инструкцией clc, а rol используется для сдвига нуля (значение флага переноса) в результат. Этот поток показан ниже с удаленными альтернативными ветвями.

  d8u_2: rol drem8u; сдвинуть делимое в остаток
sub drem8u, dv8u; остаток = остаток - делитель
...
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
↓
d8u_1: rol dd8u; сдвинуть ноль в делимое  

Обратите внимание, что dd8u является как входным делимым , так и результатом, поэтому флаг переноса сдвигается в него, а не в другой регистр.

В качестве альтернативы, если делитель не входит в остаток , нам не нужно его восстанавливать. Вместо этого нам нужно сместить единицу в результат, установив флаг переноса.

  d8u_2: rol drem8u; сдвинуть делимое в остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_3; если результат отрицательный
...
d8u_3: сек; установить перенос, который будет перемещен в результат
↓
d8u_1: rol dd8u; сдвинуть единицу на делимое  

Для любого результата используется rjmp для возврата к началу цикла, где другой бит будет сдвинут из делимого, и цикл будет повторяться для всех 8 битов.

  d8u_1: rol dd8u; сдвинуть дивиденд влево
...
rjmp d8u_1  

Использовать эту подпрограмму просто. Просто нужно загрузить дивиденд в r16, а делитель — в r17, прежде чем он будет вызван.

  LDI R16,101; загрузить 101 в r16
ldi r17,10; загрузить 10 в r17

rcall div8u; вычислить 101/10  

В приведенном выше примере r16 будет содержать значение 10 по завершении подпрограммы — результат деления, а r15 будет содержать 1, остаток.

Время выполнения в зависимости от размера кода

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

Вышеупомянутое является «оптимизированным по размеру», но рассмотрим вместо этого «оптимизированную по скорости» версию подпрограммы.

 ; ********************************************** ****************************
; *
; * "div8u" - 8/8 битное беззнаковое деление
; *
; * Эта подпрограмма делит две регистровые переменные "dd8u" (делимое) и
; * "dv8u" (делитель). Результат помещается в "dres8u", а остаток в
; * "drem8u".
; *
; * Количество слов: 66 + возврат
; * Количество циклов: 50/58/66 (мин / среднее / макс) + возврат
; * Используемые младшие регистры: 1 (drem8u)
; * Использовано старших регистров: 2 (dres8u / dd8u, dv8u)
; *
; *********************************************** **************************

; ***** Переменные регистра подпрограммы

.def drem8u = r15; остаток
.def dres8u = r16; результат
.def dd8u = r16; дивиденд
.def dv8u = r17; делитель

; ***** Код

div8u: sub drem8u, drem8u; очистить остаток и перенести

rol dd8u; сдвинуть дивиденд влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_1; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_2; иначе
d8u_1: сек; установить перенос, который будет перемещен в результат

d8u_2: rol dd8u; сдвинуть делимое влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_3; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_4; иначе
d8u_3: сек; установить перенос, который будет перемещен в результат

d8u_4: rol dd8u; сдвинуть дивиденд влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_5; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_6; иначе
d8u_5: сек; установить перенос, который будет перемещен в результат

d8u_6: rol dd8u; сдвинуть дивиденд влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_7; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_8; иначе
d8u_7: сек; установить перенос, который будет перемещен в результат

d8u_8: rol dd8u; сдвинуть дивиденд влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_9; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_10; иначе
d8u_9: сек; установить перенос, который будет перемещен в результат

d8u_10: rol dd8u; сдвинуть дивиденд влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_11; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_12; иначе
d8u_11: сек; установить перенос, который будет перемещен в результат

d8u_12: rol dd8u; сдвинуть дивиденд влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_13; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_14; иначе
d8u_13: сек; установить перенос, который будет перемещен в результат

d8u_14: rol dd8u; сдвинуть дивиденд влево
rol drem8u; сдвинуть дивиденды на остаток
sub drem8u, dv8u; остаток = остаток - делитель
brcc d8u_15; если результат отрицательный
добавить drem8u, dv8u; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8u_16; иначе
d8u_15: сек; установить перенос, который будет перемещен в результат

d8u_16: rol dd8u; сдвинуть делимое влево
Ret
  

Версия с «оптимизацией по скорости» значительно длиннее — 66 слов вместо 14.Однако он может выполняться за 58 циклов по сравнению с 97 для версии с оптимизированным размером.

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

8-битное деление со знаком

Знаковое 8-битное деление на самом деле довольно просто, если вы знаете, как делать беззнаковое деление. Вам просто нужно проверить, являются ли делитель и делимое отрицательными, преобразовать их значение Two’s Complement в величину, если они есть, и продолжить беззнаковое деление, как и раньше.В конце, если один или другой был отрицательным (но не оба!), Вам нужно преобразовать результат обратно в значение со знаком. Это показано ниже

 ; ********************************************** ****************************
; *
; * "div8s" - 8/8 битное знаковое деление
; *
; * Эта подпрограмма делит две регистровые переменные "dd8s" (делимое) и
; * "dv8s" (делитель). Результат помещается в "dres8s", а остаток в
; * "drem8s".
; *
; * Количество слов: 22
; * Количество циклов: 103
; * Используемые младшие регистры: 2 (d8s, drem8s)
; * Использовано старших регистров: 3 (dres8s / dd8s, dv8s, dcnt8s)
; *
; *********************************************** **************************

; ***** Переменные регистра подпрограммы

.def d8s = r14; регистр знака
.def drem8s = r15; остаток
.def dres8s = r16; результат
.def dd8s = r16; дивиденд
.def dv8s = r17; делитель
.def dcnt8s = r18; счетчик циклов

; ***** Код

div8s: mov d8s, dd8s; переместить делимое в регистр знака
eor d8s, dv8s; знак xor с делителем
sbrc dv8s, 7; если установлен старший бит делителя
neg dv8s; изменить знак делителя
sbrc dd8s, 7; если установлен старший бит дивиденда
neg dd8s; изменить знак делителя
sub drem8s, drem8s; очистить остаток и перенести
ldi dcnt8s, 9; счетчик цикла инициализации
d8s_1: rol dd8s; сдвинуть дивиденд влево
dec dcnt8s; счетчик уменьшения
brne d8s_2; если сделано
sbrc d8s, 7; если установлен MSB знакового регистра
neg dres8s; изменить знак результата
ret; возвращение
d8s_2: rol drem8s; сдвинуть делимое в остаток
sub drem8s, dv8s; остаток = остаток - делитель
brcc d8s_3; если результат отрицательный
добавить drem8s, dv8s; восстановить остаток
clc; чистый перенос, который нужно преобразовать в результат
rjmp d8s_1; иначе
d8s_3: сек; установить перенос, который будет перемещен в результат
rjmp d8s_1  

Эта подпрограмма деления со знаком требует на один регистр больше, чем раньше — d8s (r14).d8s используется для отслеживания знаков операндов и результата.

Подпрограмма начинается с копирования делимого dd8s в знаковый регистр d8s и выполнения исключающего ИЛИ между ним и делителем dv8s. Если делимое или делитель отрицательное, будет установлен бит 7 знакового регистра — в противном случае он будет очищен.

  div8s: mov d8s, dd8s; переместить делимое в регистр знака
eor d8s, dv8s; знак xor с делителем  

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

  sbrc dv8s, 7; если установлен старший бит делителя
neg dv8s; изменить знак делителя
sbrc dd8s, 7; если установлен старший бит дивиденда
neg dd8s; изменить знак делителя  

Затем процедура деления может продолжаться точно так же, как и раньше, поскольку мы знаем, что используем два беззнаковых значения. Единственное отличие в том, что перед возвратом мы проверяем знаковый регистр d8s. Если установлен его самый старший бит, один из операндов был отрицательным, поэтому мы знаем, что результат должен быть отрицательным и преобразован обратно в знаковое значение с использованием neg

.

  div8s:...
sbrc d8s, 7; если установлен MSB знакового регистра
neg dres8s; изменить знак результата
ret; возвращение
...  

Конечно, если вы предпочитаете скорость размеру кода, эту подпрограмму можно изменить точно так же, как указанную выше, чтобы уменьшить ветвление. Посмотрим, удастся ли тебе написать это самостоятельно!

Заключение

Вот и все. 8-битное деление в сборке AVR. Не так уж и плохо, правда? Надеюсь, вы понимаете, насколько это дороже аппаратных процедур.Избегайте этого, если можете, но если вам нужно … теперь вы знаете, как это сделать.

Микроконтроллер

— Как выполнить деление двух чисел в PIC16F877A на языке ассемблера?

Это другой способ:

 ;
; Файл: main.asm
; Цель: PIC16F877A
; IDE: MPLABX v5.25
; Ассемблер: MPASM v5.84
;
; Описание:
;
; Показать реализацию и тест целочисленного деления.
;
;
; Примечания:
;
; Сообщение на форуме: https://electronics.stackexchange.com/questions/463950/how-do-i-perform-division-of-two-numbers-in-pic16f877a-in-assembly-language
;
;
    список r = dec, n = 0, c = 132
    errorlevel -302, -312
;
# включить "p16F877A.inc "

     __CONFIG _FOSC_XT & _WDTE_OFF & _PWRTE_OFF & _BOREN_OFF & _LVP_OFF ​​& _CPD_OFF & _WRT_OFF & _CP_OFF

#define FOSC (4000000)
#define FCYC (FOSC / 4)
;
; Определите макросы, которые помогут
; с выбором банка
;
#define BANK0 (h'000 ')
#define BANK1 (h'080 ')
#define BANK2 (h'100 ')
#define BANK3 (h'180 ')
;
; *********************************************** *********************
RESET_VECTOR код 0x000; вектор сброса процессора
    нет
    перейти к началу; начать инициализацию PIC

; ------------------------------------------------- -----------------------
Начните:
    clrf INTCON; Отключить все источники прерываний
    clrf TMR0
    banksel BANK1
    clrf PIE1
    clrf PIE2

    movlw b'11000000 '; Подтягивания отключены, INT край от низкого к высокому, предварительный масштаб WDT 1: 1
    movwf OPTION_REG; Граница тактового сигнала TMR0 от низкого к высокому, тактовый сигнал TMR0 = FCY, предварительное масштабирование TMR0 1: 2

    movlw b'11111111 ';
    movwf TRISA

    movlw b'01111111 ';
    movwf TRISB

    movlw b'11111111 ';
    movwf TRISC

    movlw b'11111111 ';
    movwf TRISD

    ; отключить компараторы
    movlw b'00000111 '
    movwf CMCON

    ; Установите все входы АЦП для цифрового ввода / вывода
    movlw b'00000110 '
    movwf ADCON1

    banksel BANK0
    pagesel главная
    перейти на главную
;
; Межжелковое деление 8x8 с остатком
;
; Частное = делитель / делитель с остатком
;
; Ввод: Dividen
; Делитель
;
; Выход: частное в WREG.
; Остаток
;
Div8x8_DATA udata 0x20
Делитель res 1
Дивиден
Коэффициент разрешения 1
Остаток 1
BitCount res 1

Код Div8x8_CODE
Div8x8:
    banksel BitCount
    movlw 8
    movwf BitCount
    clrf остаток
Div8x8Loop:
    clrc
    rlf Dividen, F
    rlf Остаток, F
    movf Делитель, Вт
    subwf остаток, Вт
    скпнз
    goto BigEnough
    skpc
    goto TooSmall
Достаточно большой:
    movwf остаток
    bsf Dividen, 0
Слишком маленький:
    decfsz BitCount, F
    goto Div8x8Loop
    movf Коэффициент, Вт
    возвращение
;
; Основной цикл приложения
;
MAIN_DATA udata
Значение1 разрешение 1
Значение2 разрешение 1

MAIN_CODE код
главный:
;
; Цикл здесь вечно тестирует Dividen и Divisors
;
ProcessLoop:
;
;
    banksel Value1
    clrf Value1
    decf Value1, F

    clrf Value2
    decf Value2, F

TestLoop:
    banksel Value1
    movf Value1, W
    Banksel Divisor
    movwf Divisor

    banksel Value2
    movf Value2, W
    Banksel Dividen
    movwf Dividen

    позвонить в Div8x8

    banksel Value1
    decfsz Значение1, F
    goto TestLoop
    decf Value1, F

    banksel Value2
    decfsz Значение2, F
    goto TestLoop
    decf Value2, F

    goto ProcessLoop

    конец
  

Если вы собираетесь записать это как домашнее задание, по крайней мере, вы можете добавить комментарии.

8086 Инструкции по целочисленному делению, объясненные с помощью программ сборки

В этом руководстве мы увидим различные инструкции целочисленного деления, поддерживаемые микропроцессорами 8086. Мы также предоставим примеры программ на ассемблере для каждой инструкции деления.

В этой серии руководств по микропроцессору 8086, которые мы обсуждали ранее; Режимы адресации микропроцессора 8086, инструкции передачи данных 8086, инструкции целочисленной арифметики 8086, инструкции умножения целых чисел 8086.

8086 Microprocessor Division Instructions

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

  • DIV (числа без знака)
  • IDIV (числа со знаком)
  • AAD

8086 Инструкция DIV (беззнаковые операнды)

Команда DIV выполняет деление двух беззнаковых операндов. Знаменатель находится в исходном операнде, и он не должен быть немедленным.Однако это может быть регистр или ячейка памяти. Есть четыре случая деления в зависимости от количества битов. Деление может быть:

  1. Байт с байтом
  2. Слово со словом
  3. Слово с байтом
  4. Двойное слово со словом

Байт с байтовым делением

В этом случае операнды знаменателя и знаменателя являются байтами. Номинатор находится в регистре AL, а AH устанавливается в ноль. После деления инструкция сохраняет частное в AL, а остаток в регистре AH.

Код примера сборки
  ORG 100ч
.МОДЕЛЬ МАЛЫЙ
.ДАННЫЕ
NUM_1 DB 0F2H
NUM_2 DB 4H
.КОД
MOV BH, NUM_2; Загрузить числитель в BH
MOV AL, NUM_1; Загрузить знаменатель в AL
DIV BH; Разделить BH на AL
RET
  
Выход

Инструкция DIV делит BH на AL. F2, деленное на 04, дает частное 3C и дает 02 в качестве остатка. AL сохраняет частное, а остаток хранится в регистре AH.

Слово со словом Деление

В этом случае регистр AX содержит знаменатель. После деления частное сохраняется в регистре AX, а остаток поступает в регистр DX.

Код примера сборки
  ORG 100ч
.МОДЕЛЬ МАЛЫЙ
.ДАННЫЕ
NUM_1 DW 0F213H
NUM_2 DW 41A8H
.КОД
MOV AX, NUM_1; Загрузить числитель в AX
DIV NUM_2; разделить AX на NUM_2
RET
  
Выход

Окно вывода показывает, что деление F213H на 41A8 дает остаток 2D1B и 03 как частное.

Слово с байтом

Числитель — это 16-битное слово, хранящееся в AX, которое делится с 8-битным знаменателем. После деления AL содержит частное, а AH будет содержать остаток.

Код примера сборки
  ORG 100ч
.МОДЕЛЬ МАЛЫЙ
.ДАННЫЕ
NUM_1 DW 1B25H
NUM_2 DB 24ч
.КОД
MOV AX, NUM_1; Загрузить знаменатель в AX
DIV NUM_2; разделить AX на NUM_2
RET
  
Выход

ЧИСЛО_1 делится на ЧИСЛО_2, что дает частное C1 и остаток 01.Из содержимого регистра AX видно, что AH содержит остаток, а AL — частное.

Двойное слово за словом Divsion

Это последний случай деления, в котором числитель представляет собой 32-битное число, а знаменатель — 16-битное число. В этом случае AX и DX хранят числитель. Наиболее значимая часть находится в регистре DX, а младшие значащие биты числителя находятся в регистре AX.

После выполнения инструкции DIV остаток переходит в регистр DX, а частное находится в регистре AX.

Код примера сборки
  ORG 100ч
.МОДЕЛЬ МАЛЫЙ
.ДАННЫЕ
NUM_1 DW 2413H
.КОД
MOV AX, 5670; Загрузить младшие байты числителя в AX
MOV DX, 4531; Загрузить старшие байты числителя в DX
DIV NUM_1; разделить AX на NUM_1
RET
  
Выход

Здесь числитель в DX и AX, который равен 45315670. Он делится на 4531, чтобы получить частное от 7D9A, которое находится в регистре AX.

Ошибка разделения или ошибка переполнения

Эмулятор сгенерирует прерывание типа 0, если:

  • В случае байта с байтовым делением и слова с байтовым делением частное больше, чем FFH.
  • В случае слова с разделением на слово и двойное слово на слово, если частное больше, чем FFFFH.
  • Когда знаменатель равен 0.

Предположим, что числитель равен 5BBDH, а знаменатель — 21H.

Пример кода сборки

  ORG 100ч
.МОДЕЛЬ МАЛЫЙ
.ДАННЫЕ
NUM_1 DW 5BBDH
NUM_2 DB 21H
.КОД
MOV AX, NUM_1; Загрузить знаменатель в AX
DIV NUM_2; разделить AX на NUM_2
RET
  
Выход

В этом случае окно вывода генерирует ошибку, потому что частное равно 2C7, что превышает максимальный предел регистра AL.Регистр AL может хранить данные только до FFH.

8086 Инструкция IDIV (подписанные операнды)

IDIV — это арифметическая инструкция, которая выполняет операцию деления между двумя числами со знаком. Исходный операнд в инструкции — делитель со знаком. Его можно указать с помощью любого режима адресации, кроме немедленного режима адресации. Как и инструкция DIV, инструкция IDIV также имеет четыре случая:

  1. Байт с байтом
  2. Слово со словом
  3. Слово с байтом
  4. Двойное слово со словом

1.Байт с байтом :

Для деления байта со знаком на байт со знаком числитель сохраняется в регистре AL, а затем знак расширяется до регистра AH. После деления частное переходит в регистр AL, а остаток — в регистр AH. Значение частного должно находиться в диапазоне от +127 до 127, иначе будет генерироваться прерывание ошибки деления.

2. Слово со словом:

В этом случае числитель находится в регистре AX, а оставшиеся старшие значащие биты заполняются копиями битов со знаком.Деление создает частное в AX и остаток в DX. Здесь диапазон частного составляет от -32767 до +32767.

3. Слово с байтом:

Если числитель — 16-битное слово, а знаменатель — байт, то регистры AL и AH сохранят частное и остаток. Но теперь в этом случае весь регистр AX будет хранить числитель.

4. Двойное слово за словом:

В последнем случае двойное слово со знаком делится на одиночное слово со знаком.Поскольку двойное слово имеет 32 бита, 16-битный регистр AX не может его хранить. Таким образом, регистр DX будет хранить наиболее значимые биты числителя, а наименее значимые биты находятся в регистре AX. После деления AX сохранит частное, а DX — остаток.

Пример кода сборки

Этот пример показывает, как все эти инструкции изменяют данные регистров.

  ORG 100ч
.МОДЕЛЬ МАЛЫЙ
.ДАННЫЕ
NUM_1 DD 0C250A91H
NUM_2 DW -0B25H
NUM_3 DB 24H
.КОД
; Байт за байтовым знаковым делением
MOV AL, NUM_3
МОВ БЛ, 3
IDIV BL

; Пословно подписанный раздел
MOV AX, -265H
IDIV NUM_2

; Слово за байтом Знаковое деление
MOV AX, NUM_2
IDIV NUM_3

; Двойное слово по однословному делению со знаком
MOV AX, NUM_1
MOV DX, NUM_1 + 2
MOV BX, 0A234H
IDIV BX
RET
  
Выход

8086 Инструкция AAD

AAD — это мнемоника для «ASCII Adjust for Division».Эта инструкция используется перед делением двух распакованных чисел BCD, так что после деления полученное частное и остаток будут в распакованной форме BCD. Команда AAD выполняет и умножает AH на 10. Затем прибавляет AH к AL и после этого устанавливает AH в 0. Инструкция AAD может изменять флаги PF, SF и ZF.

Пример кода сборки

  ORG 100ч
.МОДЕЛЬ МАЛЫЙ
.КОД
MOV AX, 3730H; ASCII для 70
MOV BL, 03H; Загрузить 03H в BL
AAD
DIV BL
RET
  

Команда «MOV AX, 3730H» сохраняет 37 в регистре AH и 30 в регистре AL.Инструкция AAD умножит AH на 10 и прибавит к AL. AL становится:

AL = (10) (37) + 1C = 3

И AH сбрасывается в 0. После этого инструкция деления выполняется для генерации частного и остатка в распакованных формах BCD.

6502 математика сборки: деление — простой алгоритм, использующий степени двойки

В сети доступно несколько подпрограмм, выполняющих целочисленное деление на языке ассемблера 6502. Тем не менее, я хотел найти собственное решение.Мне нужна была процедура деления для вычисления уклона м для моей программы рисования линий.

Я начал думать о чем-то очень простом, что я мог бы реализовать на BASIC в целях тестирования, а затем перевести на машинный язык.

Самый простой способ выполнить деление — использовать вычитание. Например, если нам нужно оценить:

 10/2 

Мы можем просто вычесть число 2 из числа еще 10 раз, пока делимое (10) не станет равным нулю или меньше делителя (2).Итак:

 10-2 = 8 (1 раз)
8-2 = 6 (2 раза)
6 - 2 = 4 (3 раза)
4-2 = 2 (4 раза)
2-2 = 0 (5 раз) 

Итак, 2 может содержаться в 10 пять раз. Вот что мы искали: 10/2 = 5, это просто.

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

Принцип такой. Предположим, мы хотим выполнить A / B. Если мы удвоим B и если он все еще может содержаться в A, то мы знаем, что B может содержаться в A как минимум два раза.Если мы снова удвоим B и он все еще может содержаться в A, мы узнаем, что B может содержаться в A четыре раза… и так далее. В какой-то момент мы больше не можем умножать на два: A не может содержать такое кратное B. Итак, мы вычитаем наибольшее кратное B, которое может удерживать A, от самого A, и повторяем процесс с разницей, которая теперь является новый дивиденд. Такой подход резко снижает количество необходимых вычитаний.

Попробуем применить этот метод:

 Надо выполнить: 10/2

Установите счетчик (CT) на 0: CT = 0
Установите частичный дивиденд на 0: K = 0

Удвойте делитель: 4, положите K = 4

4 может содержаться в 10? да.CT = 1

Теперь сложим два частных, чтобы получить:

Итоговое частное = Q1 + Q2 = 4 + 1 = 5

Это окончательный результат, так как 10/2 = 5.

 

Следующая программа Commodore 64 BASIC демонстрирует описанный выше метод.

СКАЧАТЬ: алгоритм целочисленного деления (BASIC реализация)

Вы можете найти реализацию этого алгоритма на машинном языке Commodore 64 в исходном коде программы рисования линий.Я также создал тестовую версию, смешанную с программой BASIC. Код BASIC используется для ввода / вывода и показывает частное, вычисленное математическими подпрограммами BASIC.

ЗАГРУЗИТЬ: 31-битная на 15-битная ассемблерная программа с целочисленным делением (тестовая версия)

СКАЧАТЬ: 31-битная на 15-битная ассемблерная программа с целочисленным делением (исходный код)

Обратите внимание, что максимальное значение для B — 32767, так как делитель равен 15 битам. Этого было достаточно, чтобы вычислить наклон линий, но процедуру можно изменить, чтобы поддерживать более высокие значения для B.

Версия сборки использует таблицы для вычисления степеней двойки. Как видите, это прямой перевод программы BASIC, показанной ранее. Вы даже можете найти метки, соответствующие номерам строк BASIC. Обычно вам не советуют этого делать, но я обнаружил, что программу довольно сложно реализовать на языке ассемблера, поэтому я остался близок к версии BASIC, даже если полученный код очень далек от оптимального.

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

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