Учебный курс. Часть 10. Сложение и вычитание с переносом

Автор: xrnd | Рубрика: Учебный курс | 30-03-2010 | Распечатать запись Распечатать запись

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

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

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

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

Аналогичная ситуация возникает с вычитанием чисел по частям. Чтобы было совсем понятно, приведу пример программы. Допустим, требуется вычислить значение формулы k=i+j-n+1, где переменные k, i, j и n являются 32-битными целыми числами без знака. Складывать и вычитать такие числа придётся в два этапа: сначала вычисления будут производиться с младшими словами операндов, а затем со старшими с учётом переноса.

Для прибавления единицы в данном примере нельзя использовать команду INC, так как она не влияет на флаг CF и мы можем получить ошибку в результате!

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
use16                 ;Генерировать 16-битный код
org 100h              ;Программа начинается с адреса 100h
 
    mov ax,word[i]    ;Загружаем младшую часть i в AX
    mov bx,word[i+2]  ;Загружаем старшую часть i в BX
 
    add ax,word[j]    ;Складываем младшие части i и j
    adc bx,word[j+2]  ;Складываем старшие части i и j
 
    sub ax,word[n]
    sbb bx,word[n+2]  ;BX:AX = i+j-n
 
    add ax,1          ;Команда INC здесь не подходит!
    adc bx,0          ;BX:AX = i+j-n+1
 
    mov word[k],ax    ;\
    mov word[k+2],bx  ;/ Сохраняем результат в k
 
    mov ax,4C00h      ;\
    int 21h           ;/ Завершение программы
;-------------------------------------------------------
i dd 120000
j dd  80500
n dd   2300
k dd      ?

Запись word[i] означает, что мы переопределяем размер переменной (она объявлена как DWORD) и обращаемся к младшему слову. Старшее слово расположено в памяти после младшего, поэтому к адресу переменной надо прибавить 2, и соответствующая запись будет иметь вид word[i+2].

Посмотреть работу программы можно в отладчике:

Обратите внимание, как хранятся переменные в памяти. В процессорах Intel младший байт всегда хранится по младшему адресу, поэтому получается, что в окне дампа значения надо читать справа налево. В регистрах же числа записываются в нормальном виде. Сравните, как выглядит одно и то же значение k в памяти и в регистрах (старшая часть находится в BX, а младшая — в AX).

Одно из преимуществ ассемблера в том, что на нём можно реализовать работу с собственными форматами чисел, например с очень длинными целыми. А в языках высокого уровня выбор всегда ограничен компилятором. Следующая программа складывает два 7-байтных значения (для разнообразия я использовал только один регистр).

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
use16                 ;Генерировать 16-битный код
org 100h              ;Программа начинается с адреса 100h
 
    mov ax,word[x]
    add ax,word[y]
    mov word[z],ax
 
    mov ax,word[x+2]
    adc ax,word[y+2]
    mov word[z+2],ax
 
    mov ax,word[x+4]
    adc ax,word[y+4]
    mov word[z+4],ax
 
    mov al,byte[x+6]
    adc al,byte[y+6]
    mov byte[z+6],al
 
    mov ax,4C00h      ;\
    int 21h           ;/ Завершение программы
;-------------------------------------------------------
x dd 0xF1111111
  dw 0xF111
  db 0x11
y dd 0x22222222
  dw 0x2222
  db 0x22
z rb 7

Обращение к старшему байту записывается как byte[x+6]. Наверно, вы уже догадались, почему 🙂 Команда MOV не меняет состояние флагов, поэтому её можно ставить между командами сложения.

Упражнение

Напишите программу для вычисления формулы d=b-1+a-c. Все числа — 3-х байтные целые без знака. Проверьте работу программы в отладчике. Результаты можете выкладывать в комментариях.

Следующая часть »

Комментарии: