/
Text
А. В. Фрунзе
VIHKPOKOHTPO
ПРО
ISBN 594929003-В
8594Р '290033
УДК 621.316.544.1@75)
ББК 31.264.5я7
Ф93
Ф93 Фрунзе А. В., Фрунзе А. А.
Микроконтроллеры? Это же просто! Т. 3. — М.:
ООО «ИД СКИМЕН», 2003. — 224 с, илл.
ISBN 5-94929-003-7 (Т. 3)
ISBN 5-94929-001-1
Данная книга посвящена арифметике в позиционных системах счисления,
алгоритмам арифметических операций и их реализациям в микропроцессорной
и микроконтроллерной технике. В книге детально описаны основные принци-
принципы позиционной арифметики, затрагиваются вопросы построения АЛУ и при-
приводятся алгоритмы операций для целых чисел (со знаком и без), а также целых
чисел произвольной разрядности: сложения, вычитания, сравнения, умножения,
деления, арифметических сдвигов, преобразования между системами счисле-
счислений и пр. Для многих из этих операций существует несколько различных алго-
алгоритмов реализации, которые различаются ресурсоемкостью и производитель-
производительностью, и выбор в пользу конкретного алгоритма делается в зависимости от
поставленной задачи и имеющихся аппаратных средств. В книге приводятся и
анализируются альтернативные алгоритмы и предлагаются приемы программи-
программирования, способные повысить производительность разрабатываемого ПО и эф-
эффективность использования процессорных ресурсов. В качестве практических
примеров представляется программные реализации указанных алгоритмов на
языке ассемблера для микроконтроллера семейства х51. Подпрограммы носят
не только иллюстративный и образовательный характер, но имеют также и прак-
практическую ценность и могут быть использованы при разработке ПО для микро-
микроконтроллеров семейства х51 или адаптированы для иных процессоров.
Книга рассчитана на специалистов в микроконтроллерной технике, а также
инженеров, программистов и студентов, ее изучающих и желающих самостоя-
самостоятельно разобраться в данной области.
УДК 621.316.544.1@75)
ББК31.264.5я7
Все права защищены. Никакие материалы данного издания не могут бить воспроизведены в
любой форме или любыми средствами, электронными или механическими, включая фотографи-
фотографирование, ксерокопирование или нные средств копирования или сохранения информации, без пись-
письменного разрешения издательства.
ISBN 5-94929-003-7 (Т. 3) © »ИД СКИМЕН», 2003
ISBN 5-94929-001-1 © А. В. Фрунзе, А. А. Фрунзе, 2003
Содержание
Предисловие 3
Глава 1. Беззнаковые целые числа 5
Глава 2. Преобразование беззнаковых целых чисел 95
Глава 3. Многобайтные беззнаковые целые числа 123
Глава 4. Знаковые целые числа 157
Заключение к третьему тому 219
ПРЕДИСЛОВИЕ К ТРЕТЬЕМУ ТОМУ
Необходимость в книге подобного содержания возникла уже давно. С
тех пор, как в 1989 г. минским издательством «Вышейшая школа» была вы-
выпущена книга А. Л. Гуртовцева и С. В. Гудыменко «Программы для мик-
микропроцессоров», прошло уже более десяти лет, а иных изданий, посвящен-
посвященных алгоритмам арифметических и логических операций и приемам их ре-
реализации, в нашей стране выпущено не было. Более того, сама книга «Про-
«Программы для микропроцессоров» давно превратилась в библиографическую
редкость, о существовании которой мало кто из разработчиков осведомлен.
Поэтому большинству последних приходится самостоятельно изобретать
подобные алгоритмы, пользуясь несистематизированными сведениями, раз-
разбросанными в Интернете и в различных пособиях. Неудивительно, что по-
потратив немало времени на написание и отладку подобных программ, мно-
многие программисты не жаждут делиться ими со своими коллегами, считая
эти программы своим «ноу-хау». И это при том, что сколь-нибудь серьез-
серьезной коммерческой ценности подобные программы не представляют. Пото-
Потому издание подобной книги, знакомство с которой избавит разработчиков
от необходимости изобретать давно изобретенный кем-то велосипед, весь-
весьма актуально, и ее выпуск позволит многим из них легко решить проблемы,
еще вчера казавшиеся крайне сложными.
По мнению авторов, материал, изложенный в книге, представляет цен-
ценность для всех разработчиков, независимо от того, с какими микроконтрол-
микроконтроллерами они работают. Приведенная в книге информация рассмотрена на
двух уровнях — уровне алгоритмов и идей и уровне ассемблерных подпрог-
подпрограмм. Вряд ли кому нужно доказывать, что общие принципы построения
подпрограмм арифметических и логических операций, а также преобразо-
преобразований кодов едины для всех цифровых устройств. Поэтому знакомство с
информацией верхнего уровня — с алгоритмами и идеями, различными
стандартными вычислительными схемами таких сложных операций, как ум-
умножение и деление, принципами и приемами повышения быстродействия
разрабатываемых программ, — полезно для всех разработчиков микрокон-
троллериой техники, работающих с любыми микроконтроллерами.
Что касается информации нижнего уровня, то и она может оказаться
полезной не только для тех, кто работает с микроконтроллерами семейства
х51. Не секрет, что правильно написанные и хорошо документированные
тексты ассемблерных программ для одного из микропроцессоров очень легко
переводятся на ассемблер любого другого процессора или контроллера. Ав-
Авторы настоящей книги знают об этом не понаслышке — основой целого
ряда из описанных в настоящей книге подпрограмм на ассемблере х51 яв-
являются их прототипы на ассемблере 8080, изложенные в уже упоминавшей-
ся книге «Программы для микропроцессоров». Очевидно, что адаптация ас-
ассемблерных текстов под любой другой 8-разрядный микроконтроллер, будь
то PIC-контроллер фирмы Microchip или AVR фирмы Atmel, может быть
осуществлена столь же легко. Тем более, что для этого в ряде разделов кни-
книги приведены тестовые примеры с результатами вычислений, на которых
можно быстро проверить правильность работы адаптированных программ.
Конкретные реализации программ написаны на стандартном ассембле-
ассемблере 51-х микроконтроллеров. Поэтому для тех, кто работает с х51, все оче-
очевидно. Бы просто можете использовать приведенные в книге программы в
своих разработках, даже не разбираясь в том, как они работают. Многие из
приведенных программ могут быть использованы в качестве примеров для
разработки оригинальных программ, а также для знакомства с техникой и
навыками составления программ на ассемблере х51. И наконец, они суще-
существенно пополнят багаж имеющегося в распоряжении начинающих програм-
программистов программного обеспечения.
И последний аспект, на который авторы хотели бы обратить внимание.
Бытует мнение, что из-за сложности написания арифметических подпрог-
подпрограмм на языке ассемблера их нужно писать на языках высокого уровня.
Приводимый в книге материал демонстрирует, что ассемблерные арифме-
арифметические программы весьма просты. Последнее весьма актуально для тех
разработчиков микроконтроллерной техники, которые «выжимают макси-
максимум» из самых быстродействующих AVR-, PIC- и CYGN AL-контроллеров и
вынуждены предельно оптимизировать свои программы по быстродей-
быстродействию. Подробное описание в настоящей книге алгоритмов и тщательное
документирование ассемблерных подпрограмм позволит таким разработ-
разработчикам написать наиболее критичные по времени выполнения фрагменты
своих программ на ассемблере, избежать использования в них почти не до-
допускающих оптимизации фрагментов программ на языках высокого уров-
уровня и добиться действительно предельного быстродействия своих изделий.
Исходные тексты программ, описанных в книге, можно найти на сайте
http://www.pyrometer.ru. Тексты всех программ написаны и отлажены Алек-
Алексеем Фрунзе.
Авторы понимают, что несмотря на большой объем работы по тести-
тестированию описываемых программ гарантировать 100-процентное отсутствие
ошибок в программном обеспечении такого объема вряд ли возможно. По-
Поэтому они обращаются к пользователям с просьбой незамедлительно сооб-
сообщать им об ошибках, если таковые найдутся, по адресу alex.fru@mtu-net.ru,
и в редакцию журнала «Схемотехника». Со своей стороны авторы гаранти-
гарантируют исправление таких ошибок и помещение информации о них (вместе с
исправленными подпрограммами) на http://www.pyrometer.ru.
Александр Фрунзе, Алексей Фрунзе
5
КОНСУЛЬТАЦИОННО-
ТЕХНИЧЕСКИЙ
ЦЕНТР
по МИКРОКОНТРОЛЛЕРАМ
KTU-NK
Мвкрокоитроллеры в макропроцессоры, поставляемые фирмой КТЦ-МК
Фнрмы-
протводштслн
Atmel
Cypre» Semiconductor
Filrchild
Semiconductor
Fnjltra
DM Microelectronic!
Infineon Technologic*
Intel
Mstorolm
Mntfonal
Semlcondnctor
Philips Semlcnidnctui
Samsung
Sharp
STMkrotkctronki
Tnu iMtnimcnti
Tnthibn
Zilog
4blt
MARC4
8blt
MCS51(8x51V
R1SC(AVR)
l<Mt
RISC (PSoC) '
ACEx
F2MC-8L
MCS51
CISC
(HC08/9SO8)
COPS
MCS51
(LPC900.
LPC90X)
SAW86RCW
SAM88RCR/
CalmRISC
MCS51 (uPSD)
MCS51
(MSC12XX)
RISC
(Z8.Z8O.ZI 80)
F2MC-I6LX
XCI61
8x188/8x196/
x86
9S12
CR16
XA
CI61 (ST10)
MSP430
900x
32btt
ARM7/TDMI/
ARM920
FR3O/5O/3O0/SO0
PowerPC
Dxx,«xx,7xx)
TriCore
Xscalc(PXA255,
PXA26x)
68K/ColdFiic/
MCorc/ARM/
PowerPC
ARM7TDMI
ARM7/ARM9/
ARM10
ARM7/ARM9
ГХ19ЛХ39
Mbit
PovraP(9xx)
ТХ/49Л00/44/43
КТЦ-МК
ПОСТАВКА электронных компопентов
ОБЕСПЕЧЕНИЕ технической информацией
Техническая ПОДДЕРЖКА и КОНСУЛЬТАЦИИ в режиме on-line
РАЗРАБОТКА и ПРОИЗВОДСТВО электронных изделий любой сложности
127030 Москва, 1-й Щемиловский пер., 19
тел.: @95) 973-1855, 730-2085
e-mail: info@ccc-mc.ru
www.cec-mc.ru
ГЛАВА 1
БЕЗЗНАКОВЫЕ ЦЕЛЫЕ ЧИСЛА
В первой главе мы рассмотрим самый простой случай — неотрица-
неотрицательные или беззнаковые целые числа. В последующих главах кни-
книги мы включим в рассмотрение и числа, которые могут быть отри-
отрицательными, т. е. числа со знаком. Не обойдем вниманием также
класс вещественных чисел (дробей) в форматах с фиксированной и
плавающей точкой, но это будет в следующем томе.
Итак, начали. В знакомой нам десятичной системе каждое число
состоит из цифр, имеющих 10 возможных значений1: от 0 до 9. Каж-
Каждая цифра числа представляет собой степень десятки. Так, например,
десятичное число 8547 означает 8 -10} + 5 • 102 + 4 - 101 + 7 • 10°, т. е.
число 8547 состоит из восьми тысяч, пяти сотен, четырех десятков и
семи единиц. Обычно мы не пишем степеней десятки, они подразуме-
подразумеваются позициями цифр числа: самая правая позиция соответствует
единицам (т. е. 10°), следующая за ней —десяткам A01), затем — сот-
сотням A02), тысячам A0}) и т. д. до цифры, находящейся в самой левой
позиции числа. Вообще, любое целое неотрицательное число Д запи-
записанное п десятичными цифрами dt @ < г < и-1) как
D=dn_,dn-2-dA A)
1 Отсюда берет свое название десятичная система счисления, поскольку в этой систе-
системе ровно десять цифр для представления чисел. Десятичная система была изобретена
в Индии. В середине 8-го века она уже повсеместно использовалась в Индии и начала
проникать в другие страны. Десятичную систему часто называют «арабской», а деся-
десятичные цифры «арабскими», так как европейцы заимствовали ее от арабов. Но назы-
называть систему арабской неправильно, поскольку изобрели ее в Индии.
соответствует величине
V(D) = dn_x • 10"-1 + dn_2 ¦ 10"-2 +... + dx ¦ 101 + d0 ¦ 10°. B)
Такое представление чисел называется позиционным.
В десятичной системе счисления каждая позиция имеет свой
вес — определенную степень десяти. Число 10 называется основани-
основанием десятичной системы, поскольку величина 10 определяет и количе-
количество цифр системы, и отношение весов соседних позиций цифр чис-
числа. Таким образом, число 10 полностью определяет десятичную сис-
систему (за исключением, конечно, начертания самих цифр, которые
принято обозначать 0, 1,2, 3,4, 5, 6,7,8,9J.
Десятичная система счисления нам хорошо знакома еще со школь-
школьной скамьи, мы легко оперируем с десятью цифрами системы и про-
производим арифметические действия над десятичными числами. Од-
Однако, как бы ни было людям удобно использовать десятичную систе-
систему в повседневной жизни, но в цифровых устройствах (в частности,
в компьютерах) десятичная система не находит такого широкого прак-
практического применения.
В цифровой технике доминирует двоичная система — позици-
позиционная система счисления, основанием которой является число 2, т. е.
система имеет всего две цифры 0 и 1. Связано это в первую очередь с
тем, что каждую цифру нужно представить каким-то уникальным
электрическим сигналом, т. е. для десятичной системы нужно уметь
генерировать десять уникальных сигналов: по одному для каждой
цифры. Кроме генерации различных сигналов для всех десяти цифр
десятичной системы еще нужно уметь различить эти сигналы и оп-
определять цифру по имеющемуся сигналу. Двоичная система более
практична в цифровой технике, т. к. можно обойтись всего двумя
разными сигналами для представления цифр: 0 и 1. Обычно нулю
соответствует уровень напряжения около 0 В, а единице—около +5 В.
Определить двоичную цифру по сигналу проще, чем десятичную,
поскольку выбор делается всего между двумя возможными сигнала-
сигналами, а это, в свою очередь, повышает надежность системы, ее устой-
устойчивость к помехам. Решение в пользу цифры 0 принимается, если
напряжение меньше 2,5 В, в противном же случае, если напряжение
больше 2,5 В, решение принимается в пользу цифры 1, т. е. суще-
существует простое правило определения цифры по сигналу и при этом
диапазон изменения напряжения для каждой из двух цифр доста-
достаточно большой. Помеха величиной в 0,5 В не нарушит правильность
Современное начертание десятичных цифр установилось в 16-м веке.
8
определения цифры по сигналу. Если бы аналогичным образом диа-
диапазон напряжений от 0 до +5 В поделился бы на десять равных учас-
участков для представления десяти цифр десятичной системы, то генера-
генератор сигналов и детектор цифр были бы большей сложности, а поме-
помехоустойчивость была бы меньшей, чем в случае двоичной системы.
Помеха в 0,5 В вызвала бы ошибку, т. к. 0,5 В — это теперь расстоя-
расстояние между двумя сигналами, соответствующими соседним цифрам,
и вместо одной цифры (например, 4) была бы ошибочно определена
другая, соседняя C или 5).
Помимо сложности электрической части, десятичная система тре-
требует дополнительных затрат памяти при таких операциях как умно-
умножение. Вспомните, как вы учили в начальных классах школы табли-
таблицу умножения. Вспомнили? И это столько мук ради обыкновенного
умножения. А на самом-то деле, для правильного умножения посто-
постоянно иметь столько информации в памяти или подсматривать на
обложку тетрадки3 вовсе не обязательно. Достаточно просто исполь-
использовать систему с меньшим основанием и производить арифметичес-
арифметические действия в ней. Именно это чаще всего делается в технике.
Итак, двоичная система. Двоичная цифра имеет особое назва-
название — бит?. Аналогично вышерассмотренной десятичной системе,
любое число В, записанное п двоичными цифрами bi @ < г < я-1) как
B=K_lbn_T..blb0, C)
соответствует величине
V(B) = ЪпЛ ¦ 2"~* + Ъ„__2 ¦ 2"^ + ... + Ь, • 21 + Ьо ¦ 2°. D)
Например, двоичное число 1101 соответствует величине
Vr=l-2}+l-22+0-2I + l-2°.
Поскольку в различных системах счисления определенные чис-
числа могут иметь различные величины и может быть непонятно, в
какой системе записано число, мы будем стараться указывать ос-
основание системы как индекс рядом с числом во избежание воз-
возможной путаницы. Например, для указания, что число 1101 явля-
3 Раньше, во времена СССР, на обложках тетрадей «в клеточку» печатали таблицу
умножения — шпаргалку для учеников начальных классов. Не знаю, в каких масш-
масштабах эта особенность унаследована сегодняшними производителями тетрадей.
4 От английского термина binary digit (двоичная цифра), сокращенно bit.
ется двоичным, мы будем оснащать его индексом 2: 11012. Вычис-
Вычисляя значение последнего выражения для V, мы находим, что V =
= 8 + 4 + 0+ 1 = 13, т.е.
11012=1310.
Чем больше цифр используется для представления числа в си-
системе счисления, тем больше максимальное число, которое мож-
можно представить этим количеством цифр. Так, для десятичной сис-
системы и десятичных цифр дают возможность представить 10" чи-
чисел от 0 до 10"- 1. Например, для четырех десятичных цифр мак-
максимальное число будет 104 - 1 - 9999. Аналогично п бит дают воз-
возможность представить 2" чисел от 0 до 2"- 1. Например, для четы-
четырех бит максимальное число будет 24- 1 = 15. Обратите внимание
на тот факт, что для представления одного и того же числа в раз-
разных системах требуется разное количество цифр: меньшее в сис-
системе с большим основанием, большее в системе с меньшим осно-
основанием. Число 1510 требует только две десятичные цифры, тогда
как это же самое число, представленное как 11112> требует четыре
бита в двоичной системе, а четырехзначное число 999910 требует
14 бит в своем двоичном представлении 100111000011112. Это ко-
количество нулей и единиц выглядит безумно в своей однообразно-
однообразности, и вряд ли люди будут когда-то в обозримом будущем вести
счет в двоичной системе. Но сегодня именно так устроена память
многих компьютеров и цифровых устройств (каждая ячейка па-
памяти отводится под хранение одного бита), и именно в двоичной
системе счисления ведется машинный счет.
Приведем первые 20 чисел в десятичной и двоичной системах:
Таблица 1
Первые 20 чисел в разных системах счисления
Десятичное
0
1
2
3
4
5
6
7
8
9
Двоичное
0
1
10
11
100
101
110
111
1000
1001
; Десятичное
;- Ю
! 11
12
;: 13
' 14
15
16
17
18
19
Двоичное
1010
1011
1100
1101
1110
1111
10000
iioooi
iiooio
ibofi»
10
Вы уже видели, как можно «перевести» число из двоичной систе-
системы в десятичную на вышерассмотренном примере 11012 = 13]0. Если
вы можете считать по правилам десятичной арифметики, вы просто
умножаете каждый бит на соответствующую ему степень двойки, за-
затем складываете все полученные произведения вместе. Найденная та-
таким образом сумма и есть десятичный эквивалент двоичного числа.
Давайте теперь научимся переводить числа из десятичной систе-
системы в двоичную, опять же используя только десятичную арифметику.
Возьмем для первого примера число 1310 и переведем его в двоичную
систему. Поделим 13 на 2 нацело, найдем частное и остаток от этого
деления: 13 = 6-2 + 1, т. е. частное равно 6, а остаток равен 1. Теперь
возьмем полученное частное 6 и аналогично поделим его на 2:6 =
= 3-2 + 0. Частное равно 3, а остаток — 0. Продолжим деление нового
частного на 2:3 =1-2 + 1. Теперь возьмем частное, равное 1, и поделим
его на 2:1 =0-2 + 1. Дальше делить не будем. Давайте-ка теперь посмот-
посмотрим, что у нас получилось. Частные нас не интересуют, а вот остатки
для нас крайне важны. При последовательных делениях мы получили
остатки: 1,0, 1, 1. Давайте составим из этих остатков двоичное число,
только для этого запишем цифры в обратном порядке: 1, 1, 0, 1 или
11012. Посмотрите, мы получили в точности то же самое двоичное
число 11012> что и было раньше в примере «перевода» 11012в 131О.Табл.
1 также подтверждает это. Теперь можно сформулировать правило,
по которому делается перевод целых беззнаковых десятичных чисел в
квивалентные двоичные.
Для того чтобы преобразовать целое беззнаковое десятичное
число в двоичное, нужно поделить его на 2 и найти остаток от де-
деления. Затем необходимо взять частное от деления и уже его поде-
поделить на 2 с нахождением остатка. Далее нужно опять взять част-
частное от предыдущего деления и его поделить на 2 с остатком. Деле-
Деления продолжаются до тех пор, пока частное от какого-то из деле-
делений не станет равным нулю. Найденные путем деления остатки
берутся в обратном порядке (обратном по отношению к порядку,
в котором они были найдены) для составления двоичного числа.
Таким обазом, остаток, найденный первым, является самым пра-
правым битом двоичного числа, а последний найденный остаток —
самым левым битом.
В двоичном числе самый правый бит, соответствующий наимень-
наименьшей степени двойки, называется младшим битом (МЛБ). Самый ле-
левый бит, соответствующий наибольшей степени двойки, называется
старшим битом (СТБ).
Приведем несколько примеров перевода из двоичной системы в
десятичную и обратно:
11
Таблица 2
Примеры перевода чисел между двоичной и десятичной <
Перевод из двоичной системы
в десятичную
0г = 0-20 = 0,0
102=1-2'+0-2° =
2 + 0 = 2ю
110г= 1 -2г+1 •21 + 0-2° =
4 + 2 + 0 = 6,о
1011г = 1 -2э + 0-22 + 1 -2' + 1 -2° =
8 + 0 + 2 + 1 = 11,»
10010г = 1 ¦ 2" + 0 - 23 + 0 • 2г + 1 • 21 + 0 • 2° =
16 + 0 + 0 + 2 + 0 = 18,0
Перевод из десятичной системы
в двоичную
О,о = 0 • 2 + 0 = 02
Остатки: 0
О,о = 02
2ю = 1 -2 + 0
1=0-2+1
Остатки: 0,1
2,о= Юг
6ю = 3 ¦ 2 + 0
3=1-2+1
1=0-2+1
Остатки: 0,1,1
6,о = 110г
11,о = 5-2+1
5=2-2+1
2=1-2+0
1=0-2 + 1
Остатки: 1, 1,0,1
11ю= 1011г
18,0 = 9-2 + 0
9 = 4-2 + 1
4 = 2-2 + 0
2 = 1 -2 + 0
1=0-2 + 1
Остатки: 0.1, 0,0,1
18,0=100102
При желании можно поупражняться с другими числами, вовсе
не обязательно ограничиваться теми, которые содержит табл. 2. По-
Попробуйте взять числа побольше, например, 10010, 36510> 10101012,
101000012. Проверить правильность перевода можно обратным пе-
переводом, т. е. если вы переводили число Х10 и получили Y2, проверка
12
будет заключаться в обратном переводе Y2 в Х10. Очевидно, что X
должен быть в обоих случаях одним и тем же. Если это не так, ищите
ошибку в одном из двух преобразований.
СЛОЖЕНИЕ ДВОИЧНЫХ ЧИСЕЛ
Сложение двоичных чисел, по сути, ничем не отличается от сло-
сложения десятичных чисел. Давайте вспомним, как это делается. Сло-
Сложим пару десятичных чисел 92 и 435, записав 435 под 92 так, чтобы 5
оказалась под 2:
Рис.1
Теперь будем складывать цифры чисел, начнем с единиц, т. е.
сложим 2 и 5. Получается число 7, которое идет в единицы суммы.
Теперь сложим цифры десятков, 9 и 3, получается 12. Двойка идет
в десятки суммы, а единицу мы запоминаем (вспомните школь-
школьное: «2 пишем, 1 — в уме»), т. к. 1 будет использовано при форми-
формировании сотен суммы. Теперь сложим цифры сотен, т. е. 4 и 0 (в
числе 92 содержится 0 сотен), получается 4. К этому нужно еще
прибавить 1, ту самую единицу, которая «пошла на ум» при сло-
сложении предыдущих цифр.
527
Рис.2
Цифры, которые «запоминаются в уме», можно «запоминать на
бумаге», надписывая их там, где они будут добавляться к цифрам
исходных чисел, вот так:
527
Рис.3
Еще один пример, 999+123:
13
1122
Рис.4
Ну вот мы и вспомнили, как осуществляется десятичное сложе-
сложение. Важно отметить, что это сложение сводилось к последовательно-
последовательному сложению двух десятичных цифр (например, когда мы складыва-
складывали единицы с единицами или сотни с сотнями). К сумме двух цифр
добавляется запомненная единица, если такая есть. Естественно, что
мы должны знать, как складывать две десятичные цифры, иными сло-
словами, мы должны уметь считать до 20 и знать таблицу сложений от
0+0 = 0 и 0+1 — 1 до 9+9 = 18. Да, именно таблицу сложений. Хотя
таковой в явном виде могло и не встретиться в первом классе школы,
фактически вы ее учили, когда складывали одноразрядные десятич-
десятичные числа, как показано в следующем примере.
Возьмем два одноразрядных десятичных числа 7 и 5. Сразу приба-
прибавить 5 к 7 мы не можем, а 1 мы можем прибавить к любой цифре, отсю-
отсюда следующий вариант сложения, при котором одно из слагаемых раз-
разбивается на единицы и последовательно прибавляется к другому:
7+5 = 7+1+1+1+1+1 = 8+1+1 + 1+1 = 9+1 + 1 + 1 = 10+1 + 1 = 11 + 1 = 12.
Теперь давайте рассмотрим другой вариант. Положим, мы уже
знаем, что к 10 добавить можно любое однозначное число X и сумма
будет записываться как IX. Мы знаем также, как можно цифру пред-
представить в виде эквивалентной суммы двух меньших цифр. Тогда одно
из слагаемых, 7 или 5, можно разбить в свою очередь на два слагае-
слагаемых так, чтобы одно из них давало 10 в сумме с неразбитым слагае-
слагаемым:
7+C+2) = G+3)+2 = 10+2 = 12 или B+5)+5 = 2+E+5) = 2+10 = 12.
Это немножко сложнее, чем с единичками, но тоже вполне осуще-
осуществимо. Наверняка, сначала вы считали именно так или почти так, по-
поскольку найти сумму этим или похожим методом достаточно легко. А
в итоге вы и вовсе заучили, что 7+5 = 12, 4+5=9, а 9+3 = 12 и т. д.
В двоичной системе логика та же самая (сложение начинается с
младших разрядов, также учитываются запоминаемые единички),
только цифр всего две: 0 и 1. И таблица сложения содержит не 10 -10
14
элементов, как было в десятичной системе (для суммы каждой циф-
цифры с каждой), а всего 2-2. Вот как складываются двоичные цифры:
Таблица 3
Таблица сложения двоичных цифр
X
0
0
1
1
Y
0
1
0
1
X+Y
0
1
1
10
Последняя строчка фактически может интерпретироваться как
12+12 = 02> и при этом нужно запомнить единицу, которая будет ис-
использоваться при сложении более старших разрядов.
Для уже умеющих считать хотя бы в десятичной системе эта таб-
таблица должна быть очевидна. Если все же не верится, что в двоичной
системе 1+1=10, сложите эти две единицы в десятичной системе, по-
получите 2, а потом переведите 2 в двоичную систему. Вы получите 102.
Можете опять же посмотреть на число, написанное под числом 12 в
табл. 1. Это число должно быть на 1 больше предыдущего, т. е. нуж-
нужная нам сумма 12 и 12.
Давайте теперь складывать двоичные числа. Сложим 1012 и 102:
Рис.5
Тут все очевидно. Для проверки можно произвести сложение эк-
эквивалентных десятичных чисел 5 и 2 EШ = 1012 и 210 = 102), сумма
которых равна 710 или, что то же самое, 1112.
Теперь давайте сложим 1012 и 12:
1
110,
Рис.6
15
При этом у нас один раз единичка «пошла на ум», когда мы склады-
складывали младшие разряды, и добавилась к следующим складываемым раз-
разрядам в точности так же, как это мы видели при сложении десятичных
чисел. Кстати, все эти единички, которые приходится учитывать, назы-
называются переносом, т. е. когда сумма двух разрядов не может быть описа-
описана только одним разрядом и требуется еще единица в более старшем
разряде для правильного описания суммы, то говорят, что возникает
перенос. Можете убедиться в правильности этого примера сложения
путем сложения 510 и 310 — чисел, равных двоичным числам приме-
примера, — и последующего перевода результата в двоичную систему.
Теперь давайте сложим 1012и112и посмотрим, как выполняется
это сложение бит за битом, чтобы ничего не пропустить:
1
101,
11,
о,
11
+ 101,
11,
00,
111
.101,
11,
000,
111
.101,
* 11,
1000,
Рис.7
Здесь сумма младших бит дает сумму 102, из которой 0 идет в
результат (в нулевой, младший бит), а 1 в перенос. Далее, складывая
более старшие биты (те, что левее младших), мы получаем 12, к чему
должны прибавить перенос. Получается 102, и опять 0 идет очеред-
очередным (теперь уже первым) битом в результат, а 1 — снова в перенос.
От сложения вторых битов чисел мы получаем 12, поскольку второй
(третий справа) бит числа 112 равен нулю. И опять к этому нужно
добавить перенос. При этом получится 102, 0 пойдет во второй бит
результата, а 1 — дальше, в перенос. Поскольку этот перенос уже
больше не к чему прибавлять (третьи биты обоих слагаемых равны
нулю), то он и становится старшим, третьим, битом суммы. Таким
образом, мы последовательно от младшего к старшему нашли биты
суммы: 0,0,0,1. Следовательно, искомая сумма равна 10002. Если вы
вдруг запутались в этих битах, цифрах, номерах и числительных,
постарайтесь без спешки еще раз разобрать этот пример. Для про-
проверки: здесь мы складывали 510 и 310. Получили ли мы 810?
Если необходимо, можете поупражняться в двоичном сложении.
Попробуйте сложить еще несколько пар чисел из табл. 1, чтобы закре-
закрепить навык, и не забудьте проверить правильность ваших вычислений.
Мы уже почти подошли к цели, к практическому сложению дво-
двоичных чисел в микропроцессоре (в этой книге все примеры программ
приведены для микроконтроллера семейства х51, хотя изложенная
теоретическая часть и алгоритмы могут быть с успехом использова-
16
ны в программах для других микроконтроллеров, микропроцессо-
микропроцессоров и цифровых устройств вообще).
Из всего вышеизложенного материала по сложению чисел (как де-
десятичных, так и двоичных) должно создаться четкое понимание того,
что сложение чисел — это процесс последовательного сложения пар со-
соответствующих цифр с учетом переноса. Что бы мы ни складывали (раз-
(разряды единиц, разряды десятков, разряды сотен, младшие биты, стар-
старшие биты и т. д.), операция сложения пары разрядов с учетом переноса
остается неизменной независимо от позиции складываемых разрядов.
Можно даже описать такое элементарное сложение следующей ячейкой
структуры, называемой обычно полным одноразрядным сумматором:
РИС.8
Здесь Хи У обозначают i-e разряды слагаемых, S— i-й разряд сум-
суммы, Cin—перенос из предыдущего (i-l)-ro сумматора, Сои*5—перенос
в следующий (i+1 )-й сумматор. Когда переноса не происходит, то соот-
соответствующие значения Cin и Coutравны 0, а когда при сложении возни-
возникает перенос, то соответствующие значения Cin и Cout равны 1. При-
Приставляя такие одноразрядные сумматоры друг к другу, как детские ку-
кубики, можно составить сумматор многоразрядных чисел. Например, если
мы возьмем восемь таких сумматоров и соединим их, как показано на
рис. 9, то мы сможем складывать восьмиразрядные числа:
Саип
Стт
Couti
Cin1
CoutO
I'
Сто
Рис.9
5Cin и Cout — сокращенное англ. Carry in и Carry out, где Carry — перенос.
17
Полный одноразрядный сумматор для двоичной системы функ-
функционирует в соответствии с таблицей истинности, где для каждой воз-
возможной комбинации входов Л, У и Cin указана возможная комбина-
комбинация выходов S и Cout.
Таблица 4
Таблица истинности полного одноразрядного
двоичного сумматора
Cin
0
0
0
0
1
1
1
1
X
0
0
1
1
0
0
1
1
Y
0
1
0
1
0
1
0
1
Cout
0
0
0
1
0
1
1
' 1
s
0
1
1
0
1
0
0
1
Эта таблица является, по сути, обобщением таблицы сложе-
сложения.
Те, кто уже знаком с логическими элементами и микросхемами
И (AND), ИЛИ (OR), HE (NOT) и подобными, а также с булевой ал-
алгеброй, могут из табл. 4 найти выражения для S и Cour.
S = (Cin AND XAND Y) OR (Cin AND (NOT X) AND (NOT Y)) OR
((NOT Cm) AND (NOT X) AND Y)
OR ((NOT Cin) AND XAND (NOT Y)), a
Cout = (XAND Y) OR {Cin AND X) OR (Cin AND Y).
Таким образом, полный однобитный двоичный сумматор со-
состоит из трех обычных инверторов, четырех трехвходовых эле-
элементов И, трех обычных элементов И, четырехвходового элемен-
элемента ИЛИ и трехвходового элемента ИЛИ. Принципиальная схема
полного однобитного двоичного сумматора представлена ниже
на рис. 10.
18
an
-сь
Cout
Рис. 10
Эта схема, хотя и не очень сложная, все же содержит дюжину ло-
логических элементов. А схема для сложения десятичных разрядов была
бы еще сложнее.
Как уже было сказано, из нескольких таких однобитных суммато-
сумматоров можно сделать, например, восьмибитный сумматор, если соеди-
соединить восемь штук вместе. Так сделано и в нашем микроконтроллере6.
' На самом деле может быть не в точности так, поскольку распространение переноса
от сумматора младших битов к сумматору старших ограничивает быстродействие
схемы, и в действительности может быть использована схема с ускоренным распро-
распространением переноса, которую мы здесь не рассматриваем.
19
Микроконтроллер имеет инструкцию восьмибитового сложения
с учетом переноса ADDC, которая выполняет сложение аккумулято-
аккумулятора А с указанным операндом. ADDC берет для Cin^ текущее значение
флага переноса CY (или С) из регистра PSW микроконтроллера. По
окончании выполнения инструкции ADDC в CY записывается Cout?.
В свою очередь Coutj, записанный в CY, может стать Cin0 для следу-
следующего сложения и уже фактически считаться Cin8. Таким образом,
повторяя ADDC, можно складывать не только восьмибитные числа,
но и числа, состоящие из 16-ти, 24-х и т. д. битов. Для этого нужно
проделать ADDC для каждых 8-ми бит слагаемых, не потеряв при
этом значений CY между соседними инструкциями ADDC.
Поскольку часто при сложении бывает нужно иметь СЫц равным
нулю, как например, при нормальном сложении двух 8-битовых чи-
чисел, где начальное значение переноса должно игнорироваться, или
при сложении младших 8-ми битов двух 16-битовых чисел, то мож-
можно просто перед началом сложения установить CY в 0. Делается это
при помощи инструкции CLR С. Но есть и более удобное решение—
инструкция ADD, заменяющая две предыдущие. ADD работает точ-
точно так же, как и AD"DC, только Cin0 берется не из CY, а просто рав-
равным 0.
Приведем программу, которая производит сложение двух 16-бит-
16-битных чисел, расположенных в регистрах микроконтроллера (R3 —
старший байт первого слагаемого, R2 — младший байт первого сла-
слагаемого, R5 — старший байт второго слагаемого, R4 — младший байт
второго слагаемого):
ADD16:
Подпрограмма сложения двух целых 16-ти разрядных чисел
Вход:
R3R2 - 1-е слагаемое
R5R4 - 2-е слагаемое
Выход:
R5R4 - сумма A-е слагаемое + 2-е слагаемое)
Флаги:
CY - признак переполнения при сложении беззнаковых чисел
0V -признак переполнений при сложении чисел со знаком
Используемые регистры:
А
Требует свободных байт в стеке:
2
20
MOV A. R4 ; Ш1Б слагаемого 2
ADD A. R2 ; добавить МЛБ слагаемого 1
MOV R4, A ; записать сумму обоих МЛБ в МЛБ сгагаемого 2
MOV A. R5 ; СТБ слагаемого 2
ADDC A, R3 ; добавить (с учетом переноса) СТБ слагаемого 1
MOV R5, А ; записать сумму обоих СТБ в СТБ слагаемого 2
RET
Рис. 11
Напомним, что если после сложения двух беззнаковых чисел ус-
устанавливается флаг переноса CY, то это значит, что для хранения
правильной суммы требуется еще один дополнительный бит — бит
для этого знака переноса, равного 1, т. е. фактически можно рассмат-
рассматривать эту ситуацию как переполнение при сложении. Действитель-
Действительно, при сложении двух целых и-разрядных чисел, сумма может ока-
оказаться и+1-разрядной.
КОММЕНТАРИИ К КОДАМ ПОДПРОГРАММ
Вот мы и добрались до первой подпрограммы. Их будет еще мно-
много, и больших, и маленьких, и простых, и сложных. Чтобы облег-
облегчить использование этих подпрограмм, каждая из них снабжена
кратким описанием, заключенным в прямоугольнике из точек с за-
запятыми в начале текста подпрограммы, а также комментариями в
теле самой подпрограммы. Краткое описание содержит информа-
информацию о назначении подпрограммы и используемых ресурсах про-
процессора: формат и расположение входных данных в подпрограмму,
формат и расположение выходных данных из подпрограммы, спи-
список регистров, флагов и ячеек памяти, используемых для времен-
временного хранения данных и промежуточных вычислений, глубину сте-
стека. Комментарии в теле подпрограммы облегчают понимание ее
фактической работы.
Основные правила иопользования ресурсов
1. Обычно флаги не сохраняются подпрограммой и чаще всего
используются как дополнительные выходные биты, например, для
индикации ошибочного результата. Таким образом, не стоит пола-
полагаться на то, что какой-то из флагов сохранится. Бит 5 регистра PSW
обычно не используется, а когда он все же модифицируется подпрог-
подпрограммой, это документировано в кратком описании.
21
2. Регистры, перечисленные как используемые, не сохраняются,
т. е. не стоит полагаться на то, что они не изменятся или будут равны
каким-то определенным значениям.
3. Указанный размер стека уже включает в себя хранение адреса
возврата из подпрограммы и размер, требуемый для вызова из под-
подпрограммы других подпрограмм, т. е. указанный размер стека—это
требуемый минимум для нормальной работы всей подпрограммы.
Если вы используете прерывания, убедитесь, что у вас достаточно
места в стеке для подпрограммы и для обработчика прерываний.
Файл с макроопределениями, используемыми в приведенных да-
далее подпрограммах, представлен на рис. 12.
;; Общеупотребимые константы и макросы ;;
АСС
В
PSW
SP
DPL
DPH
АСС. 7
АСС. 6
АСС. 5
АСС. 4
АСС.З
АСС. 2
АСС.1
АСС. О
В.7
В.6
В.5
В.4
В.З
В.2
В.1
В.О
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EOJ
.EOU
.EQU
.EQU
.EOU
.EOU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
ОЕОН
DFOH
ODCH
081Н
D82H
?83Н
0Е7Н
0Е6Н
СЕ5Н
0Е4Н
ОЕЗН
СЕ2Н
ОЕ1Н
СЕОН
0F7H
0F6H
OF5H
0F4H
0F3H
0F2H
OF1H
OFOH
адрес аккумулятора А
адрес дополнительного аккумулятора В
адрес слова состояния рограммы PSW
адрес регистра указателя стека
адрес младшей половины DFTR
адрес старшей половины DPTR
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
бита 7
бита б
бита 5
бита 4
бита 3
бита 2
бита 1
бита О
бита 7
бита 6
бита 5
бита 4
бита 3
бита 2
бита 1
бита О
аккумулятора А
аккумулятора А
аккумулятора А
аккумулятора А
аккумулятора А
аккумулятора А
аккумулятора А
аккумулятора А
аккумулятора В
аккумулятора В
аккумулятора В
аккумулятора В
аккумулятора В
аккумулятора В
аккумулятора В
аккумулятора В
22
PSW.7
PSW.6
PSW.5
PSW. 4
PSW.3
PSW.2
PSW.1
PSW.O
OV
SG
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
.EQU
«DEFINE JO
«DEFINE jo
«DEFINE JNO
«DEFINE jno
«DEFINE JS
«DEFINE js
«DEFINE JNS
«DEFINE jns
0D7H
0D6H
0D5H
0D4H
0D3H
OD2H
OD1H
ODOH
PSW.2
ACC.7
JB OV, ;
JB OV, ;
JNB OV,
JNB OV,
JB SG.
JB SG.
JNB SG,
JNB SG,
адрес
адрес
адрес
адрес
адрес
адрес
адрес
адрес
бита
бита
бита
бита
бита
бита
бита
бита
7 слова
6 слова
5 слова
4 слова
3 слова
2 слова
1 слова
О слова
состояния
состояния
состояния
состояния
состояния
состояния
состояния
состояния
программы PSW
программы PSW
программы PSW
программы PSW
программы PSW
программы PSW
грограммы PSW
программы PSW
флаг переполнения
псевдо-флаг знака результата (стариий бит АСС)
JO label - переход если есть переполнение @V=1)
jo label - переход если есть переполнение @V=1)
; JNO label - переход если нет переполнения @V=0)
; jno label - переход если нет переполнения @V=0)
; JS label - переход если есть знак (SG=1)
; js label - переход если есть знак (SG=1)
; JNS label - переход если нет знака (SG=0)
; jns label - переход если нет знака (SG=0)
Рис. 12
ВЫЧИТАНИЕ ДВОИЧНЫХ ЧИСЕЛ
По аналогии с двоичным сложением, очень похожим на десятич-
десятичное, двоичное вычитание также очень похоже на десятичное. Давайте
вспомним, как делается десятичное вычитание. Вычтем 32 из 415, запи-
записав вычитаемое 32 под уменьшаемым 415 так, чтобы 2 оказалась под 5:
Рис.13
Теперь будем вычитать цифры чисел, начнем с единиц, т. е. выч-
вычтем 2 из 5. Получается 3, которое идет в единицы разности. Затем
вычтем цифры десятков 3 из 1. Но 3 из 1 вычесть нельзя7, а вот если
7 Вычитать из меньшего числа большее число нельзя, т. к. мы работаем в данный
момент с неотрицательными числами.
23
мы «займем» единичку из сотен уменьшаемого, то 3 можно будет
вычесть из 10+1, т. е. из 11, и получится 8, которая пойдет в десятки
разности. Теперь вычтем цифры сотен. Из четверки уменьшаемого
мы вычитаем ноль, поскольку в числе 32 содержится 0 сотен, получа-
получается 4. Но поскольку мы уже «занимали» единичку из разряда сотен,
надо уменьшить полученное на 1. В итоге в разряд сотен разности
идет цифра 3.
Рис. 14
Аналогично сложению, мы можем записывать «занятые» единич-
единички над разрядами, из которых они занимаются и из которых они дол-
должны быть вычтены:
Рис. 15
Еще один пример:
Рис. 16
Аналогично начинаем вычитание с разрядов единиц: 0 и 3. Тройку
вычесть из 0 нельзя. Занимаем 1 из разряда десятков уменьшаемого:
10-3 = 7 — это будет разряд единиц разности. Теперь вычитаем разря-
разряды десятков. Из 0 вычесть 2 нельзя, занимаем 1 из сотен: 10 - 2 = 8. По-
Поскольку мы занимали единицу из десятков, нужно уменьшить это число
еще на 1, получается 7 — разряд десятков разности. Вычтем разряды со-
сотен: 1 из 0. Тут также нужен заем, поэтому занимаем 1 из разряда тысяч:
10 - 1 = 9. Так как на предыдущем шаге мы занимали из сотен, надо 9
уменьшить на 1, получается 8 — это разряд сотен разности. Вычитание
разрядов тысяч нам даст 1, а с учетом заема из разряда тысяч мы получим
0 для разряда тысяч разности. Этот незначащий ноль не пишется.
24
Рис.17
Вспомнили? Надеюсь, да. Теперь давайте обратим наше внима-
внимание на то, что при вычитании все сводится к последовательному вы-
вычитанию разрядов чисел с учетом возможных заемов. Для выполне-
выполнения всего вычитания нужно знать, как вычитать пары цифр. Это
очень похоже на то, что было при сложении, не так ли? И здесь тоже
мы фактически пользовались своеобразной таблицей вычитания
цифр.
В двоичной системе вычитание производится аналогично тому,
как оно производится в десятичной системе, только цифр всего 2 и,
следовательно, таблица вычитания проще:
Таблица 5
Таблица вычитания двоичных цифр
X
0
0
1
1
У
0
1
0
1
Х-У
0
1
1
0
Вторая строчка таблицы может интерпретироваться как вычитание
цифры 12 из цифры 02 с получением цифры 12 и образованием заема,
который должен быть учтен при вычитании более старших бит чисел.
Приведем несколько примеров вычитания беззнаковых двоичных
чисел.
Вычтем 12из 1112:
110,
РИС. 18
Тут все понятно. Можете проверить сложением 1102 и 12, в ре-
результате должно получиться 1112.
25
Теперь вычтем 102 из 1012:
Рис. 19
Тут тоже все просто. При вычитании младших бит получается 1.
Вычитание следующих бит требует заема из старшего бита уменьшае-
уменьшаемого, и следующий бит разности тоже получается 1. При вычитании
старших бит с учетом заема уже получается 0, который как незнача-
незначащий не пишется. Проверьте, действительно ли 1012 - Ю2 + 112.
Последний пример:
1 11 111
_1000г _1000г _10002
12 012 1012
Рис. 20
И здесь тоже никаких проблем. При вычитании младших битов
получается 1 для младшего бита разности и происходит заем из пер-
первого бита уменьшаемого8. Вычитание первых битов требует заема
из второго бита уменьшаемого, а в первый бит разности попадает не
просто 1, а 1 с учетом предыдущего заема, т. е. 0. Вычитание вторых
битов дает 0, но так как из второго бита был заем, то во второй бит
разности в итоге пойдет 1 и будет сделан заем из старшего бита умень-
уменьшаемого. Вычитание старших битов с учетом заема уже даст незна-
незначащий нуль. Проверить рассматриваемый пример можно сложени-
сложением или вычитанием эквивалентных десятичных чисел.
Если потребуется, попрактикуйтесь еще. Возьмите другие числа
для вычитания и не забудьте обязательно проверить правильность
ваших вычислений.
Ну вот, мы освоили двоичное вычитание. Уже совсем скоро мы
будем вычитать на микроконтроллере. А пока давайте рассмотрим
устройство двоичного вычитателя. Да-да, вычитателя. Точно так же,
' Если забыли, самая правая цифра целого числа (она же является самой младшей)
нумеруется как нулевая. Цифра, расположенная левее, имеет порядковый номер 1 и
т. д. до самой старшей цифры числа, самой левой при записи.
26
как и многоразрядный сумматор может быть составлен из несколь-
нескольких соединенных последовательно полных одноразрядных суммато-
сумматоров, многоразрядный вычитатель может быть составлен из несколь-
нескольких последовательно соединенных полных одноразрядных вычита-
телей, схема одного из которых приведена на рис. 21:
X Y
т
D
Вт
Рис.21
Здесь X и У обозначают «°-е разряды вычитаемого и уменьшаемо-
уменьшаемого, соответственно, D— i-й разряд разности, Bin— заем из предыду-
предыдущего (i-l)-ro вычитателя, Bout9 — заем в следующий (й-1)-й вычи-
вычитатель. Когда заема не происходит, то соответствующие значения Bin
и Bout равны 0, а когда при вычитании возникает заем, то соответ-
соответствующие значения Bin и Bout равны 1.
Полный одноразрядный вычитатель для двоичной системы фун-
функционирует в соответствии со следующей таблицей истинности, где
для каждой возможной комбинации входов X, У и Bin указана воз-
возможная комбинация выходов D и Bout.
Таблица 6
Таблица истинности полного одноразрядного
двоичного вычитателя
Bin
0
0
0
0
1
1
1
1
X
0
0
1
1
0
0
1
1
У
0
1
0
1
0
1
0
1
Bout
0
0
1
0
1
0
1
1
о
0
1
1
0
1
0
0
1
9 Bin и Bout — сокращенное англ. Borrow in и Borrow out, где Borrow — заем.
27
Эта таблица является обобщением таблицы вычитания.
Аналогично можно из табл. 6 найти выражение для D и Bout.
D = (Bin AND X AND Y) OR (Bin AND (NOT X) AND (NOT Y))
OR ((NOT Bin) AND (NOT X) AND Y)
OR ((NOT Bin) AND X AND (NOT У)), а
Bout = (X AND (NOT Y)) OR (Bin AND X) OR (Bin AND (NOT Y)).
Следовательно, полный однобитный вычитатель состоит из трех
обычных инверторов, четырех трехвходовых элементов И, трех обыч-
обычных элементов И, четырехвходового элемента ИЛИ и трехвходового
элемента ИЛИ. Следует отметить, что тип и количество элементов в
сумматоре и вычитателе совпадают. Более того, S и D вычисляются
одинаково. Принципиальная схема полного одноразрядного двоич-
двоичного вычитателя представлена ниже на рис. 22.
Bin
Bout
Рте. 22
28
Наш 8-битный микроконтроллер вполне мог бы иметь вычита-
тель, сделанный из 8-ми таких однобитных вычитателей10.
Микроконтроллер имеет инструкцию 8-битового вычитания с уче-
учетом заема SUBB, которая выполняет вычитание из аккумулятора А
указанного операнда. SUBB берет для Bin0 текущее значение флага пе-
переноса CY (или С) из регистра PSW микроконтроллера, т. е. использу-
использует тот же флаг процессора, что и инструкция сложения ADDC. По окон-
окончании выполнения инструкции SUBB в CY записывается Boutj. В свою
очередь Boutj, записанный в CY, может стать Вшодля следующего вы-
вычитания и уже фактически считаться Bin&. Таким образом, повторяя
SUBB, можно вычитать не только 8-битные числа, но и числа, состоя-
состоящие из 16-ти, 24-х и т. д. битов. Для этого нужно проделать SUBB для
каждых 8-ми бит уменьшаемого и вычитаемого, не потеряв при этом
значений CY между соседними инструкциями SUBB.
Инструкции SUB, которая бы работала как SUBB, но брала 0 для Bin^,
в нашем микроконтроллере нет (хотя во многих других есть), поэтому
мы должны по мере необходимости использовать инструкцию CLR С.
Приведем программу, которая производит вычитание двух 16-бит-
16-битных чисел, расположенных в регистрах микроконтроллера (R3—стар-
(R3—старший байт вычитаемого, R2— младший байт вычитаемого, R5—стар-
R5—старший байт уменьшаемого, R4 — младший байт уменьшаемого):
SUB16:
Подпрограмма вычитания двух целых 16-ти разрядных чисел
Вход:
R3R2 - вычитаемое
R5R4 - уменьшаемое
Выход:
R5R4 - разность (уменьшаемое - вычитаемое)
Флаги:
CY - признак заема (анти-переполнения)при вычитании
беззнаковых чисел
0V - признак переполнения при вычитании чисел со знаком
Используемые регистры:
А
Требует свободных байт в стеке:
2
10 Однако лишь мог бы, но, скорее всего, не имеет такого вычитателя, т. к. вычитание
Можно свести к сложению и использовать уже имеющийся сумматор. Об этом будет
Подробно сказано в свое время, когда мы познакомимся с числами со знаком.
29
CLR
MOV A, R4
SUBB A, R2
MOV R4, A
MOV A, R5
SUBB A, R3
MOV R5. A
очистить флаг переноса для первого SUBB
МЛБ уменьшаемого
вычесть МЛБ вычитаемого
записать разность обоих ИЛБ в МЛБ уменьшаемого
СТБ уменьшаемого
вычесть (с учетом заема) СТБ вычитаемого
записать разность обоих СТБ в СТБ уменьшаемого
RET
Рис. 23
Обратите внимание на то, что если после вычитания двух беззна-
беззнаковых чисел устанавливается флаг CY, то это значит, что уменьшае-
уменьшаемое было меньше вычитаемого, и остается нереализованный заем из
старшего бита. Разность при этом должна получиться отрицатель-
отрицательной, но об этом позже. Пока при получении заема (CY =1) разность
можно интерпретировать как B10)я + уменьшаемое - вычитаемое,
где п — количество бит в уменьшаемом, вычитаемом и разности. На-
Например, если бы вы вычитали 000000Ю2 из 000000012 и получили
111111112 (здесь п - 810) и CY = 1, то это было бы то же самое, как
если бы вы вычитали 000000102 из 1000000012 и получили 111111112,
но уже с CY = 0.
Кстати, если в процессоре, с которым вы работаете, нет инструкций
сравнения чисел или есть, но они не дают вам возможности сравнивать
многобайтовые числа, то для сравнения можно использовать вычита-
вычитание. Если после вычитания беззнаковых чисел образуется заем, то умень-
уменьшаемое меньше вычитаемого. Если заема не образуется, то уменьшае-
уменьшаемое больше или равно вычитаемого. Равенство имеет место, когда не
образуется заема и все байты разности оказываются равными нулю.
УМНОЖЕНИЕ ДВОИЧНЫХ ЧИСЕЛ
Вы, наверняка, уже не удивитесь, если я скажу, что двоичное ум-
умножение тоже очень похоже на десятичное. Действительно, обе сис-
системы позиционные, и единственная разница заключается в том, что
они имеют различные основания.
Величины чисел в десятичной системе могут быть вычислены по
цифрам di чисел:
V(D) = <*„_! • 10"-1 + с?я_2 • 10"-2 +... + dl-\O1 + do- 10°. E)
30
Аналогично величины чисел в двоичной системе могут быть вы-
вычислены по цифрам Ь, чисел:
V{® = Ь„_, • 2-» + Ь„_2 • 2"-* +... + Ь, ¦ 21 + Ьо ¦ 2°. F)
Давайте умножим два и-разрядных десятичных числа X и У, за-
записав значение У через цифры числа У:
X-Y=X- {уп_х ¦ Ю-1 + ynJi ¦ Ю-* + ... + у, - 101 + у0 • 10°) =
= X • у„_, • 10"-' + X- у„_2 • 10"-2 +... + Х- у, ¦ 10» + X¦ Л ¦ 10°. G)
А теперь давайте проанализируем это выражение. Очевидно, что
Xумножается на каждую цифру У, т. е. на^и на соответствующую /-ю
степень десяти. Последнее слагаемое в сумме—X, умноженное на млад-
младшую цифру У. Предпоследнее слагаемое — X, умноженное на более
старшую цифру У и еще умноженное на 10. Слагаемое предшествую-
предшествующее этому — это X, умноженное на еще более старшую цифру У и на
100 и т. д. Вам это ничего не напоминает? Ну, как же, это ведь умноже-
умножение «в столбик»! Эти все умножения на 1,10,100 и т. д. — ничто иное,
как просто сдвиг числа влево, вначале на 0 позиций, потом на 1, на 2 и
т.д.
Давайте возьмем простенький пример:
1
782
Рис.24
Ну, теперь-то точно должны были увидеть. Здесь все очевид-
очевидно: 23 умножается на каждую цифру числа 34, получаются час-
частичные произведения 92 и 69. Далее к 92 прибавляется число 69,
сдвинутое на 1 позицию влево, иными словами, умноженное на
10. Вот и все.
Таким образом, для реализации умножения многоразрядных чи-
чисел нам нужно уметь умножать друг на друга две цифры и склады-
складывать их со сдвигом.
Следует заметить, что умножая 23 на 4 и на 3, мы должны были
знать, как вообще производится умножение цифры с на некото-
31
рое число А. Делается это просто. Цифра с умножается на каждую
цифру а{ числа А и на соответствующий вес этой цифры в числе,
т. е. на 10'. Затем все полученные произведения складываются, и
получается произведение цифры с на число А. Самое главное
здесь — то, что мы должны уметь умножать цифру на цифру, т. е.
фактически знать таблицу умножения цифр — произведение каж-
каждой десятичной цифры на каждую десятичную цифру. Всего
10-10 = 100 произведений. Эту таблицу умножения каждый из нас
учил в школе. В то время мы еще не знали, что ей есть альтернати-
альтернатива, и мучились, заучивая ее. Но, спешу вас обрадовать: хоть и меня,
и вас заставили-таки выучить эту огромную таблицу, все же это
было не совсем зря. В итоге мы научились умножать числа в деся-
десятичной системе, и к тому же это поможет нам сейчас понять, как
делается умножение чисел в других позиционных системах счис-
счисления, поскольку во всех системах умножение выполняется по об-
общим правилам.
Однако прежде, чем мы сделаем очередной шаг, мы должны кое-
что вспомнить о шестнадцатиричной системе счисления. По край-
крайней мере, я надеюсь, что среди тех, кто читает настоящие строки, нет
никого, кто еще ни разу ничего не слышал о шестнадцатиричных
числах. Если я ошибся, и такие все же найдутся, рекомендую им об-
обратиться к первой главе первого тома, там эти числа описаны пре-
предельно просто, чтобы и новичок понял, что это такое и с чем его едят.
А для тех, кто уже что-то слышал об этой системе счисления, я вкратце
напомню некоторые основные ее свойства.
Ячейка памяти типичного 8-разрядного микроконтроллера все-
всегда содержит какое-либо двоичное число (в данный момент безраз-
безразлично какое, пусть, к примеру, это будет 10011П02). Такая длинная
цепь нулей и единиц сложна для запоминания и неудобна для ввода
с клавиатуры. Число 100111102 могло бы быть преобразовано в де-
десятичное, что дало бы 158]0. Запомнить такое число легче, но про-
процесс преобразований занимает определенное время, хотя бы то, ко-
которое нужно, чтобы включить компьютер и запустить Windows-каль-
Windows-калькулятор. Поэтому большая часть систем микроинформатики для ви-
визуализации вводимой/выводимой информации, а также для ручно-
ручного ввода использует шестнадцатеричную форму записи, которая по-
позволяет упростить запоминание и использование таких длинных дво-
двоичных чисел, как, например, 100111102.
Шестнадцвтеричная система счисления (hexadecimal) или систе-
система с основанием 16, использует 16 символов: цифры от 0 до 9 и А, В,
С, D, E, F. В табл. 7 приведены эквиваленты десятичных и шестнад-
цатеричных чисел.
32
Таблица 7
Десятичные числа и их шестнадцатеричные эквиваленты
Десятичное
0
1
2
3
4
5
6
7
8
9
Шестнадцатиричное
0
1
2
3
4
5
6
7
8
9
Десятичное
10
11
12
13
14
15
16
17
18
19
Шестнадцатиричное
А
В
С
D
Е
F
10
11
12
13
Заметим, что каждый шестнадцатеричный символ может быть
представлен единственным сочетанием четырех бит. Например,
016 = 0000
16
2,
- 00012, 216 = 00102, 316 = 00112, 416 = 01002> 516 = 01012,
16 6 6 2
616 = 01102, 716 = 01112, 816 = 10002, 916 = 10012, А]6 = 10102> В16 = 10112,
CI6=11002,D16=11012,EI6=1110j,F16=llll2.
Таким образом, представлением двоичного числа 100111102 в
шестнадцатеричном коде является число 9Е16. Это значит, что стар-
старшие четыре разряда 1001 нашего двоичного числа равны 916, а млад-
младшие четыре разряда 1110 равны Е16. Следовательно, 100111Ю2 = 9Е16
(не следует забывать, что индексы означают основание системы счис-
счисления).
Так повелось, что в практике программирования шестнадцате-
шестнадцатеричные числа выделяются не подстрочным индексом, а буквой Н в
конце числа. Иными словами, программисты обычно пишут 9ЕН, а
не 9Е16, хотя и то, и другое означает одно и то же. Еще одно прави-
правило — если старший разряд шестнадцатеричного числа равен А, В, С,
D, Е или F, то при записи перед ними ставят 0. Например, правильно
записать не ВН, а ОВН, не C83DH, a 0C83DH и т. д. Это делается для
того, чтобы программы-ассемблеры различали числа от имен меток
или констант. Имя всегда должно начинаться с буквы, а число — с
цифры.
Как преобразовать двоичное число 1П0102 в шестнадцатерич-
ное? Надо начать с младшего бита и разделить двоичное число на
группы из четырех бит. Затем надо заменить каждую группу из че-
четырех бит эквивалентной шестнадцатиричной цифрой: 10102 = А]6,
00112 = 316, следовательно, 1110102 = ЗА16.
Как преобразовать число 7F16 в двоичное? В этом случае каждая
шестнадцатиричная цифра должна быть заменена своим двоичным
эквивалентом из четырех бит. В примере двоичное число 01112 за-
33
меняет шестнадцатиричную цифру 7]6, а 11112 заменяет F16, откуда
111
7F16 = 2
Шестнадцатиричная запись широко используется для представ-
представления двоичных чисел, поэтому содержимое табл. 7 также необходи-
необходимо запомнить.
Теперь, после того, как мы вспомнили о шестнадцатиричной
системе счисления, вернемся чуть назад, к примеру на рис. 24. В
нем мы освежили в памяти алгоритм, позволяющий умножить
друг на друга два двухразрядных десятичных числа. Вначале мы,
опираясь на выученную еще в школьные годы таблицу умноже-
умножения, умножили младшую цифру множителя на младшую цифру
множимого: 4-3 = 12. Результат оказался двухразрядным. Млад-
Младшую цифру, двойку, мы записали младшим (нулевым) разрядом
первого частичного произведения, а старшую, единицу, запомни-
запомнили. Далее умножили младшую цифру множителя на старшую циф-
цифру множимого: 4-2 = 8. К полученному результату мы прибавили
запомненную единицу, старшую цифру предыдущего умножения
(8+1=9), и записали эту девятку старшим разрядом частичного
произведения. Отмечу, что умножение младшей цифры множи-
множителя на старшую цифру множимого и последующее сложение с
запомненной единицей в рассматриваемом примере дало однораз-
одноразрядный результат, и полученная цифра стала первым разрядом
частичного произведения. Если бы результат упомянутых дей-
действий был двузначным, то очевидно, что младшая его цифра по-
прежнему стала бы первым разрядом этого частичного произве-
произведения, а старшая — вторым. Таким образом, в общем случае ум-
умножение цифры на двузначное число дает трехзначное частичное
произведение (хотя в нашем примере это произведение умести-
уместилось в двух разрядах).
Далее совершенно аналогично мы умножили старшую цифру
множителя, тройку, на множимое и получили второе частичное про-
произведение. После этого, сдвинув второе относительно первого на 1
разряд влево, мы сложили эти два частичных произведения, полу-
получив окончательный результат.
Все сказанное, надеюсь, очевидно для любого из тех, кто читает
настоящие строки (если нет — еще раз внимательно разберите при-
пример на рис. 24 и два последних абзаца). Поэтому двинемся дальше.
Как не раз уже говорилось, правила выполнения арифметических опе-
операций едины для всех позиционных систем счисления независимо от
их основания. Поэтому для вас не должно быть большим откровени-
откровением то, что умножение шестнадцатиричных чисел, о которых мы вспом-
вспомнили несколькими абзацами ранее, выполняется совершенно анало-
34
гично тому, как оно было выполнено в примере на рис. 24. Например,
если одно из чисел равно 3FH, а другое — 0А5Н, то умножение долж-
должно осуществляться следующим образом. Сначала умножаем младшую
цифру множителя на младшую цифру множимого, получая двухраз-
двухразрядное шестнадцатиричное число 4ВН: 5Н ¦ 0FH = 4ВН. Проверьте!
Цифру В оставляем младшим (нулевым) разрядом первого частично-
частичного произведения, а старшую, четверку, запоминаем. Далее умножаем
младшую цифру множителя на старшую цифру множимого, получа-
получаем шестнадцатиричное число OFH EH-3H=OFH). Прибавив к 0FH за-
запомненную четверку, получим 13Н и возьмем младшую и старшую
цифры этого числа как первый и второй разряды полученного час-
частичного произведения, иными словами 3FH • 5Н = 13ВН.
Далее совершенно аналогично умножим старшую цифру мно-
множителя на множимое, получив при этом 276Н: ОАН - 3FH = 276Н.
Сдвинув это второе частичное произведение на один разряд влево,
т. е. умножив на ЮН, и сложив с первым A3ВН), получим оконча-
окончательный результат: 2760Н+13ВН=289ВН. Вот и все, ровно то же, что
и в примере на рис. 24 с одним лишь отличием — мы пользовались
не десятичной таблицей умножения (от 1 ¦ 1 = 1 до 9-9 = 81), а шест-
шестнадцатиричной (от1Н-1Н = 1Ндо 0FH ¦ OFH = 0E1H; приводить ее
здесь я не буду ввиду того, что она вряд ли понадобится кому-нибудь
из вас в дальнейшем).
Пошли дальше. Давайте подумаем, как бы мы, опираясь на вы-
вышесказанное, осуществили умножение друг на друга двух двух-
двухбайтовых чисел. По аналогии с рассмотренными примерами ум-
умножения двух двухразрядных десятичных и шестнадцатиричных
чисел логично было бы поступить следующим образом. Вначале
умножим младший байт множителя на младший байт множимо-
множимого, получив при этом в общем случае двухбайтовый результат.
Младший байт результата этого умножения мы должны оставить
в качестве младшего (нулевого) байта первого частичного произ-
произведения, а старший запомнить. Далее нужно умножить младший
байт множителя на старший байт множимого и к полученному
двухбайтовому результату прибавить запомненный старший байт
результата предыдущего умножения. Приписав к этой сумме спра-
справа вышеупомянутый младший (нулевой) байт, мы получим трех-
трехбайтовое первое частичное произведение. Далее совершенно ана-
аналогично мы должны умножить старший байт множителя на мно-
множимое и получить второе трехбайтовое частичное произведение.
После этого, сдвинув второе относительно первого на 1 байт вле-
влево, мы сложим эти два частичных произведения, получив оконча-
окончательный результат.
35
Как видите, алгоритм прост. Есть только одно маленькое "но" —
нам для его реализации нужно иметь в распоряжении "байтовую"
таблицу умножения (от 1Н • 1Н = 1Н до 0FFH - 0FFH = 0FE01H). Где
ее взять? Для рассматриваемых нами микроконтроллеров это не воп-
вопрос. Микроконтроллеры семейства х51 имеют инструкцию MUL АВ.
Эта инструкция умножает содержимое 8-битового регистра А на со-
содержимое 8-битового регистра В. Старшая часть произведения запи-
записывается в регистр В, а младшая — в регистр А. Таким образом, наш
микроконтроллер умеет аппаратно осуществлять умножение 8 ¦ 8 =
= 16 бит. Рассматриваемая инструкция — не что иное, как искомая
нами "байтовая" таблица умножения, встроенная в систему команд
МК, которой мы можем пользоваться при написании подпрограмм
многобайтового умножения.
Итак, давайте теперь более строго сформулируем алгоритм, по-
позволяющий умножать друг на друга два двухбайтовых числа, т. е. осу-
осуществить умножение формата 16 • 16 = 32 бит с использованием инст-
инструкции, реализующей умножение формата 8-8 = 16 бит. Пусть у нас
есть два 1б-битных числа X и Ус соответствующими старшими и млад-
младшими байтами: Х= ХН- 28 + XL и Y- YH- 28 + YL. Умножим их:
Х- Y=(XH-2* + XL)-(YH-2e + YL)=XH- УЯ-216 +
+ {ХН- YL + XLYH)-2* + XLYL. (8)
Как видите, вычисление произведения Х- У сводится к вычисле-
вычислению четырех произведений (младших и старших байтов обоих чи-
чисел друг на друга) и последующему суммированию со сдвигами этих
произведений (умножения на 28 и 216 эквивалентны тривиальным
сдвигам влево на 8 и 16 бит).
Кстати, если вы еще раз посмотрите на пример, приведенный на
рис. 24, вы заметите, что там умножение реализовано точно также:
23 ш = 2-10 + 3, 3410 = 3-10 + 4 (я специально выбрал пример, в кото-
котором умножаются друг на друга два именно двухразрядных числа).
Умножим их:
23 - 34 = B • 10 + 3) • C • 10 + 4) = 2 • 3 • 102 + B • 4 + 3 • 3) • 10 +
+ 3-4 = 600 + 170+ 12 = 782.
Сравните с выражением (8), не правда ли, один и тот же алгоритм?
Так что, как видите, умножение в различных системах счисления дей-
действительно может быть выполнено по одним и тем же правилам.
36
Теперь давайте вернемся к тому, что используя инструкцию MUL
АВ, которая аппаратно умножает содержимое 8-битового регистра А
на содержимое 8-битового регистра В, микроконтроллер семейства
х51 может осуществить умножение формата 16 • 16 = 32 бит. Подоб-
Подобное умножение выполняется при помощи подпрограммы, приведен-
приведенной ниже:
MUL16M:
Подпрограмма умножения двух целых беззнаковых 16-ти разрядных
чисел
Вход:
R3R2 - множимое
R5R4 - множитель
Выход:
R7R6R5R4 - 32-х разрядное произведение (множимое*множитель)
Флаги:
CY - признак переполнения 16-ти разрядного произведения
Используемые регистры:
А. В, R2-R7
Требует свободных байт в стеке:
2+2=4
M0V
M0V
PUSH
MUL
MOV
MOV
MOV
MOV
PUSH
MUL
MOV
MOV
MOV
POP
MUL
ADD
A.
B.
В
АВ
R7,
R6,
A,
В,
В
АВ
R5.
R4,
А.
В
АВ
А,
R3
R5
В
А ; R7R6 = СТБ ММ * СТБ МН
R2
R4
В
А ; R5R4 = МЛБ ММ • МЛБ МН
R3
R5
37
MOV
MOV
ADDC
MOV
MOV
ADDC
MOV
MOV
POP
MUL
ADD
MOV
MOV
ADDC
MOV
MOV
ADDC
MOV
R5,
A.
A.
R6,
A.
A,
R7,
A.
В
AB
A.
R5.
A.
A.
R6.
A.
A.
R7,
A
R6
В
A
R7
#0
A ; R7R6R5 увеличили на МЛБ МН • СТБ ММ
R2
R5
А
R6
е
А
R7
#0
А ; R7R6R5 увеличили на МЛБ ММ * СТБ МН
ORL A, R6
JZ MUL16M_1 ; CY=O, если результат поместился в 16 бит
SETB
MUL16M_1:
RET
; CY=1, если результат не поместился в 16 бит
Рис. 25
Рассмотренная подпрограмма содержит множимое в регист-
регистрах R3 (старший байт) и R2 (младший байт), а множитель — в
регистрах R5 (старший байт) и R4 (младший байт). Здесь 32-бит-
32-битное произведение записывается в регистры R7 (старший байт), R6,
R5 и R4 (младший байт). Флаг CY устанавливается в 1, если про-
произведение занимает больше 16 бит, т. е. R7 или R6 или оба не рав-
равны 0, в противном случае CY устанавливается в 0. Эта необяза-
необязательная проверка величины произведения делается для того, что-
чтобы можно было иметь все числа (или большинство) в программе
16-битными, но при этом переполнения не оставались незамечен-
незамеченными.
38
Надо сказать, что подобные алгоритмы очень широко применяют-
применяются, если в используемом микроконтроллере есть инструкция, аналогич-
аналогичная MUL АВ, или, что еще лучше, умножающая два двухбайтных аргу-
аргумента с получением четырехбайтного произведения формата 16 • 16 =
= 32 бит. Как нетрудно догадаться, последняя не только снимает вопрос
об умножении друг на друга двух двухбайтовых чисел, но и позволяет
реализовать быстрое умножение формата 32 • 32 = 64 бит. Например, у
нас есть два 32-битных числа Хи Ус соответствующими старшими и
младшими двухбайтовыми словами: X=XWH- 216 + XWL, и У= YWH ¦
216 + YWL. Здесь XWH, XWL, YWHw. YWL — 16-битные (двухбайто-
(двухбайтовые) числа, т. е. те, с которыми оперирует упомянутая выше инструк-
инструкция умножения двухбайтовых чисел. Умножим X на У:
Х- У = {XWH • 216 + XWL) ¦ {YWH • 216 + YWL) =
= XWH- YWH- 232 + (XWH- YWL+XWL ¦ YWH) • 216 +
+ XWL YWL. (9)
Как видите, и здесь вычисление произведения X • Y сводится к
вычислению все тех же четырех произведений младших и старших
двухбайтовых чисел друг на друга и последующему суммированию
со сдвигами этих произведений (умножения на 216 и 232 эквивалент-
эквивалентны тривиальным сдвигам влево на 16 и 32 бита).
А как быть, если микроконтроллер вообще не имеет встроенных
инструкций умножения, аналогичных MUL AB? Например, их не име-
имели некогда безумно популярные микропроцессоры 8080/8085 (оте-
(отечественные аналоги — КР580ВМ80 и КР1821ВМ85). Как быть в этом
случае? Да очень просто. Нужно программно реализовать чисто дво-
двоичное умножение, которое будет содержать лишь операции сложе-
сложений и сдвигов и не потребует каких-либо средств для аппаратного
умножения. Давайте посмотрим, как это можно выполнить.
Рассмотрим для начала простенький пример, осуществляющий
умножение двух трехразрядных десятичных чисел:
Рис. 26
39
Здесь все очевидно: 111 умножается на каждую цифру 234, полу-
получаются частичные произведения 444, 333 и 222. Далее к 444 прибав-
прибавляется 333, сдвинутое на одну позицию влево (иными словами, ум-
умноженное на 10), после чего к этому прибавляется 222, сдвинутое на
две позиции влево (т. е. умноженное на 100).
В общем, примерно то же, что и на рис. 24, только больше час-
частичных произведений и их суммирований. Очевидно, что если бы
мы умножали друг на друга не трехразрядные, а 12-разрядные деся-
десятичные числа, принцип формирования произведения был бы тем же,
только объем вычислений был бы намного больше. Так вот, абсо-
абсолютно тот же принцип лежит в основе алгоритма умножения двоич-
двоичных чисел, не использующих инструкцию MUL АВ. Давайте убе-
убедимся в этом.
Вначале замечу, что поскольку нам придется осуществлять ум-
умножение друг на друга двоичных цифр, нам следует ознакомиться
с двоичной таблицей умножения. Должен вам сказать, она несо-
несоизмеримо проще своего десятичного эквивалента, занимавшего
всю последнюю страницу обложки нашей школьной тетрадки "в
клеточку":
Таблица 8
Таблица умножения двоичных цифр
X
0
0
1
1
V
0
1
0
1
XV
0
0
0
1
Очевидно, что любая цифра (или число), умноженная на 0, даст
нулевое произведение, и в то же время умножение цифры (или чис-
числа) на 1 не изменяет исходной цифры (числа). Пожалуй, стоит ак-
акцентировать на этом внимание, поскольку именно тот факт, что в
двоичной системе всего две цифры 0 и 1, упрощает умножение. Нам
не нужно ничего умножать на цифры 2, 3, 4 и т. д. Все, на что мы
умножаем — это цифры 0 и 1- Да, и не забудьте, что сложения при
двоичном умножении тоже двоичные.
Давайте теперь с учетом этой таблицы умножим 10102 на 1012:
40
х1010>
101,
+ 1010,
. 00003
1010г
110010г
Рис. 27
Видите? Если разряд множителя 1012 равен 0, то прибавляется О
(или ничего не прибавляется), а если разряд множителя равен 1, то
прибавляется множимое 10102, сдвинутое на соответствующее коли-
количество позиций влево11. Просто? Конечно, просто. Можете прове-
проверить, что 10102 • 1012 = П00102, произведя умножение эквивалент-
эквивалентных десятичных чисел 10ю и 51П. Равны ли 501П и 110010,?
10"
Приведем еще несколько примеров.
Умножим 1012на 1Ю2:
'юJ
Рис. 28
Умножим 1112 на 1112:
Рис. 29
Если в таких примерах, как этот, у вас не получается правиль-
правильного произведения (здесь умножается 710 на 710, должно получиться
1' Когда мы сдвигаем двоичное число на разряд влево или приписываем к нему справа 0,
это эквивалентно умножению числа на основание двоичной системы, т. е. на 10, = 2,0.
41
4910), возможны две ошибки: просто неверно выполненное сложе-
сложение или одновременное сложение «в столбик» трех и более чисел.
Старайтесь не складывать «в столбик» более двух чисел одновре-
одновременно. Почему? Хотя бы потому, что в предыдущих разделах опе-
операция сложения была введена как операция над двумя числами, а
не тремя, и если вы попытаетесь сложить три ненулевых числа в
столбик, то у вас несомненно где-то возникнет проблема с опре-
определением правильной цифры суммы и правильного значения пе-
переноса. Даже несмотря на то, что в приведенных примерах умно-
умножение требует сложения трех и более ненулевых чисел, и при этом
не приводится детального описания сложения, вы не должны скла-
складывать три и более чисел «в столбик», делайте сложение по два
числа и не ошибетесь. Так, в вышерассмотренном примере пра-
правильно вычислять сумму частичных произведений следующим
образом:
1112+ 11102+ 111002 = (П12+ Ш02)+ П1002 =
= 101012+111002=Ц00012.
Подведем некоторый итог. В нашем микроконтроллере есть ин-
инструкция MUL АВ, которая позволяет умножать два целых беззнако-
беззнаковых 8-битных числа. А что делать, если в микропроцессоре вообще
нет инструкции умножения? Ответ прост. Нужно просто умножить
двоичные числа «в столбик», как было показано выше. При этом все,
что понадобится делать—это сдвигать и иногда складывать. Ни боль-
больше, ни меньше.
Следующая подпрограмма производит умножение двух целых
беззнаковых двоичных 16-битных чисел «в столбик». Основным
отличием от рассмотренного алгоритма умножения является то,
что не множимое число сдвигается влево перед сложением с на-
накапливаемой суммой, а накапливаемая сумма сдвигается вправо
перед сложением с множимым. Принципиальной разницы в том,
что относительно чего сдвигается, конечно, нет. Главное, чтобы
это было сделано правильно. Как и в предыдущем случае, подпрог-
подпрограмма ожидает множимое в регистрах R3 (старший байт) и R2
(младший байт), а множитель в регистрах R5 (старший байт) и R4
(младший байт). 32-битное произведение записывается в регист-
регистры R7 (старший байт), R6, R5 и R4 (младший байт). Флаг CY, как и
раньше, устанавливается в 1, если произведение занимает больше
16 бит (т. е. R7 или R6 или оба не равны 0), в противном случае CY
устанавливается в 0.
42
HUL16A:
Подпрограмма умножения двух целых беззнаковых 16-ти разрядных
чисел
Вход:
R3R2 - множимое
R5R4 - мнсхитель
Выход:
R7R6R5R4 - 32-х разрядное произведение (множимое«множитель)
Флаги:
CY - признак переполнения 16-ти разрядного произведения
Используемые регистры:
А, В, R2-R7
Требует свободных байт в стеке:
2
HOV
MOV
M0V
HUL16A_1:
MOV
RRC
JNC
MOV
ADD
HDV
HOV
ADDC
MOV
HUL16A_2:
MOV
RRC
HOV
MOV
RRC
MOV
MOV
RRC
R6, «0
R7, «0
B, «16
A, R4
A
HUL16A_2
A. R6
A. R2
R6, A
A. R7
A. R3
R7. A
A, R7
A
R7. A
A, R6
A
R6, A
A, R5
A
обнулили СТС произведения
16 циклов сложения и сдвига
пропустить сложение, если бит множителя равен О
; добавили множимое к частичному произведению
43
MOV
MOV
RRC
MOV
R5, A
A, R4
A
R4, A
; R7R6R5R4 сдвинуто вправо
DJNZ В. MUL16AJ ; зацикливание
MOV A, R7
ORL A, R6
CLR С
JZ MUL16A_3 ; CY=O, если результат поместился в 16 бит
SETB С ; CY=1, если результат не поместился в 16 бит
MUL16A_3:
RET
Рис. 30
Итак, мы рассмотрели подпрограмму, осуществляющую умно-
умножение целых двоичных чисел путем сложений частичных произве-
произведений и сдвига вправо накапливаемой суммы после каждого цикла
анализа разряда множителя. В то же время, рассматривая примеры
на рис. 27—29, мы не накапливаемую сумму сдвигали вправо перед
сложением с множимым, а множимое число сдвигали влево перед
сложением с накапливаемой суммой. Как было отмечено, принци-
принципиальной разницы в том, что относительно чего сдвигать, конечно,
нет. Однако каждый из вариантов представляет собой самостоятель-
самостоятельную вычислительную схему целочисленного умножения. Таких схем
существует четыре. Рассмотрим их более подробно. Замечу только, >
что для тех, кто только осваивает технику программирования, при-
приводимая отсюда и до конца этого раздела информация при первом
знакомстве может быть пропущена без ущерба для понимания ос-
остального материала.
Как уже отмечалось, любой программный метод выполнения
умножения (за исключением громоздкого табличного метода) осно-
основан на многократном выполнении операций сложения. Очевидно, что
простейший способ умножения целых беззнаковых чисел Х- У = Z
есть суммирование множимого X с накоплением его столько раз,
сколько единиц содержит значение множителя У. Этот способ при
больших значениях У требует значительных затрат времени на вы-
выполнение многократных операций сложения, и поэтому применение
его ограниченно.
44
Сокращение количества операций сложения при реализации умно-
умножения основано на методах совместного анализа цифр множителя. Эти
методы дробят процесс получения произведения на ряд шагов, связан-
связанных с формированием частичных произведений (ЧП) — произведений
множимого на отдельные разряды или группы разрядов множителя —
и их суммированием, т. е. формированием суммы частичных произве-
произведений (СЧП). Ускоренные методы умножения используют анализ
одновременно двух и более цифр множителя для получения ЧП, а обыч-
обычные (неускоренные) методы — анализ поочередно каждой отдельной
цифры множителя. Ускоренные методы требуют специальных схемо-
схемотехнических решений, которые, как правило, не используются в микро-
микроконтроллерах, и поэтому здесь мы их рассматривать не будем.
Методы умножения двоичных беззнаковых чисел основаны на
представлении произведения в виде полинома:
A0)
i=0
^ {0,1} — двоичные цифры множителя; 411,.= Х- у{ — частич-
частичные произведения (ЧП; = X, если yt = 1, и ЧП,-= 0, если у{ = О).
Напомним, что умножение ЧП, * 0 на степень 2' эквивалентно
сдвигу множимого на i разрядов влево. Формирование произведе-
произведения, согласно формуле A0), представляется как последовательность
следующих действий:
1) обнуление СЧП;
2)анализ младшего разряда множителя: если у0 = 1, выполня-
выполняется сложение ЧП0 = X с СЧП и переход к п. 3; если у0 = 0, сразу
переход к п. 3;
3) сдвиг множимого влево;
4) анализ очередного разряда множителя: если ух = 1, выполняет-
выполняется сложение ЧП, ¦ 21 с СЧП и переход к п. 5; если у1 = О, непосред-
непосредственно переход к п. 5;
5) сдвиг множимого влево;
6) анализ очередного у2 разряда множителя и т. д.
После анализа старшего уп1 разряда множителя осуществляется
последнее сложение ЧПП_, • 2 с СЧП (если уп_х — 1), и процесс пре-
прекращается. Результирующая СЧП является искомой. Очевидно, что
неподвижную СЧП и сдвигаемое влево на я-1 разряд л-разрядное
множимое необходимо размещать в 2и-разрядных форматах, при-
причем операция сложения должна обрабатывать 2 «-разрядные данные.
45
Иными словами, 16-битное множимое (я = 16) мы должны размес-
разместить в четырех 8-разрядных регистрах или в четырех ячейках памя-
памяти, что обеспечит нам его хранение в процессе сдвига и сложения с
СЧП. Соответственно, такое же количество регистров или ячеек па-
памяти нужно отвести под СЧП, и к тому же при сложении в этом слу-
случае должно оперировать с четырехбайтными числами.
Форматы множимого и операции сложения можно ограничить п
разрядами, если изменить процедуру обработки, определяемую фор-
формулой A0), следующим образом:
л-1 л-1
i=0 1-0
1=0
где V=X ¦ 2"— множимое, сдвинутое влево на п разрядов.
Преобразуем полученное выражение по схеме Горнера:
Z = (...((O+V-yo)-2-1 + Vyl)-2-l + ... + Vyn_l)-2-1. A2)
Выражения в скобках в формуле A2) представляют собой после-
последовательные значения СЧП;, определяемые рекуррентной формулой:
СЧП, = СЧПМ .2-' +У.ун,СЧП0 =0, ie {1,...,и}, A3)
где V- ум = ЧПМ. Заметим, что умножение СЧП на 2 эквивалентно
ее сдвигу вправо на один разряд.
В соответствии с формулами A2)иA3) процесс формирования
произведения выглядит следующим образом:
1 )обнуление СЧП;
2) анализ младшего разряда множителя: если у0 = 1, сложение
ЧПд=Xс СЧП и переход к п. 3; если у0=0, непосредственно переход к п. 3;
3) сдвиг СЧП вправо;
4) анализ очередного разряда ух множителя и т. д. После анализа
старшего разряда упЛ множителя осуществляются последнее сложе-
сложение ЧПП_, с СЧП (если уи_, = I) и последний сдвиг СЧП вправо, после
чего процесс прекращается.
Таким образом, данный процесс умножения требует для пред-
представления неподвижного множимого V = Х ¦ 2" «-разрядного форма-
формата, поскольку младшие п разрядов множимого равны нулю, и такого
же формата для операции сложения (сложение выполняется со стар-
старшими п разрядами СЧП). Формулы A0) и A2) определяют алгорит-
46
мы умножения с младших разрядов множителя соответственно при
неподвижной и подвижной СЧП.
Аналогичные формулы имеют место при умножении со старших
разрядов множителя:
2' + X ¦ Уя-уУТх + Хуя_гУТх +...+
РЧП — РЧП .9 j-V.v РЧГТ — П »e/i n\ (^ei\
" — ^ v *V • ш ' *• it ™ л т JL • I 161
Формулы A4)—A6) получены из полинома A0) соответственно
по схеме Горнера с перенумерацией индексов. Выражения A4) и A5)
определяют процесс умножения с неподвижным и-разрядным мно-
множимым и подвижной, сдвигаемой влево 2л-разрядной СЧП. Этот
процесс реализуется следующей последовательностью действий:
1) обнуление СЧП;
2) сдвиг СЧП влево;
3) анализ старшего разряда множителя: если уи_, = 1, выполняет-
выполняется сложение ЧП,,^ = X • уп_х с СЧП и переход к п. 4; если уп_х = 0,
прямой переход к п. 4;
4) сдвиг СЧП влево;
5) анализ очередного уп_2 разряда множителя и т. д. После анали-
анализа младшего у0 разряда множителя выполняется последнее сложение
ЧП0 = X ¦ у0 с СЧП (если уо~1), и процесс прекращается.
Выражение A6) определяет процесс умножения с неподвижной
СЧП и подвижным, сдвигаемым вправо 2л-разрядным множимым.
Этот процесс имеет такую последовательность действий:
1) обнуление СЧП;
2) сдвиг множимого вправо;
3) анализ старшего разряда множителя: если у„_х= 1> выполняет-
выполняется сложение ЧПпЧ • 2 с СЧП и переход к п. 4; если уп_х = 0, прямой
переход к п. 4;
4) сдвиг множимого вправо;
5) анализ очередного разряда уп_г множителя и т. д. После анали-
анализа младшего разряда у0 множителя осуществляется последнее сложе-
сложение ЧП„ • 2 с СЧП (если у0 = 1), и процесс прекращается.
Рассмотренным четырем алгоритмам умножения соответствуют
четыре вычислительные схемы, используемые при разработке про-
программ умножения (рис. 31—34):
47
схема умножения 1 (рис. 31) соответствует выражениям A2), A3);
схема умножения 2 (рис. 32) — формуле A0);
схема умножения 3 (рис. 33) — соотношениям A4), A5);
схема умножения 4 (рис. 34) — формуле A6).
Рис. 31
Сдвиг
СЧПBп)
Сдвиг
МН(п)
СУ
> Сдвиг
ММBп)
СЧПBп)
Рис.32
48
Сдвиг
МН(п)
ММ(п)
СОвиг I Т
СЧПBп)
Рис.33
. Сдвиг
UH(n)
Сдвиг
ММBп)
СМПBп)
Рис 34
На схемах приведены следующие обозначения:
МН — множители, ММ — множимые (с указанием в скобках раз-
разрядности данных). Анализ разрядов множителя осуществляется пу-
путем сдвига его разрядов вправо (умножение с младших разрядов в
схемах 1,2) или влево (умножение со старших разрядов в схемах 3,4)
с модификацией признака переноса CY, используемого для управле-
управления процессом накопления СЧП.
49
Последовательность выполнения в вычислительных схемах опе-
операций сдвига и сложения поясняется в табл. 9—12 на примере умно-
умножения двух беззнаковых целых четырехразрядных чисел: ММ D) =
= 11112 = 1510 и МН D) = 01012 = 5,0. В таблицах процесс умножения
раскрыт как последовательность пяти шагов: подготовительного (об-
(обнуления СЧП) и четырех шагов сдвига множителя с анализом при-
признака переноса и выполнения сдвигов ММ, СЧП и операции сложе-
сложения. Тип выполняемой операции условно изображен в последнем
столбце каждой таблицы: «—>'» означает сдвиг вправо, «<—» означает
сдвиг влево,«+» — сложение, «=»— присвоение значения. Справа от
знака операций указаны аббревиатуры соответствующих данных, над
которыми выполняется указанная операция (в операции сложения
вторым слагаемым всегда является СЧП). Результат умножения —
8-разрядное число 0100Ю112 = 75,0
Достоинства и недостатки приведенных вычислительных схем
описаны ниже при рассмотрении конкретных программ умноже-
умножения.
Как нетрудно заметить, подпрограмма М1ЛЛ6А выполнена в со-
соответствии с вычислительной схемой 1 — множимое остается непод-
неподвижным, а множитель и сумма частичных произведений сдвигаются
вправо. Рассмотрим реализацию аналогичной подпрограммы, выпол-
выполненной по схеме 2.
Таблица 9
Умножение по схеме 1
Номер шага
0
1
2
3
4
Сдвиг МН
МИ D)
0 10 1
- 0 1 0
0 1
0
CY
—
1
0
1
0
Операции с СЧП и ММ
CY
—
0
0
0
1
0
0
СЧП(в)
00000000
1111
11110000
01111000
00111100
1111
00101100
10010110
01001011
тип
=счп
+ММ
=счп
->счп
-»счп
+ММ
=счп
-»счп
-»счп
50
Таблица 10
Умножение по схеме 2
Номер шага
0
1
2
3
4
Сдвиг МН
МН<4)
0 10 1
- 0 1 0
0 1
0
CY
—
1
0
1
0
Операции с СЧП и ММ
счпда
00000000
00001111
00001111
00011110
00001111
00111100
00111100
01001011
01111000
01001011
Тип
=счп
+ММ
=счп
->мм
=счп
<-мм
+ММ
=счп
<-мм
=счп
Таблица 11
Умножение по схеме 3
Номер шага
0
1
2
3
4
Сдвиг МН
CY
—
0
1
0
1
МНD)
0 10 1
10 1-
0 1
1
Операции с СЧП и ММ
СЧП(8)
00000000
00000000
00000000
1111
00001111
00011110
00111100
1111
01001011
Тип
=счп
<-счп
«-СЧП
+ММ
=счп
<-счп
«-СЧП
*мм
=счп
51
Таблица 12
Умножение по схеме 4
Номер шага
0
1
2
3
4
MUL16B:
Сдвиг МН
CY
—
0
1
0
1
МНD)
0 10 1
10 1-
0 1
1
Операции с СЧП и ММ
СЧП(в)
00000000
01111000
ооосоооо
00111100
00111100
00111100
00011110
00111100
00001111
00001111
01001011
тип
=СЧП
-»мм
=счп
-мм
+ММ
=счп
-»мм
=счп
-»мм
+ММ
=счп
Подпрограмма умножения двух целых беззнаковых 16-ти разрядных
чисел ,
Вход:
R1R0 - множимое
R3R2 - множитель
Выход:
R7R6R5R4 - 32-х разрядное произведение (множимое«множитель)
Флаги:
CY - признак переполнения 16-ти разрядного произведения
Используемые регистры и ячейки памяти:
А, В, R0-R7, ячейка COUNT
Требует свободных байт в стеке:
2+1
MOV
PUSH
MOV
MOV
MOV
A,
ACC
B,
R2,
R3,
R3
R2
#0
#0
; старший байт множителя храним в стеке
; младший байт множителя храним в регистре В
; обнулили старшие 2 байта множимого
52
MOV
MOV
MOV
MOV
MOV
MUU16B_1:
POP
RRC
PUSH
MOV
RRC
MOV
JNC
MOV
ADD
MOV
MOV
ADDC
MOV
MOV
ADDC
MOV
MOV
ADDC
MOV
MUL16B_2:
MOV
RLC
MOV
MOV
RLC
MOV
MOV
RLC
MOV
MOV
RLC
MOV
R4. йО
R5, ЙО
R6. ЙО
R7. йО
COUNT, I
ACC
A
ACC
А, В
A
Б.А
MUL16B_2
A, R4
A. RO
R4, A
A, R5
A. R1
R5, A
A. R6
A, R2
R6. A
A. R7
A. R3
R7. A
A, RO
A
RO, A
A. R1
A
R1, A
A, R2
A
R2, A
A, R3
A
R3, A
обнулили СТС произведения
6 ; 16 циклов сложения и сдвига
пропустить сложение, если бит множителя равен О
добавили множимое к частичному произведению
R3R2R1R0 сдвинуто влево
53
DJNZ COUNT, MUL16B_1 ; зацикливание
POP
MOV
ORL
CLR
JZ
SETB
MUL16B_3:
RET
ACC
A, R7
A, R6
С
MUL16B_3
С ;
; CY=O, если результат поместился в
CY=1, если результат не поместился
16
в 16
бит
бит
Рис.35
Подпрограмма MUL16B реализует схему умножения, при кото-
которой множимое сдвигается влево при неподвижной сумме частичных
произведений. При этом для хранения множимого нам приходится
использовать сразу четыре регистра — R3, R2, R1 и R0, также четыре
регистра занимает и СЧП — R7, R6, R5 и R4. Младший байт множи-
множителя хранится в регистре В, а старший ввиду отсутствия свободных
регистров сохраняем в стеке. Счетчик циклов приходится разместить
в одной из ячеек оперативной памяти (COUNT), ибо для него также
не нашлось свободного регистра. Нетрудно заметить, что MUL16B
требует большего числа регистров и ячеек памяти, чем MUL16A, при
том что быстродействие ее примерно на 30 % ниже. Поэтому вычис-
вычислительная схема 2 целочисленного умножения обычно используется
редко, только там, где есть определенные аппаратные ресурсы, по-
позволяющие повысить быстродействие аппаратным образом.
По тем же причинам, что и вычислительная схема 2, редко ис-
используется и вычислительная схема 4. Поэтому здесь мы не будем
приводить вариант подпрограммы, реализующей четвертую схему.
Интерес может представлять лишь схема 3, характеризуемая непод-
неподвижным множимым и сдвигаемыми влево СЧП. В отличие от схе-
схемы 1, здесь умножение идет со старших разрядов множителя. Алго-
Алгоритм реализуется подпрограммой MUL16C.
MUL16C:
Подпрограмма умножения двух целых беззнаковых 16-ти разрядных
чисел
Вход:
R3R2 - множимое
54
R5R4 - множитель
Выход:
R5R4R7R6 - 32-х разрядное произведение (множимое»множитель)
Флаги:
CY - признак переполнения 16-ти разрядного произведения
Используемые регистры:
А, В, R2-R7
Требует свободных байт в стеке:
2
MOV R6, ЯО
обнулили СТС произведения
16 циклов сложения и сдвига
MOV
MOV
MUL16CJ:
M0V
ADD
MOV
MOV
ADDC
MOV
MOV
ADDC
MOV
MOV
ADDC
HOV
R7,
B,
A.
A.
R6,
A.
A,
R7.
A,
A,
R4.
A,
A,
R5,
BO
B16
R6
R6
A
R7
R7
A
R4
R4
A
R5
R5
A
; R5R4R7R6 сдвинуто влево
JNC MUL16C_2; если разряд множителя равен О
MOV
ADD
MOV
HOV
ADDC
MOV
JNC
A, R6
A, R2
R6, A
A, R7
A.R3
R7, A
MUL16C 2
сложили множимое с текущей суммой ЧП
MOV A, R4
55
ADDC
NOV
MOV
ADDC
MOV
MUL16C 2:
A,
R4,
A.
A.
R5.
«0
A
R5
BO
A
; учет переноса от сложения
DJNZ В, MUL16C_1 ; зацикливание
MOV A, R5
ORL A. R4
CLR С
JZ MUL16C_3 ; СУ=О, если результат поместился в 16 бит
SETB С ; CY=1, если результат не поместился в 16 бит
MUL16C_3:
RET
Рис.36
Как видите, подпрограмма MUL16C немного длиннее, чем MUL16А,
притом что скорость выполнения ее несколько ниже, чем у первой. Та-
Таким образом, для микроконтроллеров семейства х51 подпрограмма
MUL16A, выполненная в соответствии с вычислительной схемой I, яв-
является оптимальной, и именно ей мы будем пользоваться в дальней-
дальнейшем. Однако замечу, что для другого микроконтроллера или микро-
микропроцессора оптимальной может оказаться подпрограмма, выполненная
по иной вычислительной схеме. Так, например, у уже упоминавшихся
8-разрядных микропроцессоров 8080/8085 в системе команд есть две
группы инструкций, работающих с парами регистров, т. е. с 16-разряд-
16-разрядными операндами. Это команды DAD RD и INX RD, где RD — это
пары регистров, ВС (В и С), DE (D и Е) и HL (H и L). Для тех, кто не
знает 8080, но не боится слегка загрузить голову мусором, который вряд
ли понадобится им в дальнейшем, отмечу, что в этих процессорах реги-
регистры общего назначения имеют названия не R0, R1 и т. д., а А, В, С, D и
т. д. Так вот, вместо длинной последовательности команд х5]
MOV
ADD
MOV
MOV
А,
А,
R4.
А,
R4
»1
А
R5
56
ADDC A. «0
MOV R5, A ; учет переноса от сложения
в 8080/8085 можно воспользоваться короткой однобайтовой коман-
командой INX D, увеличивающей на единицу величину 16-битного числа,
размещенного в 8-разрядных регистрах С и D. Соответственно пос-
последовательность
HOV
ADD
MOV
MOV
ADDC
MOV
A, R6
A, R2
R6, A
A. R7
A,R3
R7, A
; сложили множимое с текущей суммой ЧП
в 8080/8085 заменяется одной короткой однобайтовой командой
DAD В (к содержимому двухбайтовой пары HL прибавляется содер-
содержимое пары ВС, результат записывается в HL), а последовательность
MOV
ADD
MOV
MOV
ADDC
MOV
A. M
A, R4
R4, A
A, R5
A, R5
R5, A
; сдвинули множитель на 1 разряд влево
сдвигающая число в регистрах R5R4 на на 1 разряд влево, т. е. удваи-
удваивающая его, у 8080/8085 выполняется последовательностью трех про-
простых однобайтных команд
XCHG
DAD H
XCHG
В результате программа умножения формата 16 - 16 = 32 бит по
вычислительной схеме 3 выглядит на языке 8080/8085 следующим
образом:
М16С_80:
XRA А
MOV Н,А
MOV L.A
57
ЦИКЛ:
XCHG
DAD
XCHG
RAR
DAD
JNC
INX
RAL
JNC
DAD
JNC
INX
ADI
JNC
RET
H
H
ПЕР1
D
ПЕР2
В
ПЕР2
D
16
ЦИКЛ
ПЕР1:
ПЕР2:
Рис. 37
Как видите, эта программа весьма элегантна, значительно проще
своего аналога на языке микроконтроллеров 51-го семейства. И она —
поверьте на слово те, кто не знаком с 8080 — абсолютно работоспособ-
работоспособна. Благодаря наличию вышеупомянутых инструкций для 8080/8085
оптимальной является эта подпрограмма умножения, выполненная по
вычислительной схеме 3. Поэтому для тех, кто собирается адаптиро-
адаптировать приведенную в настоящей книге информацию под какой-либо
другой класс микроконтроллеров, отличных от х51, я рекомендую рас-
рассматривать все описанные выше вычислительные схемы.
И последнее, о чем обязательно нужно сказать в настоящем раз-
разделе — сравнительное быстродействие рассмотренных выше под-
подпрограмм. Наверное, мало кого удивит, если я скажу, что подпрог-
подпрограмма умножения MUL16M работает быстрее своих аналогов
MUL16A и MUL16C. Ясно, что подпрограмма, использующая аппа-
аппаратное байтовое умножение, должна функционировать быстрее тех,
которые для вычислений используют только сложения и сдвиги. А
насколько быстрее она работает?
На выполнение тестового примера, осуществляющего умноже-
умножение OFFFFH на OFFFFH, подпрограмма MUL16M тратит 61 такт (или
61 мкс при 12-мегагерцовой тактовой частоте стандартного 8хС51).
MUL16A затратила на этот пример 397 тактов (в 6,5 раз больше), а
MUL16C — 487 тактов (в 8 раз больше). Комментарии, как говорит-
говорится, излишни. Если для вас актуально быстродействие ваших про-
58
грамм, вы просто вынуждены работать с подпрограммой, использу-
использующей аппаратный умножитель.
Проверить работоспособность подпрограмм умножения, входя-
входящих в написанные вами программы, полезно на тестовых наборах
данных (табл. 13) или на аналогичных, составленных с использова-
использованием стандартного Windows-калькулятора.
Таблица 13
Тосты умножения формата 16>16=32
(целые двоичные беззнаковые числа)
Представление чисел
Шестнадцатеричное
FFFF - FFFF=FFFE0001
FFFF - 0001=O000FFFF
F00O-000F=0O0E100O
FFO0 00FF=O0FE01O0
5555 • AAAA=38E31C72
7FFF - 7FFF=3FFF0001
Десятичное
65535 ¦ 65535=4294836225
65535 • 1=65535
61440•15=921600
65280 255=16646400
21845 ¦ 43690=954408050
32767 32767=1073676289
ДЕЛЕНИЕ ДВОИЧНЫХ ЧИСЕЛ
Деление как таковое является обратной процедурой по отношению
к умножению. При умножении числа Хна число Умы находили такое
число Z, которое бы включало в себя У раз число X (или наоборот, X раз
число У), т. е. Z6buio бы суммой У слагаемых, каждое из которых равно
X. Это число Z и есть искомое произведение. При делении же мы дела-
делаем обратное — находим, сколько раз У число X содержится в числе Z.
При целочисленном делении ZmX, Z может поделиться на Хне полно-
полностью: часть Z, меньшая, чем X, останется и не сделает вклада в целое
частное У. Фактически мы решаем следующее уравнение:
Z=XY+R, A7)
где Z— делимое, X— делитель, У— искомое частное, R — искомый
остаток (R < X).
Произведение точных целых я-разрядных чисел имеет разрядность
In. Поэтому при делении точных целых чисел примем разрядности
делимого и делителя соответственно 2я и я (и обычно выбирают рав-
равным 8, 16 и т. д.). Как известно, множество целых чисел незамкнуто
относительно операции деления, частное может быть не только точ-
точным целым числом, но и конечной или бесконечной дробью (правиль-
59
ной или неправильной), т. е. числом ограниченной точности. В связи с
этим разрядность частного при делении целых чисел определяется не-
необходимой точностью его вычисления и может превышать разрядность
делимого или делителя. На практике деление целых чисел выполняет-
выполняется как деление с остатком: находятся целое неполное точное частное
(его произведение с делителем дает целое число, не превышающее де-
делимое) и целый остаток, т. е. разность между делимым и произведени-
произведением неполного частного на делитель. Как было уже отмечено, разряд-
разрядность неполного частного не превосходит разрядности делимого, а
разрядность остатка—разрядности делителя (остаток по абсолютной
величине всегда меньше делителя).
Большинство микроконтроллеров, в том числе и рассматривае-
рассматриваемые х51, не содержат полноценных команд деления чисел, поэтому
для выполнения этой операции необходимы программы. Любой про-
программный метод деления сводится к последовательному нахождению
цифр частного, начиная с его старшей цифры, путем вычитания де-
делителя из делимого или остатка и анализа получаемой разности. Дво-
Двоичное деление проще десятичного, поскольку выбор очередной циф-
цифры частного производится всего из двух цифр. О и 1, причем цифра 1
выбирается при неотрицательной разности (делимое по модулю боль-
больше или равно делителю), а цифра 0 — при отрицательной разности
(делимое по модулю меньше делителя). Процедура обычного (неус-
(неускоренного) деления носит последовательный характер: очередная
цифра частного и новый остаток не могут быть вычислены прежде,
чем будет получен и исследован предыдущий остаток.
Деление десятичных и двоичных чисел выполняется по общим
для обеих счислительных систем правилам. Поэтому для начала да-
давайте вспомним, как осуществляется десятичное деление. Для этого
рассмотрим следующий пример. Пусть делимое равно 4436, дели-
делитель равен 34. Имеем:
4436 I 34
Рис.38
Наш делитель двузначный, поэтому в делимом выберем старшие
два разряда и разделим составленное из них число (в нашем случае 44)
на делитель. Иными словами, посмотрим, сколько раз делитель 34 вхо-
входит в состав 44. В данном случае у нас получилась единица, и эту еди-
60
ничку запишем в старший разряд частного. Умножим этот получен-
полученный разряд на 34 и запишем произведение под старшими двумя разря-
разрядами делимого. Затем вычтем первое из последнего. Полученную раз-
разность D4 - 34 = 10) запишем под упомянутым произведением C4), и
припишем к ней справа (иногда говорят "снесем в нее") третий по стар-
старшинству разряд делимого, т. е. тройку. Сформированное таким обра-
образом число 103 (его можно назвать первой промежуточной разностью)
опять разделим на делитель или вновь определим, сколько раз 34 вхо-
входит в 103. Нетрудно посчитать, что три раза, т. е. следующий, второй
разряд частного равен 3. Запишем его справа от единицы, умножим на
34, и полученное произведение A02) запишем под первой разностью.
Вычтем первое из второго A03 - 102 = 1) и к полученной разности (еди-
(единице) припишем справа четвертый по старшинству разряд делимого,
шестерку. Сформированное таким образом число 16 назовем второй
промежуточной разностью. После этого определим, сколько раз дели-
делитель входит в эту вторую промежуточную разность, т. е. попытаемся
разделить эту разность на 34. Однако она, как нетрудно заметить, мень-
меньше делителя, делитель входит в нее 0 раз, и этот 0 — младший разряд
частного. Далее, как видим, сносить вниз нечего, в связи с чем процесс
деления на этом завершается, и эта вторая разность оказывается ничем
иным, как остатком.
А теперь давайте разделим 4436 на 94 и посмотрим, как осуще-
осуществляется деление в этом случае:
Рис. 39
В этом примере мы имеем делитель больше, чем старшая часть
делимого. Поэтому на 94 мы делим не двухразрядное число 44, кото-
которое меньше 94, а число, состоящее из трех старших цифр, т. е. 443.
Обычно при выполнении целочисленного деления "в столбик" посту-
поступают именно так, берут число из такого количества старших цифр,
которое позволяет произвести деление с ненулевым результатом. Но
для этого перед делением мы должны определить, сколько цифр взять,
чтобы число из старших цифр делимого было бы больше делителя. И
если мы, вооруженные опытом начальной школы, в состоянии опре-
определить это "в уме", т. е. без каких-либо фиксируемых на бумаге допол-
дополнительных вычислений, то микроконтроллеру для этого придется со-
61
вершить какие-либо действия. На практике оказывается удобнее зас-
заставить его выбирать для первого деления именно столько старших
разрядов делимого, какова разрядность делителя, и при этом, если упо-
упомянутое число будет меньше делителя, в первом цикле деления мы
попросту получим незначащий нуль, как на рис. 40:
94
047
Рис. 40
Итак, мы повторили школьную процедуру деления "в столбик".
Давайте попробуем аналогичным образом осуществить деление од-
одного двоичного числа на другое, например 10000002 на 11112. Проце-
Процедура деления выглядит следующим образом:
1000000 I 1111
0000 | 0100
10000
' 1111
00010
¦ оооо
00100
¦ оооо
100
Рис. 41
Наш делитель четырехзначный, поэтому в делимом выберем стар-
старшие четыре разряда и посмотрим, сколько раз делитель 11112 входит в
составленное из этих четырех разрядов число 10002. В данном случае
делитель больше числа 10002. Следовательно, старший разряд частного
равен 0, и этот нуль мы запишем в частное. Умножим полученный раз-
разряд на 11112 и запишем состоящее из нулей произведение под старши-
старшими четырьмя разрядами делимого. Затем вычтем первое из последнего.
Припишем к полученной разности справа пятый по старшинству раз-
разряд делимого, т. е. еще один нуль. Сформировав таким образом число
62
100002 (его, как и раньше, назовем первой промежуточной разностью),
опять определим, сколько раз 11112 входит в 100002. Нетрудно посчи-
посчитать, что один раз, т. е. следующий, второй, разряд частного равен 1.
Запишем его справа от уже имеющегося 0. Затем умножим его на 1 111 2
и полученное произведение A1112) запишем под первой промежуточ-
промежуточной разностью. Вычтем первое из второго A00002 -11П2=1)ик полу-
полученной единице припишем справа шестой по старшинству разряд де-
делимого очередной нуль. Сформированное таким образом число 102 (или
000 Ю2) назовем второй промежуточной разностью. Далее определим,
сколько раз делитель входит в эту вторую разность. Однако она, как не-
нетрудно заметить, меньше делителя, в связи счем очередной, третий, раз-
разряд частного опять равен нулю. Умножим этот полученный разряд на
11112 и запишем состоящее из нулей произведение под младшими че-
четырьмя разрядами второй разности. Опять вычтем первое из последне-
последнего. Припишем к полученной разности справа седьмой по старшинству
разряд делимого, т. е. еще один нуль. Сформировав таким образом чис-
число 001002 (его назовем третьей промежуточной разностью), опять опре-
определим, сколько раз 11112 входит в 001002. Ясно, что 0, таким образом
четвертый разряд частного—опять 0. Поскольку все разряды делимого
уже использованы, то процесс деления на этом завершается, и оставша-
оставшаяся разность A002) оказывается ничем иным, как остатком.
Итак, при делении 10000002 на 11112 мы получили 1002 и столько
же в остатке. Чтобы проверить, правильно ли мы выполнили деле-
деление, переведем эти числа в десятичные: 10000002 = 6410, 11112 = 1510,
1002 = 410; 64: 15 = 4 с остатком 4. Как видите, все сошлось, что под-
подтверждает правильность рассмотренной процедуры целочисленно-
целочисленного двоичного деления с остатком.
Еще пара примеров, чтобы вы освоились с тем, как осуществля-
осуществляется деление двоичных целых чисел. Разделим 010101012 на 10Ю2:
01010101 I 1010
~0000 | 01000
1010
- 1010
0001
" 0000
Рис.42
63
Проверим: 010101012=8510, 10102=1010> 85:10=8 и 5 в остатке. Как
видите, все сошлось: 01002=8I0, 1012=5]0.
В следующем примере разделим 010110Ш2 на 11012:
01101010 I 1101
0000 | 01000
1101
• 1101
РИС. 43
И здесь все в порядке: 106:13=8 с остатком 2. Как видите, ничего
сложного, те же самые действия, которым нас когда-то учили в на-
начальной школе на уроках арифметики.
Теперь давайте обратим внимание на следующее обстоятельство.
Для реализации операции деления нам достаточно лишь уметь вы-
вычитать числа друг из друга. Например, когда мы осуществляли де-
деление 4436 : 34, для определения того или иного разряда частного
мы должны были разделить соответствующую первую, вторую и
т. д. промежуточную разность на делитель. Но как было сказано,
при этом мы находили, сколько раз делитель содержится в этой раз-
разности. А найти это можно и без операции деления. Вычтем дели-
делитель из промежуточной разности и посмотрим, осталась ли она
после этого большей или равной 0 или нет. Если она неотрицатель-
неотрицательна, то разряд частного примем равным 1, при отрицательном ре-
результате разряд частного равен 0. Далее, если после первого вычи-
вычитания результат был неотрицательным, еще раз вычтем из него де-
делитель. При отрицательном результате нужно остановиться, а если
результат второго вычитания опять неотрицательный, увеличим ис-
искомый разряд частного еще на 1 (т. е. до 2) и продолжим вычита-
вычитание. Еще раз вычтем делитель и остановимся при отрицательном
результате вычитания. При неотрицательном же увеличим разряд
частного еще на 1 (до 3) и будем продолжать выполнять указанные
действия до тех пор, пока в результате вычитания не получится от-
отрицательное число. Как нетрудно догадаться, эти действия могут
64
быть выполнены 1,2, 3 и т. д. раз, но не более 9. При этом мы полу-
получим в итоге, сколько раз делитель содержится в соответствующей
промежуточной разности, т. е. искомую цифру разряда частного.
Надеюсь, все сказанное понятно, если нет — внимательно прочи-
прочитайте последние три абзаца.
Когда мы выполняли деление 10000002 на 11112, мы точно также
находили, сколько раз делитель содержится в первой, второй и т. д.
промежуточной разности. При этом в силу особенностей двоичной
системы счисления делитель в каждой этой разности может содер-
содержаться не более одного раза. Иными словами, если соответствующая
разность меньше делителя, то он содержится в ней 0 раз, и найден-
найденный разряд частного равен 0. Если соответствующая разность равна
или больше делителя, то делитель содержится в ней только один раз,
и вычитание его из упомянутой промежуточной разности приведет
к тому, что результат этого вычитания окажется меньше делителя.
Поэтому найденный разряд частного в этом случае равен 1, а част-
частное в итоге будет состоять из последовательности нулей и единиц (а
разве кто-то ждал иного?).
Идем далее. Чуть выше мы договорились, что при выполнении
деления мы должны сравнить первую, вторую, третью и т. д. проме-
промежуточную разность с делителем. Если разность меньше делителя, то
соответствующий разряд частного равен 0, к этой разности справа надо
добавить следующий разряд из делимого и провести очередное срав-
сравнение. Если разность не меньше делителя, то устанавливаем текущий
разряд частного равным единице, вычитаем делитель из соответству-
соответствующей разности, добавляем к результату следующий разряд из дели-
делимого и проводим очередное сравнение. И так до того момента, пока
мы не используем все разряды делимого. Вроде все очевидно. Но воз-
возникает вопрос — как осуществлять сравнение чисел? Ведь рассматри-
рассматриваемые микроконтроллеры не имеют команд сравнения, по крайней
мере, сравнения двухбайтных, трехбайтных и т. д. чисел. Как быть?
На самом деле все довольно просто. Для сравнения двух чисел нуж-
нужно попросту вычесть одно из другого (с вычитанием-то мы уже знако-
знакомы!), и если результат вычитания неотрицательный, то первое число
больше второго. Отрицательный результат (установившийся в 1 флаг
переноса CY) свидетельствует о том, что первое число меньше второ-
второго, а нулевой, как нетрудно сообразить, информирует нас о равенстве
сравниваемых чисел. Вот только после такого сравнения первое из чисел
(если перед сравнением не сохранить его где-то) изменится. Следова-
Следовательно, если оно нам понадобится в дальнейшем, то его либо нужно
перед сравнением где-то сохранить, либо восстановить — прибавить
к остатку, полученному в результате сравнения, делитель.
65
—1°
о|о
o|f|f |o|f|c|f |c|
flflolf
f|,|o|f|o|,|o|
1
. 0
f
0@@
TT<|o|
f
<|0
f
t)|f |o|f
>l«|0|fh-
«I'l.
hit
Of
Of
0
o|«
0
ol,
,|o
0
—>
Рис.44
В итоге алгоритм, при помощи которого микроконтроллер мог
бы осуществить целочисленное деление, должен выглядеть следую-
следующим образом:
1) расположить и-разрядный делитель под старшими и разряда-
разрядами делимого и вычесть первое из второго. Результат вычитания по-
поместить на место старших я разрядов делимого;
2) если результат вычитания неотрицательный (флаг переноса
остался сброшенным), то разряд частного установить равным 1 и
перейти к п. 4. Если результат вычитания отрицательный (устано-
(установился флаг переноса), то разряд частного установить равным 0 и пе-
перейти к п. 3;
3) восстановить остаток, т. е. сложить делитель со старшими и
разрядами делимого, поместив сумму на место этих старших и раз-
разрядов делимого;
4) проверить, оказался ли младший разряд делимого под млад-
младшим разрядом делителя. Если нет, то перейти к п. 5, если да, то вый-
выйти из подпрограммы;
5) сдвинуть частное на 1 разряд влево. Сдвинуть делимое на 1
разряд влево или, что эквивалентно, делитель на 1 разряд вправо;
6) вычесть «-разрядный делитель из старших л разрядов дели-
делимого. Результат вычитания поместить на место старших л разрядов
делимого. Перейти к п. 2.
Итак, поскольку частное в отличие от произведения можно по-
получать только со старших его цифр, имеются всего две вы-
вычислительные схемы деления (рис. 45 и 46). Схема 1 (рис. 45) ана-
аналогична схеме 3 умножения (см. рис. 33), а схема 2 (рис. 46) —
схеме 4 умножения (см. рис. 34). Очевидно, что схема 1 — это схе-'
ма с рис. 45, в которой делимое по мере вычисления сдвигается
66
влево, делитель остается неподвижным. Схема 2 — это схема с
рис. 46, в которой делимое неподвижно, а делитель по мере вы-
вычисления сдвигается вправо.
СЛвиг
Сдвиг
Рис.45
1
<—
1 «
1
1
0МBп). ОСТ(п) 1
- Сдвиг
¦T(n) P
Сдвиг Е
1
ДМРп). ОСТ(п)
Рис. 46
На схемах (рис. 45 и 46) использованы следующие обозначения:
ДМ — делимое; ДЛ — делитель; ЧСТ — частное; ОСТ — остаток (в
скобках указаны разрядности данных). Цифры частного формиру-
формируются по значению признака переноса CY, устанавливаемого в опера-
67
циях вычитания делителя из делимого или остатка, и сдвигаются вле-
влево. При этом в схеме 1 для получения нового остатка предыдущий
остаток сдвигается влево при неподвижном делителе, а в схеме 2 на-
наоборот, при неподвижном остатке сдвигается вправо делитель. Чаще
всего схема 1 оказывается проще, поскольку не требует команд сдви-
сдвига вправо многобайтных данных (в большинстве микроконтролле-
микроконтроллеров сдвиг вправо сложнее сдвига влево, последний, как мы уже виде-
видели, легко реализуется многобайтовым сложением регистров с сами-
самими собой). Поэтому все рассматриваемые ниже программы деления
выполнены по схеме 1.
В схеме 1 есть два способа получения цифр частного и остатка:
деление с восстановлением остатка и деление без восстановления ос-
остатка. С первым из них мы уже практически познакомились выше и
здесь опишем его формально. Итак, рассмотрим способ деления с вос-
восстановлением остатка для класса целых беззнаковых двоичных чи-
чисел (он применим и для дробных беззнаковых чисел). Пусть X, У, Z,
Q — целые беззнаковые (X, Y, Q — и-разрядные числа, г Z — 2и-
разрядные) числа, такие что Х- Y+ Q=Z, где Q < X. Тогда справедли-
справедливы выражения:
Z:Y = X;Z:Y=(X,Q), A8)
где X— неполное частное; Q — остаток. Если Z < Y, то X - О, Q = Z;
еслиг=У,тоХ= 1, Q=0;eaiHZ=X- Y,toQ=0.
Обозначим Q разность между остатком Q(._j и делителем, где i e A,
2,..., и}; Qq-Z} axj—цифру частного X, причем / = и - i, j e {0,1,...,
и - 1}. Тогда процедуру деления с восстановлением остатка можно за-
записать в виде рекуррентной формулы
= I212-1 Y' "- ЩИ 2B'- " У - ° '
Эта процедура состоит из л циклов деления, причем в каждом t-м
цикле осуществляется сдвиг влево на один двоичный разряд остатка
Qy_,, полученного во время выполнения предыдущего (Ы)-го цикла, и
вычисление разности между удвоенным остатком и делителем:
2Q;_, - Y. Если разность положительна, то в очередной (я-1)-й разряд
частного записывается цифра хп^- - 1, а разность становится новым ос-
остатком Qf Если же разность отрицательна, то в разряд частного записы-
записывается хпА = 0, а остаток 2QM восстанавливается либо путем сложения
2Q,,, -Y+Y, либо извлечением из области его сохранения и интерпре-
интерпретируется как новый остаток Q,- После выполнения n-го цикла деления
68
будут сформированы все и цифр целой части частного. С целью вычис-
вычисления дробной части частного и повышения тем самым точности его
вычисления мы можем многократно повторять последнюю операцию,
вычисляя частное с любой заданной наперед точностью.
Стоит заметить, что в алгоритме A9) вначале производится сдвиг
частного влево, а затем возможное вычитание. Это немного отличает-
отличается от предыдущего описания алгоритма, но это удобнее, т. к. такой
сдвиг освобождает в регистрах место для следующего бита частного.
Однако, в свою очередь, при этом несколько усложняется вычисление
разности и определение разряда частного. Но принципиально замена
местами сдвига частного и возможного вычитания ничего не меня-
меняет — ив том, и в другом случае деление выполняется корректно.
Еще одно замечание. При выполнении деления 2«-разрядного де-
делимого на «-разрядный делитель возможно переполнение «-разрядного
формата частного: появление абсолютной величины, не представимой
в «разрядном формате. Это имеет место в том случае, когда число в
старших п разрядах делимого больше или равно делителю. Перепол-
Переполнение необходимо выявлять программным способом и блокировать
дальнейшие вычисления вследствие их некорректности. Избежать пе-
переполнения можно за счет увеличения разрядности частного до раз-
разрядности делимого, но при этом в два раза увеличивается и количе-
количество циклов деления. Помимо выявления переполнения при делении
чисел необходимо выявлять ситуацию деления на 0. Заметим, что при
анализе переполнения при делении целых чисел можно автоматически
выявлять и деление на 0, как это делается в наших программах.
Теперь давайте посмотрим, как способ деления с восстановлени-
восстановлением остатка и с анализом возможности переполнения частного реали-
реализован в подпрограмме DIV16.
DIV16:
Подпрограмма деления двух целых беззнаковых чисел
Вход:
R7R6R5R4 - 32-х разрядное делимое
R3R2 - 16-ти разрядный делитель
Выход:
R5R4 - 16-ти разрядное частное от деления (если CY=O)
R7R6 - 16-ти разрядный остаток от деления (если CY=O)
Флаги:
CY - признак переполнения 16-ти разрядного частного
или деление на нуль, т.е. CY=1 - признак ошибки и
результат в R7R6 и R5R4 не определен
Используеиье регистры:
69
А. В
Требует свободных байт в стеке:
2
CLR
MOV
SUBB
MOV
SUBB
JC
SETB
RET
DIV16_0:
MOV
DIV16 1:
JNC
MOV
С
A.
A,
R6
R2
A, R7
A, R3
DIV16_0 ; R7R6 < R3R2. значит частное умещается
; в 16-ти битах
С ; CY=1 - переполнение 16-ти разрядного частного или
; деление на нуль
В, «16 ; 16 циклов
MOV
ADD
MOV
MOV
ADDC
MOV
MOV
ADDC
HOV
HOV
ADDC
MOV
JC
частное
MOV
SUBB
MOV
MOV
SUBB
MOV
A, R4
A, R4
R4. A
A, R5
A, R5
R5, A
A, R6
A, R6
R6, A
A, R7
A, R7
R7. A
DI\
A, R6
A, R2
R6, A
A. R7
A. R3
R7, A
R7R6R5R4 сдвинуто влево
DIV16_2 ; если переполнение остатка, вычесть ДЛ, +1 в
; вычли делитель из остатка
DIV16_3 ; +1 в частное, восстанавливать остаток не нухно
A. R6
70
DIV16 2:
ADD
MOV
MOV
ADDC
MOV
SJMP
CLR
MOV
SUBB
MOV
MOV
SUBB
MOV
A. R2
R6. A
A, R7
A, R3
R7, A
DIV16JJ
С
A. R6
A, R2
R6, A
A. R7
A. R3
R7, A
; восстановили
; +0 в частное
; вычли делите
DIVJ6_3:
INC
R4
добавили 1 в частное
DIV16_4:
DJNZ В, DIV16J ; цикл
CLR
RET
; CY=O - нет переполнения 16-ти разрядного частного
: и нет деления на нуль
Рис. 47
Вначале осуществляется проверка на переполнение частного. Пе-
Переполнение возникнет, если старшие два байта делимого будут боль-
больше или равны делителю. Поэтому до того, как начать деление, мы
сравниваем их (точнее, вычитаем из старших двух байт делимого
делитель) и переходим непосредственно к операции деления только
в случае, если CY=1. Если это не так, то мы выходим из подпрограм-
подпрограммы со знаком "аварийной ситуации" (установленным в 1 флагом
переноса), и основная программа, вызвавшая подпрограмму DIV16,
должна будет каким-либо образом информировать вас о том, что
деление осуществлено некорректно.
Далее все достаточно тривиально. В регистре В организован счет-
счетчик циклов — их должно быть 16, коль скоро 32-разрядное число
делится на 16-разрядное. Сдвиг влево осуществляется путем сложе-
сложения содержимого регистров R7R6R5R4 с самими собой (кто забыл,
напомню, что сдвиг влево на 1 разряд эквивалентен удвоению дво-
двоичного числа). Если при вычитании делителя из соответствующего
остатка перенос не возник, в соответствующий разряд частного за-
заносится 1, если возник, то заносится 0 и остаток восстанавливается. В
71
ходе выполнения подпрограммы делимое на каждом шаге по биту
освобождает регистры R4R5 (начиная с младшего бита R4), и в этих
регистрах "накапливается" частное. Выглядит это так, как будто час-
частное по мере его определения вытесняет делимое из R4R5.
Способ деления без восстановления остатка определяется выра-
выражениями:
_ , ^.-Г, если ?_,>();
[1,
(О,
ссли G, ^ 0; B0)
,ссли<2; <0.
В этой процедуре при получении отрицательной разности
Q,_i = 2Qj_2- У последняя не восстанавливается, как в приведенной
выше рекуррентной формуле для деления с восстановлением остат-
остатка Qj_, = 2Q^2, а сдвигается в следующем, i-m, цикле влево и к ней
прибавляется делитель: Qj= 2QM + У.
Почему это возможно? В схеме с восстановлением остатка при по-
получении в (г- 1)-м цикле отрицательной разности Qi: = 2Q2 - У мы,
во-первых, прибавляли к этой разности делитель Y (восстанавливали
остаток, имея в итоге Qt_, = 2Qj_2), во-вторых, сдвигали влево Q{_v по-
получая Q, = 2Qj_,, и затем уже в i-м цикле вычитали этот делитель Y из
Qit получая Qt = 2Qt_, — Y= 2BQ{_2) - У. То есть прибавили к отрица-
отрицательной разности У, сдвинули его влево вместе с ней (удвоили) и за-
затем в t-м цикле вновь вычли. А если к какой-либо величине прибавить
константу 2 У, а затем вычесть У, то это эквивалентно тому, чтобы про-
просто один раз прибавить к ней У. Иными словами, вместо сложения Ус
2Qi_2, удвоения этой суммы и последующего вычитания из нее Умож-
но вначале удвоить 2Q;_2 и прибавить к удвоенному У. А ведь это и
есть алгоритм деления без восстановления остатка, и он, как мы толь-
только что убедились, идентичен алгоритму деления с восстановлением
остатка. Нижеследующее преобразование еще раз подтверждает это:
Q, = 2Qj_, + У = 2 BQ;_2 - У) + Y = 2 B(^2) - У. B1)
При способе деления с восстановлением остатка в каждом цикле
производятся либо одна, либо две операции: вычитание или вычита-
вычитание и сложение (при восстановлении). При втором способе прово-
проводится только одна операция, вычитание или сложение. Поэтому вто-
второй способ имеет потенциально большее быстродействие, но это его
преимущество проявляется лишь при аппаратных реализациях де-
72
ления или при делении длинных чисел. Программная реализация вто-
второго способа сложнее, что связано с необходимостью хранения знака
остатка до начала следующего цикла деления с целью определения
типа выполняемой операции (сложение или вычитание делителя) и
коррекции конечного остатка Qn в случае, если Qn_l < 0 (необходимо
вычесть делитель). Вследствие этого деление без восстановления ос-
остатка редко используется в программах деления коротких чисел.
Последовательность действий при делении с восстановлением и без
восстановления остатка поясняется на примере деления двоичных чи-
чисел 00100000:1111 = @010, 0010) соответственно в табл. 14 и 15, в кото-
которых приняты следующие обозначения операций: <—Qt — сдвиг влево
остатка: ±Y— суммирование или вычитание делителя; =Q, — присво-
присвоение значения остатку. Заметим, что остаток считается неотрицатель-
неотрицательным при делении с восстановлением остатка, если в результате вычита-
вычитания 2Qj_[ - Y признак переноса CY = 0, а при делении без восстановле-
восстановления остатка, если признак переноса CY не меняет своего значения меж-
между операциями2QiA и2QM±Y,т.е.CY: 1->1 илиСУ:0->0.
Еще одно замечание: если при выполнении деления по схеме без
Таблица 14
Деление с восстановлением остатка
I
1
2
3
4
Остаток
CY
0
1
1
0
1
1
1
0
1
1
Q
01000000
1111
01010000
1111
01000000
10000000
1111
10010000
1111
10000000
00000000
1111
00010000
00100000
1111
00110000
1111
00100000
X*.
0
0
1
0
Тип операции
<-Q>
-У
-Qx<0
+Y
=О,>0
<-0,
-Y
=а<о
+Y
=а>о
<-о>
-Y
=СЬ>0
<-а
-Y
= С?4<0
+Y
=С?4>0
73
Таблица IS
Деление без восотановления остатка
1
1
2
3
4
Остаток
CY
0
1
0
1
1
0
1
1
Q
01000000
1111
01010000
10100000
1111
10010000
00100000
1111
00010000
00100000
1111
00110000
1111
00100000
0
0
1
0
Тип операции
«-Q,
-У
=О,<0
<-о,
+ Y
=Ог<О
<-о>
+Y
=Qj>0
*-Ch
-Y
=О,<0
+ Y
=О«>0
восстановления остатка в последнем цикле Q; окажется отрицатель-
отрицательным, его нужно восстановить так же, как это делалось в завершаю-
завершающем цикле схемы с восстановлением остатка (сравните последние
пять строк обеих таблиц). Это нужно для того, чтобы получить в ре-
результате деления неискаженный целочисленный остаток (если дели-
делимое не делится нацело).
Как уже упоминалось, при выполнении деления 2и-разрядного
делимого на n-разрядный делитель возможно переполнение «-раз-
«-разрядного частного. Фактически это означает, что получаемое частное
нельзя представить в «-разрядном формате и ему требуется большее
количество разрядов. Например, при попытке разделить 111100002
на 10112 вы получите 101012 с остатком 10012 B40 : 11 = 21 и 9 в
остатке). Это переполнение имеет место в том случае, когда число в
старших п разрядах делимого больше или равно делителю A1112 >
10112). Такое переполнение нужно выявлять программным способом
и блокировать дальнейшее вычисление вследствие его некорректно-
некорректности. Избежать переполнения можно при увеличении разрядности
частного до разрядности делителя с одновременным увеличением в
два раза количества циклов деления. Помимо выявления переполне-
переполнения при делении чисел необходимо выявлять ситуацию деления на
нуль. Отметим, что при анализе переполнения деления целых чисел
74
можно автоматически выявлять и деление на нуль.
Как и раньше, проверить работоспособность программ полезно
на тестовых наборах данных (табл. 16) или на аналогичных, состав-
составленных с использованием стандартного Windows-калькулятора.
Таблица 16
Тесты деления формата 32:16 = A6,16)
(целые двеичные беззнаковые числа)
Представление чисел
Шестнгииатеричное
FFFEO0O1 :FFFF=(FFFF.OOOO)
FFFE0002:FFFF=(FFFF,0001)
00FE0100:FF00=@0FF,0)
FFFDFFFF:FFFF=(FFFE,FFFD)
38E31 C75:AAAA=E555,0003)
3FFF0000:7FFF=GFFE,7FFE)
Десятичное
4294836225:65535=F5535,0)
4294836226:65535=F5535,1)
16646400:65280=B55,0)
4294836223:65535=F5534,65533)
954408053:43690=B1845,3)
10736762B8:32767=C2766.32766)
Примечание. Здесь и далее результат целочисленного деления мы будем пред-
представлять двумя числами, помещенными в круглые скобки и разделенными запятой.
При этом первое из чисел — целое частное, второе — остаток.
НЕКОТОРЫЕ ПОЛЕЗНЫЕ ПОДПРОГРАММЫ
В настоящем разделе мы познакомимся с тремя полезными под-
подпрограммами, которые нам часто понадобятся в дальнейшем.
Первая из них — подпрограмма сравнения двух целых 16-раз-
16-разрядных чисел.
СМР16:
Подпрограмма сравнения двух целых 16-ти разрядных чисел
Вход:
R3R2 - 1-е число
R5R4 - 2-е число
Выход:
А - равен 0, если числа равны
не равен 0, если числа не равны
Флаги:
CY - равен 1, если число в R5R4 меньше числа в R3R2
равен 0. если число в R5R4 >= числа в R3R2
(при сравнении чисел без знака)
0V - равен 1, если число в R5R4 меньше числа В R3R2
равен 0, если число в R5R4 >= числа в R3R2
75
(при сравнении чисел со знаком)
Т.е. для чисел без знака:
R5R4 = R3R2. если А = О (JZ)
R5R4 о R3R2, если А о О (JNZ)
R5R4 < R3R2, если CY = 1 (JC)
R5R4 > R3R2, если CY = 0 и А О О
R5R4 >= R3R2, если CY = О (JNC)
Т.е. для чисел со знаком:
R5R4 = R3R2. если А = О (JZ)
R5R4 о R3R2. если А о О (JNZ)
R5R4 < R3R2. если 0V = 1 (J0)
R5R4 > R3R2, если 0V = 0 и А о О
R5R4 >= R3R2. если 0V = О (JN0)
Используемые регистры:
А. В
Требует свободных байт в стеке:
2
CLR
M0V
SUBB
MDV
M0V
SUBB
А.
А.
В.
А,
А.
R4
R2
А
R5
R3
СМР16 1:
JNS
CPL
0RL
RET
очистить флаг переноса для первого SUBB
МЛБ уменьшаемого
вычесть МЛБ вычитаемого
записать разность обоих МЛБ в В
СТБ уменьшаемого
вычесть (с учетом заема) СТБ вычитаемого
CY уже установлен
СМР16_1 ; реальный знак разности чисел со знаком (с учетом
; возможного переполнения при вычитании) будет равен
; SG XOR OV. JNS и CPL вместе реализуют ф-цию XOR
; и результат оказывается в 0V
0V
; 0V уже установлен
А, В ; А будет равен 0, если разность чисел равна
; нуле, т.е. числа равны
Рис.48
76
Как вы помните, если для сравнения двух чисел не предусмотре-
предусмотрено специальных команд, то операция сравнения может быть выпол-
выполнена путем вычитания одного числа из другого и анализа получен-
полученной разности. Поэтому подпрограмма выполняет следующие дей-
действия. Вначале осуществляется вычитание младшего байта одного
числа из младшего байта другого, и результат сохраняется в регистре
В. Далее осуществляется вычитание с учетом заема старших байтов.
Если после вычитания CY = 1, то уменьшаемое меньше вычитаемого
(в данном случае речь идет о числах без знака), иначе если CY = О,
уменьшаемое больше или равно вычитаемому. Далее, если сравни-
сравниваемые числа равны, то и первый (хранимый в В), и второй (в акку-
аккумуляторе) байты разности будут равны 0, и следовательно, результат
завершающей подпрограмму операции ORLA,B оставит аккумуля-
аккумулятор нулевым. В противном случае (если сравниваемые числа не рав-
равны), на выходе подпрограммы в аккумуляторе будет ненулевой ре-
результат. Таким образом, для беззнаковых чисел анализ после выпол-
выполнения подпрограммы СМР16 флага CY и аккумулятора дает инфор-
информацию о том, равны сравниваемые числа или нет, и если нет, то ка-
какое из них больше, а какое меньше.
Рассматриваемая подпрограмма также осуществляет сравнение
знаковых чисел, для чего в нее включен макрос JNS СМР 16_1 и ко-
команда CPL OV. Однако более подробное описание, для чего они нуж-
нужны и что они делают, мы дадим в главе, рассматривающей числа со
знаком.
На рис. 49 приведена подпрограмма арифметического сдвига вле-
влево на 1 бит двухбайтового числа, хранимого в регистрах R5 и R4. При
этом в младший бит числа заносится 0. Как нетрудно сообразить,
число в регистрах R5 и R4 в результате этой операции удваивается,
поэтому можно пользоваться этой подпрограммой для удвоения
чисел, учетверения и т. д.
В ряде случаев возникает необходимость в так называемом логи-
логическом сдвиге двухбайтового числа на 1 разряд влево через перенос
(подпрограмма RLC16). В результате этого сдвига в младший бит
числа, хранящегося в регистре R4, заносится содержимое флага CY
(оно может быть как 0, так и 1 в зависимости от результата выполне-
выполнения предыдущих команд). В результате этого сдвига в CY оказывает-
оказывается содержимое старшего бита регистра R5.
RLA16:
;; Подпрограмма арифметического сдвига 16-ти разрядного числа ;;
;; влево на 1 разряд: в младший разряд числа задвигается 0 ;;
77
Вход:
R5R4 - число
Выход:
R5R4 - сдвинутое число
Флаги:
CY - выдвинутый из числа бит
Используемые регистры:
Требует свободных байт в стеке:
2
CLR
RLC16:
Подпрограмма логического сдвига 16-ти разрядного числа влево на
1 разряд через перенос
Вход:
R5R4 - число
CY - задвигаемый в чиспо бит
Выход:
R5R4 - сдвинутое число
Флаги:
CY - выдвинутый из числа бит
Используемые регистры:
А
Требует свободных байт в стеке:
2
MOV A. R4
RLC A
MOV R4. А
MOV A, R5
RLC A
MOV R5, А
RET
Рис.49
78
Схожие подпрограммы приведены на рис. 50: арифметический и
логический сдвиг вправо двухбайтового числа из R5 и R4. Правда,
подпрограмма RRA16 ориентирована на работу со знаковыми чис-
числами, для чего она оставляет неизменным старший (седьмой) бит
регистра R5. Более подробно об этом мы поговорим в главе, рассмат-
рассматривающей числа со знаком.
RRA16:
Подпрограмма арифметического сдвига 16-ти разрядного числа
вправо на 1 разряд: старыми (знаковый) разряд числа остается
неизменным
Вход:
R5R4 - число
Выход:
R5R4 - сдвинутое число
Флаги:
CY - выдвинутый из числа бит
Используемые регистры:
А
Требует свободных байт в стеке:
2
MOV A, R5
RLC А
RRC16:
;; Подпрограмма логического сдвига 16-ти разрядного числа вправо на;;
;; 1 разряд
;; Вход:
;; R5R4 -
;; cy -
;; Выход:
;; R5R4 -
;; Флаги:
;; CY -
через перенос ;;
число ;;
задвигаемый в число бит ;;
сдвинутое число ;;
выдвинутый из числа бит ;;
:; Используемые регистры: ;;
:: А
;; Требует
:: 2
свободных байт в стеке: ;;
79
MOV
RRC
MOV
MOV
RRC
MOV
A.
A
R5,
A,
A
R4.
R5
A
R4
A
RET
Рис. 50
В остальном приведенные подпрограммы очевидны и не требу-
требуют каких-либо объяснений.
НЕКОТОРЫЕ ПОЛЕЗНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ
В последнем разделе главы, посвященной беззнаковым числам,
мы познакомимся с некоторыми приемами, используемыми про-
программистами для повышения эффективности написанных ими
программ.
Под повышением эффективности программ мы будем понимать
повышение скорости их выполнения и (или) сокращение длины про-
программы. При этом наиболее важной задачей является, безусловно,
повышение быстродействия подпрограмм, ибо насколько не возрас-
возрастало бы быстродействие микроконтроллеров или микропроцессоров,
всегда находятся задачи, использующие это быстродействие на пре-
пределе (а подчас его все равно не хватает). Поэтому, сколь бы произво-
производительными ни были аппаратные средства, для которых вы разраба-
разрабатываете программное обеспечение, навыки повышения скорости
выполнения написанных вами программ никогда не помешают.
Умножение и деление на 2561О
Умножение на 256 означает, что вместо двухбайтового числа вы
должны взять трехбайтовое, старшие два байта которого идентичны
исходным двум байтам, а младший байт равен 0. Иными словами,
умножение на 256 означает "подпихивание" к исходному числу справа
нулевого байта.
Например, пусть в регистрах R5 и R4 хранится число 700010
(R5=1BH, R4=58H) и вам надо умножить его на 256. Если использо-
использовать самую быстродействующую из рассмотренных выше подпрог-
подпрограмм MUL16M, то вы должны занести в регистры R3 и R2 число 25610
(R3=01H, R2=0H) и вызвать упомянутую подпрограмму, после чего
80
примерно через 70 мкс12 в регистрах R6, R5 и R4 вы получите требуе-
требуемое произведение.
Но есть и другой путь. Перенесите содержимое регистра R5 в R6
(сохранив предварительно R6), R4 в R5 и занулите R7 и R4. Описы-
Описываемые действия займут менее 10 мкс, а результат будет тем же.
Деление на 256 означает, что вы должны попросту отбросить млад-
младший байт делимого, если вас не интересует остаток от целочисленного
деления. Если же остаток вас интересует, например для округления,
именно в него и должен пойти отбрасываемый младший байт.
Например, пусть вам нужно разделить трехбайтовое число из R6, со-
сохранив предварительно R6, R5, R4 на 256. Вы можете занулить регистр
R7, поместить 256 в регистры R3 и R2 (R3=01H, R2=0H) и вызвать под-
подпрограмму DIV16, после чего через несколько сотен микросекунд в реги-
регистрах R5 и R4 вы получите требуемое частное, а в регистре R6—остаток.
Но есть и другой путь. Перенесите содержимое регистра R4 в R6,
R5 в R4, R6 в R5. Описываемые действия займут несколько микросе-
микросекунд, а результат будет тем же.
Очень часто деление на 256 используется при усреднении резуль-
результатов измерения. Например, требуется усреднить результат по вы-
выборке из сотни измерений. В привычной нам десятичной системе
нужно для этого просуммировать все 100 измерений, а затем отбро-
отбросить от полученного числа два младших разряда. Если же мы соби-
собираемся поручить все эти действия микроконтроллеру, то логично
заставить его просуммировать не 100, а 128 результатов, удвоить по-
полученную сумму (об этом чуть ниже) и разделить ее на 256. При та-
таком алгоритме обработка займет менее двух десятков микросекунд.
Умножение и деление на степень двойки
И здесь все просто. Умножение на 2 означает, что вы должны сдви-
сдвинуть ваше число влево на 1 разряд, занеся при этом в младший бит
сдвинутого числа 0. Удобнее всего это сделать при помощи рассмот-
рассмотренной выше подпрограммы RLA16. Единственное, что при этом нуж-
нужно учитывать — это то, что в результате удвоения результат может
оказаться более чем двухбайтовым. Иными словами, если число, ко-
которое вы хотите удвоить, больше, чем 32768,0, то после удвоения оно
превысит 65535,0 и потребует для своего хранения не два, а три байта.
Выход из сложившейся ситуации следующий—используйте трех-
трехбайтовый или четырехбайтовый формат для хранения результата.
Например, давайте рассмотрим случай четырехбайтового формата.
"Напомню, речь идет об обычном классическом х51-совместимом микроконтрол-
микроконтроллере, работающем на частоте 12 МГц.
81
Разместим наше двухбайтовое число в регистрах R5 и R4 (старший
байт — в R5), а старшие незначащие вначале регистры R7 и R6 зану-
лим. Чтобы удвоить исходный результат, достаточно вызвать простей-
простейшую подпрограмму RLA32 (рис. 51). Она, в свою очередь, вначале
вызывает подпрограмму RLA16, удваивающую число в R5R4 и сохра-
сохраняющую старший, шестнадцатый", бит удвоенного числа в CY. Далее
с его учетом удваиваются старшие биты числа (в R7R6). Очевидно,
что если исходное число двухбайтовое, то подпрограмма RLA32 мо-
может быть вызвана без риска переполнения до 16 раз включительно.
RLA32:
;; Подпрограмма
;; без знака
;; Вход:
;; R7R6R5R4 -
;; Выход:
;; R7R6R5R4 -
;: Олаги:
быстрого удвоения 32-х разрядного числа ;;
число ;;
сдвинутое число ;;
;; CY - выдвинутый из числа бит ;
;; Используемые
;; А
регистры: ;
;; Требует свободных байт в стеке: ;
;; 4
LCALL
M0V
RLC
MOV
MOV
RLC
MOV
RLA16
A, R6
А
R6, А
A, R7
А
R7, А
RET
Рис. 51
"Напомню, чио нумерация бит начинается с нуля.
82
Нетрудно догадаться, что если нужно не удвоить число, а умно-
умножить его, скажем, на 8, нам достаточно всего-навсего трижды выз-
вызвать подпрограмму RLA32. Таким образом, умножение числа на 2",
где п - 1... 16, сводится к и-кратному вызову RLA32.
Деление числа на 2 осуществляется сдвигом беззнакового числа на 1
разряд вправо, при этом в старший бит числа должен быть занесен 0.
Это можно осуществить при помощи приведенной ниже простейшей
подпрограммы RRAU16. Она, в свою очередь, вначале обнуляет флаг
CY, после чего вызывает подпрограмму RRC16, сдвигающую вправо
число в R5R4 и сохраняющую младший бит сдвинутого числа в CY.
RRAU16:
Подпрограмма быстрого деления на 2 16-ти разрядного числа
без знака
Вход;
R5R4 - число
Выход:
R5R4 - сдвинутое число
©лаги:
CY - выдвинутый из числа бит
Используемые регистры:
А
Требует свободных байт в стеке:
2
CLR С
LCALL RRC16
RET
Рис. 52
Если вам нужно осуществить быстрое деление без округления (об
этом ниже) беззнакового числа на 2", где п = 1... 16, вы попросту дол-
должны осуществить n-кратный вызов RRAU16.
Однако необходимо иметь ввиду, что осуществляя деление,
следует контролировать, не получили ли вы в результате нулевое
частное. Например, в регистрах R5R4 находилось число 3 и вы
дважды вызвали RRAU16. Такая ситуация, когда в результате де-
деления частное оказалось равным 0, называется антипереполнени-
антипереполнением, и обязанность предотвращения этой ситуации обычно лежит
83
на программисте. Ниже мы еще коснемся этого вопроса, а пока
пойдем дальше.
Умножение и деление на 16,о
В свете предыдущего материала, чтобы умножить (разделить)
число на 16]0> достаточно четыре раза вызвать подпрограмму RLA16
(RRAU16). Однако микроконтроллеры семейства х51 имеют в своей
системе команд инструкцию работы с тетрадами (половинами бай-
байтов), благодаря этому возможно осуществить подобное умножение
(деление) еще быстрее, чем при четырехкратном вызове упомяну-
упомянутых подпрограмм.
В основе рассматриваемых операций лежат инструкции SWAP A
и XCHD A,@Ri. Первая меняет местами старшую и младшую тетрады
аккумулятора, вторая — младшую тетраду аккумулятора с младшей
тетрадой байта из внутреннего ОЗУ, адрес которого хранится в регис-
регистре Ri. Посмотрим, как можно с их помощью реализовать, например,
умножение на 1610 целого беззнакового двухбайтового числа.
Умножение на 1610 означает, что младшая тетрада младшего бай-
байта должна быть сдвинута на место старшей, старшая—на место млад-
младшей тетрады старшего байта, младшая тетрада старшего байта — на
место старшей, а старшая — на место младшей тетрады третьего бай-
байта. На место младшей тетрады младшего байта надо поместить нули.
Сказанное иллюстрируется рис. 53, где показан результат умноже-
умножения на 16 шестнадцатиричного числа 4321Н.
Множимое: СТБ=01000011 В=43Н; МЛБ=00100001 В=21Н
Произведение:
СТБ=00000100В=04Н; СРБ=00П0010В=32Н; МЛБ=00010000В=10Н
Рис. 53
Как видите, произведение в отличие от двухбайтового множи-
множимого уже трехбайтовое. Внимательный анализ тетрад множимого и
произведения позволяет понять, какие перемещения тетрад нужно
сделать, чтобы реализовать упомянутое умножение на 16,0.
Действия эти осуществляет подпрограмма R0M_16, приведенная
на рис. 54. При этом множимое находится в памяти, а в ячейке R0
хранится адрес его младшего байта. Старший байт множимого рас-
расположен вслед за младшим в ячейке, адрес которой на 1 больше ад-
адреса младшего байта. После умножения младший байт произведе-
произведения замещает младший байт множимого, средний байт произведе-
84
ния — старший байт множимого, а старший байт произведения хра-
хранится в аккумуляторе. В качестве комментариев приведено содержи-
содержимое младшего (МЛБ), среднего (СРБ) и старшего (СТБ) байтов про-
произведения после выполнения каждой из команд.
Время выполнения подпрограммы R0M_16 составляет 14 мкс (с
учетом времени вызова самой подпрограммы), что почти вчетверо
меньше затрат на четыре последовательных вызова RRAU16.
ROM 16:
; Подпрограмма
; без знака
.: Вход:
быстрого
умножения на
1610 16-ти разрядного числа ;
; в R0 - адрес младшего байта числа, старший байт - в следующей ;:
за младшим
; Выход:
,; младший и
А
i i "
,; Используемые
:; A, R0
ячейке
средний байт по исходным адреса», старший в регистре;;
регистры:
;; Требует свободных байт
;; 2
CLR
ХСН
SWAP
XCHD
ХСН
INC
ХСН
SWAP
ХСН
XCHD
RET
А
A. @R0
А
A. @R0
А, №0
R0
А. №0
А
A, @R0
А, №0
в стеке:
;АКК=(ООН),
;АКК=B1Н),
;АКК=A2Н),
;АКК=A0Н),
;АКК=@2Н).
СТБ=D3Н), МЛБ=B1Н)
СТБ=D3Н). НЛБ=(ООН)
СТБ=D3Н), МЛБ=(ООН)
СТБ=D3Н). МЛБ=@2Н)
СТБ=D3Н). МЛБ=A0Н)
;В R0 - адрес СТБ
;АКК=D3Н),
;АКК=C4Н),
;АКК=(С2Н),
;АКК=@4Н),
СТБ=@2Н), МЛБ=A0Н)
СТБ=@2Н), МЛБ=A0Н)
СТБ=C4Н), МЛБ=A0Н)
СТБ=C2Н). МЛБ=A0Н)
Рис.54
Подпрограмма быстрого деления числа на 1610 может быть орга-
организована аналогичным образом. Предоставляем вам возможность
попрактиковаться, самим составив требуемую для ее реализации пос-
последовательность команд.
85
Умножение на число, близкое к степени двойки
Предположим, что целое беззнаковое число нужно умножить, к
примеру, на 1710. В свете предыдущего материала очевидно, что са-
самый быстрый способ получения такого произведения — быстро ум-
умножить множимое на 16,0 и сложить его с множимым (оно перед
умножением должно быть где-то сохранено). Почему это так, оче-
очевидно из следующего соотношения:
а ¦ 17 = а ¦ 16 + я.
Еще один пример: нужно умножить число на 254,0. Сохраним
множимое, затем умножим его на 25610, после чего дважды вычтем
из произведения сохраненное множимое:
а ¦ 254 = я ¦ 256 - а - а.
Подобная операция займет около 30 мкс, что вдвое быстрее, чем
если бы мы воспользовались подпрограммой MUL16M.
Умножение на число, представляющее сумму или разность
степеней двойки
В ряде случаев множитель может быть представлен суммой или
разностью двух чисел, каждое из которых является степенью числа 2.
Например, нам нужно умножить некоторое число на 272. Так как
272 = 256 + 16, то можно заметно ускорить процедуру умножения,
вначале быстро умножив множимое на 256, затем быстро умножив
его на 16 и сложив результаты обоих умножений:
а-272= я- 256 + а- 16.
Еще пример: умножение числа на 192. Заметим, что 192 = 256 -
64. Тогда
я- 192 = а-256-я-64 = я-256-(я- 16)-2-2.
Иными словами, умножьте число на 256 (см. выше), запомните
результат, затем умножьте его на 16, дважды сдвиньте влево для пос-
последующего учетверения и вычтите из запомненного.
Как нетрудно догадаться, если бы вам пришлось умножать исход-
исходное число не на 192, а на 320, алгоритм был бы тот же за исключением
того, что вместо вычитания нам пришлось бы осуществлять сложение:
а ¦ 320 = я • 256 + я • 64 = я • 256 + (а ¦ 16) - 2 • 2.
86
Подобных чисел, являющихся суммой двух чисел, каждое из ко-
которых есть степень двойки, немало, и если вы научитесь их выделять
из общей массы целых беззнаковых чисел, вам удастся оптимизиро-
оптимизировать некоторые из написанных вами алгоритмов.
Необходимо отметить еще один момент. Как отмечалось выше,
скорость выполнения подпрограммы MUL16M весьма высока —
умножение двух целых беззнаковых 16-разрядных чисел осуществ-
осуществляется с ее помощью всего за 61 мкс (точнее за 61 машинный цикл
стандартного МК семейства х51). Б этой связи выигрыши на два-три
десятка циклов, достигаемые при помощи рекомендаций, приведен-
приведенных выше, не выглядят очень заметными. Однако если приводимый
здесь материал используется для разработки программ для других
микроконтроллеров, у которых нет инструкций умножения восьми-
или шестнадцатиразрядных чисел, вам придется реализовывать ум-
умножение по одной из приведенных выше вычислительных схем
(рис. 31—34). Как отмечалось в конце раздела, посвященного умно-
умножению, скорость выполнения подпрограмм, реализующих умноже-
умножение по этим схемам только при помощи сложений и сдвигов, почти
на порядок ниже, чем у MUL16M. В связи с этим речь может идти не
о том, что вы заменили 61-микросекундную подпрограмму алгорит-
алгоритмом, потребовавшим для своей реализации 10.. .20 или даже 30 мкс,
а о том, что эти Ю...30-микросекундные алгоритмы заменили
300...400-микросекундную подпрограмму. В этом случае, как види-
видите, выигрыш оказывается весьма существенным, и применение при-
приводимых в настоящем разделе рекомендаций выглядит более чем
оправданным14.
Умножение на прааильную дробь
Во многих применениях, когда микроконтроллеры, казалось бы,
используются только для работы с целочисленными результатами,
нам все же иногда приходится умножать или делить эти результаты
на дроби. Это не представляет проблемы, если вы используете про-
программы арифметики с фиксированной или плавающей точкой, но
может осложнить жизнь, если вы эти программы не используете. По
крайней мере для новичков может быть непонятно, как оставаясь в
рамках целочисленных операций сложения, вычитания, умножения
и деления можно умножить число, например, на п = 3,141592... или
разделить на 0,933.
" Естественно, если ваш микроконтроллер располагает инструкцией, позволяющей
умножать друг на друга два 16-разрядных или два 32-разрядных числа, многие из
этих рекомендаций могут понадобиться вам лишь для расширения кругозора.
87
На самом деле все очень просто. Если дробь, на которую нам нуж-
нужно умножить число, обыкновенная (т. е. не десятичная, а записывае-
записываемая в виде а/Ъ, где а и Ъ — целые числа), то достаточно вначале умно-
умножить множимое на числитель дроби, а затем полученное произведе-
произведение разделить на знаменатель. При этом нужно иметь в виду следую-
следующие обстоятельства. Во-первых, обязательно вначале умножайте на
числитель, а только после этого делите на знаменатель дроби, а не на-
наоборот. Если вы вначале осуществите деление на знаменатель, вы по-
почти наверняка внесете заметную погрешность в ваши вычисления из-
за ограниченной точности операции деления. Например, вам нужно
умножить 15 на 9/10. Умножив 15 на 9 и затем разделив его на 10, вы
получите 13, если не будете округлять результат, а попросту отбросите
остаток отделения, или 14, если используете этот остаток для округле-
округления. Если же вы вначале осуществите операцию деления, то вы полу-
получите 1 (если отбросите остаток) или 2 (если округлите частное с его
помощью). В первом случае после умножения на числитель дроби вы
получите 9, а во втором — 18. И то, и другое весьма далеко от правиль-
правильного результата 13,5, несоизмеримо дальше того, что получилось, ког-
когда мы вначале умножили число на числитель дроби и только потом
разделили на полученное произведение на знаменатель.
Во-вторых, вы должны обеспечить такой формат представления
данных, чтобы при умножении не происходило переполнения. На-
Например, если множимое двухбайтовое, а числитель и знаменатель
дроби каждый не превышают 6553510, вы можете спокойно пользо-
пользоваться подпрограммами умножения и деления, приведенными в на-
настоящей главе. Но если числитель дроби, на которую вы должны
умножить ваше множимое, более 65535,0, вы должны предвидеть, что
результат произведения может оказаться больше, чем 232 -1, и следу-
следует использовать пяти- или шестибайтовый формат представления, а
также соответствующие подпрограммы умножения и деления.
В-третьих, нужно помнить, что операции умножения и особен-
особенно деления отнимают довольно много времени у микроконтроллера,
если у него, как у х51, нет аппаратного делителя. В свете этого жела-
желательно осуществлять некоторые преобразования дробей, на которые
мы должны умножать наше целочисленное множимое для того, что-
чтобы после этого преобразования числитель или знаменатель представ-
представлял собой степень двойки. Как нетрудно догадаться, лучше, если сте-
степенью двойки будет знаменатель, тогда длительную операцию деле-
деления можно будет заменить рядом более коротких операций переме-
перемещения байтов и сдвигов.
Что здесь имеется ввиду? Чтобы лучше понять, о чем идет речь,
рассмотрим такой пример. Пусть вам нужно полученный в резуль-
88
тате тех или иных измерений двухбайтовый целочисленный резуль-
результат (к примеру, 3 Ю0]0) умножить на 295/317. Конечно, можно занес-
занести этот результат в регистры R5R4, в R3R2 занести 29510=127Н, выз-
вызвать подпрограмму MUL16M, затем разместить в R3R2 31710=13DH
и вызвать подпрограмму DIV16. В результате вы получите 288410 и
272]0 в остатке, после чего вам еще нужно округлить результат до
288510.
Но есть и другой путь. Преобразуйте 295/317 в десятичную дробь и
умножьте ее на 65536,0 — проделав эти вычисления, вы получите
60987,760. Округлите результат до ближайшего целого числа F098810).
Сравните 295/317=0,9305993690 и 60988/65536=0,9306030 — значения
дробей равны с точностью лучше одной тысячной процента. Поэтому
умножение нашего числа 3100,0 на 295/317 мы вполне можем заменить
умножением на 60988/65536, ошибка за счет замены одной дробью дру-
другой не превысит ту самую тысячную процента. А умножение на 60988/
65536, как нетрудно догадаться, дает очень существенный выигрыш в
скорости вычислений — ведь вам только нужно умножить 3100,0 на
6098810> а деление на 65536,0 сводится лишь к "отсоединению" от произ-
произведения младших двух байт и переносу их в остаток. Проверьте при
помощи Windows-KMbKyflHTopa3100,0-6098810=l8906280010=0B44DE90H;
0B44DE90H: 10000Н = @B44H.0DE90HI5. Далее округлите результат пу-
путем сложения частного со старшим битом остатка 0DE90H и результат
готов! Причем, заметьте, вы сэкономили на самой длительной опера-
операции —делении, вместо нее вы лишь перенесли два байта из одной пары
регистров в другую.
Таким образом, наиболее эффективный способ вычисления про-
произведения числа на правильную дробь — замена последней другой
дробью, числитель которой равен числу 65536,0, умноженному на
значение дроби, а знаменатель—655361о. Фактически эта новая дробь
определяется следующим образом:
а/Ъ - [ (а/Ъ) ¦ 6553610)]/65536ш. B2)
Осуществив такое преобразование, вы далее лишь умножаете
ваше число на числитель этой новой дроби, после чего вместо опера-
операции деления произведения на 65536,0 попросту отбрасываете от про-
произведения младшие два байта или переносите их в остаток, после чего
используете для округления окончательного результата.
15 Напомню, что результат целочисленного деления мы представляем двумя числа-
числами, помещенными в круглые скобки и разделенными запятой. При этом первое из
чисел — целое частное, второе — остаток.
89
Умножение на неправильную дробь
Напомню, что правильная дробь — это дробь, числитель кото-
которой меньше знаменателя. При переводе ее в десятичную форму она
характеризуется тем, что ненулевой у нее является только дробная
часть, а целая равна 0. Например, 0,366 или 0,9999—это правильные
дроби, в отличие от 3,1416 или 1,002.
Выше мы рассмотрели, как осуществить умножение целого числа
на правильную Дробь. А как быть, если вам нужно умножить число на
неправильную дробь? Ответ очевиден, но для тех, кто еще не догадал-
догадался, подскажу. Отделите от вашей неправильной дроби целую часть,
умножьте на нее свое число и результат где-то сохраните. Затем ум-
умножьте свое число на дробную часть аналогично тому, как это было
описано ранее. После этого сложите оба произведения и вы получите
искомый результат умножения вашего числа на неправильную дробь.
Да, а как быть, если вам надо не умножить число на дробь, а раз-
разделить на нее? В общем, это тоже очевидно—чтобы разделить число
на дробь, нужно умножить его на знаменатель дроби, а затем разде-
разделить на ее числитель. Остальное найдете в учебнике математики для
начальной школы, по-моему, это изучалось где-то там.
Округление чисел после операции деления
И последнее, что хотелось бы обсудить в настоящей главе — тех-
техника округления целого частного. Напомню, что в результате опера-
операции целочисленного деления в общем случае вы получаете частное и
остаток. Во многих случаях последний просто отбрасывают, хотя
правильнее округлить частное с его учетом.
Давайте вспомним правило округления дробного результата до
целого. Если округляемый результат записан в виде десятичной дроби
и цифра, идущая после десятичной запятой — 0,1,2,3 или 4, то дроб-
дробная часть числа попросту отбрасывается, а целая остается без измене-
изменений. Если после десятичной запятой идет 5, 6, 7, 8 или 9, то дробная
часть числа также отбрасывается, а целая увеличивается на 1.
Нетрудно показать, что вы получите при округлении тот же ре-
результат, что и выше, если прибавите к округляемому числу 0,5,0, после
чего отбросите его дробную часть без анализа цифры, записанной
после запятой. Например, пусть нам нужно округлить до целого 13,4]0.
В соответствии с описанным выше правилом, поскольку после запя-
запятой стоит цифра 4, мы просто отбрасываем ее, не меняя целой части
числа, и получаем в результате округления 1310. В то же время при
округлении 13,6,0 мы помимо отбрасывания дробной части должны
увеличить целую на 1, т. к. после запятой идет 6. Следовательно, ок-
округление 13,6,одает 14]0.
90
А теперь давайте попробуем для округления прибавлять к округ-
округляемому результату 0,510 с последующим отбрасыванием его дроб-
дробной части. 13,410 + 0,5,0 = 13,9,0. Отбросим дробную часть и получим
1310. Далее, 13,6,0 + 0,510 = 14,1,0. Отбросим дробную часть и получим
14,0. Как видите, описанный алгоритм округления, предполагающий
прибавление к округляемому числу 0,5,0 с последующим отбрасыва-
отбрасыванием дробной части, дает правильный результат.
Как этим можно воспользоваться для округления частного, по-
получаемого в результате выполнения подпрограммы D1V16 или ей ана-
аналогичной? Очень просто. Перед тем, как начать операцию деления,
прибавьте к делимому половину делителя (ее можно получить при
помощи подпрограммы RRAU16). После этого осуществите опера-
операцию деления и попросту отбросьте остаток. В результате вы получи-
получите правильно округленное целое частное.
И еще одно небольшое замечание. Если ваш делитель представ-
представляет собой степень двойки, то, как отмечалось выше, вместо опера-
операции деления достаточно произвести и сдвигов делимого на I разряд
вправо, где п — показатель степени двойки в делителе. При этом ок-
округление осуществляется сложением полученного таким образом
частного с флагом переноса CY, оставшимся после последнего сдви-
сдвига делимого вправо. Обосновать правомерность такого действия ав-
авторы предлагают вам самостоятельно.
КРАТКИЕ ВЫВОДЫ
Итак, мы рассмотрели, как реализуются подпрограммы основ-
основных действий беззнаковой целочисленной двухбайтной двоичной
арифметики — сложение, вычитание, умножение и деление. В ка-
какой-то степени рассмотренный материал можно назвать ключевым.
Во-первых, анализируемые далее подпрограммы беззнаковой цело-
целочисленной многобайтной арифметики, знаковой целочисленной
арифметики и арифметики с плавающей запятой основываются на
подпрограммах, рассмотренных в настоящей главе. Во-вторых,
рассмотрены различные варианты построения таких сложных под-
подпрограмм как умножение и деление. В-третьих, подробно рассмот-
рассмотрены вычислительные схемы арифметических операций, которые
являются общими для любых микроконтроллеров и микропроцес-
микропроцессоров. Как следствие последнего, эти схемы будут лежать в основе
аналогичных арифметических подпрограмм, которые некоторые из
читателей напишут для других микроконтроллеров с отличной от
х51 системой команд. И наконец, в-четвертых, рассмотренные под-
подпрограммы могут оказаться достаточными для очень большого
числа применений. Например, подавляющее большинство исполь-
91
зуемых сегодня в технике аналого-цифровых и цифро-аналоговых
преобразователей имеют разрядность, не превышающую 16. Сле-
Следовательно, и вводимые, и выводимые данные — двухбайтовые. Как
правило, и промежуточные результаты обработки вполне могут
обойтись 16-ю разрядами, хотя для начинающих это высказывание
может показаться весьма сомнительным. Но тем не менее, это так —
в большинстве измерительных приборов, разрабатываемых одним
из авторов настоящего материала, из арифметических подпрограмм
присутствуют только подпрограммы, рассмотренные в настоящей
главе и им аналогичные.
Теперь вкратце о том, чему же была посвящена настоящая глава.
В знакомой нам десятичной системе каждое число состоит из цифр,
имеющих 10 возможных значений: от 0 до 9. Каждая цифра числа
представляет собой степень десятки. Обычно мы не пишем степеней
десятки, они подразумеваются позициями цифр числа: самая правая
позиция соответствует единицам (т. е. 10°), следующая за ней — де-
десяткам (т. е. 10!), затем — сотням A02), тысячам A03) и т. д. до циф-
цифры, находящейся в самой левой позиции числа. Вообще, любое це-
целое неотрицательное число Д записанное п десятичными цифрами
с7,@</<и-1)как
D = dn-idn-r~d\d№
соответствует величине
V(D) = <*„_, • 10"-' + dn_2 • 10й-2 + ... + <f, - 10' + Ц, • 10°.
Такое представление чисел называется позиционным.
Таким образом, в десятичной системе счисления каждая позиция
имеет свой вес — определенную степень десяти. Число 10 называет-
называется основанием десятичной системы, поскольку величина 10 опреде-
определяет и количество цифр системы, и отношение весов соседних пози-
позиций цифр числа, т. е. величина 10 полностью определяет десятичную
систему (за исключением, конечно, начертания самих цифр, кото-
которые принято обозначать 0,1, 2, 3,4, 5,6, 7,8, 9).
В цифровой технике доминирует двоичная система — позици-
позиционная система счисления, основанием которой является число 2 (т. е.
система имеет всего две цифры: 0 и 1). Связано это в первую очередь
с тем, что для десятичной системы нужно представить каждую циф-
цифру каким-то уникальным электрическим сигналом, т. е. нужно уметь
генерировать 10 уникальных сигналов, по одному для каждой циф-
цифры. Кроме генерации различных сигналов для всех десяти цифр де-
92
сятичной системы еще нужно уметь различать эти сигналы и опре-
определять цифру по имеющемуся сигналу. Двоичная система более прак-
практична в цифровой технике, т. к. можно обойтись всего двумя разны-
разными сигналами для представления цифр: 0 и 1. Обычно нулю соответ-
соответствует уровень напряжения около О В, а единице — около +5 В. Оп-
Определить двоичную цифру по сигналу проще, чем десятичную, т. к.
выбор делается всего между двумя возможными сигналами, а это, в
свою очередь, повышает надежность системы и ее устойчивость к
помехам. Решение в пользу цифры 0 принимается, если напряжение
меньше 2,5 В, в противном же случае, если напряжение больше 2,5 В,
решение принимается в пользу цифры 1. Таким образом, существу-
существует простое правило определения цифры по сигналу и достаточно
большой диапазон изменения напряжения для каждой из двух цифр.
Двоичная цифра имеет особое название — бит. Аналогично рас-
рассмотренной выше десятичной системе любое число В, записанное и
двоичными цифрами Ь; @ < i < п-1) как
соответствует величине
V{B) = Ь„_, • 2"-' + bn_2 ¦ 2"-2 + ... + Ь, • 2' + b0 ¦ 2°.
Поскольку и двоичная, и десятичная система являются позицион-
позиционными и одинаковым образом организованными, арифметические опе-
операции в обеих системах выполняются в них по одним и тем же прави-
правилам. Например, сложение чисел (как десятичных, так и двоичных) —
это процесс последовательного сложения пар соответствующих цифр
с учетом переноса. Что бы мы ни складывали (разряды единиц, разря-
разряды десятков, разряды сотен, младшие биты, старшие биты и т. д.), опе-
операция сложения пары разрядов с учетом переноса остается неизмен-
неизменной независимо от позиции складываемых разрядов.
К счастью, складывать или вычитать побитно нам нет необходи-
необходимости. Микроконтроллер имеет инструкцию восьмибитового сложе-
сложения с учетом переноса ADDC, которая выполняет сложение аккуму-
аккумулятора А с указанным операндом. ADDC берет текущее значение
флага переноса CY из регистра PSW микроконтроллера; по оконча-
окончании ее выполнения в CY записывается результирующее значение
флага переноса, которое в свою очередь может стать входным для
следующего сложения. Таким образом, повторяя ADDC можно скла-
складывать не только восьмибитные числа, но и числа, состоящие из 16-
ти, 24-х и т. д. битов. Для этого нужно проделать ADDC для каждой
93
пары восьмибитных слагаемых, не потеряв при этом значений CY
между соседними инструкциями ADDC.
Поскольку при сложении младших 8-ми битов любых двух дво-
двоичных чисел CY должен быть равен 0, то его, конечно, можно просто
занулить перед началом сложения при помощи инструкции CLR С.
Но, есть и более удобное решение — инструкция ADD, заменяющая
две предыдущие. ADD работает так же как ADDC, только не прини-
принимает во внимание значение бита переноса (точнее работает так, как
если бы бит переноса всегда был равен 0).
Вычитание в микроконтроллерах семейства х51 в целом органи-
организовано аналогично сложению. Микроконтроллер имеет инструкцию
8-битового вычитания с учетом заема SUBB, которая выполняет вы-
вычитание из аккумулятора А указанного операнда. SUBB учитывает
текущее значение флага переноса СУ из регистра PSW микроконт-
микроконтроллера. По окончании выполнения инструкции SUBB CY модифи-
модифицируется. Таким образом, повторяя SUBB можно вычитать не толь-
только 8-битные числа, но и числа, состоящие из 16-ти, 24-х и т. д. битов.
Для этого нужно проделать SUBB для каждых восьмибитовых пар
уменьшаемого и вычитаемого, не потеряв при этом значений CY
между соседними инструкциями SUBB.
Инструкции SUB, которая бы работала как SUBB, но не принима-
принимала бы во внимание значение флага переноса, в нашем микроконт-
микроконтроллере нет (хотя во многих других есть), поэтому мы должны по
мере необходимости использовать инструкцию CLR С.
Для организации умножения микроконтроллеры семейства х51
располагают инструкцией MUL AB. С ее помощью можно не только
умножить друг на друга два восьмибитных числа, но также осуще-
осуществить умножение двух- и трехбайтовых чисел, произведя попарное
умножение друг на друга всех без исключения байт обоих множите-
множителей, а затем складывая частичные произведения с учетом позиций
их сомножителей.
Возможно также осуществить умножение друг на друга двух дво-
двоичных чисел без использования команды MUL AB. При этом умно-
умножение осуществляется путем сдвига множимого относительно накап-
накапливаемого произведения и последующего сложения одного с другим,
если соответствующий разряд множителя равен 1. Возможны четы-
четыре варианта сдвига множимого относительно произведения, вслед-
вследствие чего существуют четыре различные вычислительные схемы
двоичного беззнакового умножения, основанного на сдвигах и сло-
сложениях чисел. В зависимости от наличия тех или иных инструкций в
системе команд микроконтроллера можно выбрать наиболее эффек-
эффективную из этих вычислительных схем. Например, для МК семейства
94
х51 самой эффективной оказалась первая из рассмотренных выше
схем, в то время как для популярного некогда микропроцессора
8080 — третья. Однако подпрограммы, реализующие все эти вычис-
вычислительные схемы, сильно проигрывают в быстродействии подпрог-
подпрограммам умножения, использующим инструкции типа MUL А В, если
последние имеются в рассматриваемом микроконтроллере. Так, при
реализации умножения друг на друга двух двухбайтовых чисел под-
подпрограмма, использующая инструкцию MUL AB, оказывается в 7.. .8
раз более быстрой, чем подпрограммы, основанные на сложениях и
сдвигах операндов и частных произведений.
К сожалению, эффективной операции деления в микроконтрол-
лах семейства х51 нет. Поэтому операция деления в них организовы-
организовывается чисто программным образом — путем вычитаний и сдвигов
сомножителей и промежуточных результатов. Здесь также существует
несколько вычислительных схем, однако в отличие от умножения их
не четыре, а всего две (деление всегда начинается со старшего бита, в
то время как умножение можно организовать как со старшего, так и с
младшего бита).
Чаще всего в программах короткого деления используют схему
деления с восстановлением остатка — из делимого вычитают дели-
делитель, и если разность оказывается отрицательной, к ней прибавляет-
прибавляется делитель, который был вычтен на предыдущем шаге. В зависимо-
зависимости от знака вышеупомянутой разности определяется соответствую-
соответствующая цифра частного — единица, если разность не меньше нуля, и
ноль, если разность оказалась отрицательной и к ней пришлось при-
прибавлять только что вычтенный из делимого делитель.
Следует иметь ввиду, что частное, получаемое в результате деле-
деления целых беззнаковых чисел, не всегда является точным. Если дели-
делимое не делится на делитель нацело, то в результате деления у нас воз-
возникает целое частное и остаток, меньший, чем делитель. В зависимо-
зависимости от поставленной задачи программист должен решить, что делать
с полученным остатком — попросту отбросить его или же округлить
результат с его учетом. В последнем случае нужно написать соответ-
соответствующую подпрограмму округления.
При использовании подпрограмм деления программист должен
исключить переполнение частного и деление на нуль. Как уже упо-
упоминалось, переполнение и-разрядного частного возможно при вы-
выполнении деления 2п-разрядного делимого на «разрядный делитель.
Фактически это означает, что получаемое частное нельзя представить
в «-разрядном формате, и ему требуется большее, чем и, количество
разрядов. Например, если вы попытаетесь разделить 111100002 на
10112, то получите 101012 с остатком 10012 B40 : 11 = 21 и 9 в остат-
95
ке) — переполнение 4-разрядного частного, для его представления
нужно пять разрядов. Очевидно, это переполнение имеет место в
случае, когда число в старших я разрядах делимого больше или рав-
равно делителю A1112 > 10112). Такое переполнение нужно выявлять
программным способом и блокировать дальнейшее вычисление
вследствие его некорректности. Избежать переполнения можно при
увеличении разрядности частного до разрядности делителя с одно-
одновременным увеличением в два раза количества циклов деления.
Как уже было отмечено, помимо выявления переполнения при
делении чисел необходимо выявлять ситуацию деления на нуль. От-
Отметим, что при анализе переполнения при делении целых чисел си-
ситуацию деления на нуль можно выявлять автоматически. Если она
имеет место, необходимо блокировать дальнейшие вычисления и
каким-либо образом сообщить вызывающей программе (читай —
программисту) о возникшей ситуации и ее причинах.
Глава 2
ПРЕОБРАЗОВАНИЕ БЕЗЗНАКОВЫХ
ЦЕЛЫХ ЧИСЕЛ
В первой главе мы отметили, что микроконтроллеры и микропро-
микропроцессоры оперируют с двоичными числами потому, что двоичное
представление более естественно и легкореализуемо для цифровых
систем, а также обеспечивает максимальную помехозащищенность
передаваемой цифровой информации. Далее мы рассмотрели, как
реализуется двоичное сложение, вычитание, умножение и деление,
и привели примеры подпрограмм для сложения, вычитания, умно-
умножения и деления двоичных 16-битовых (или двухбайтовых) чисел.
Мы убедились, что все эти операции могут быть реализованы на
основе команд сложения, вычитания и сдвига, в результате чего
любой микроконтроллер, имеющий в своем наборе команд эти ин-
инструкции, может легко выполнить все перечисленные арифмети-
арифметические операции.
Однако мы, люди, привыкли оперировать с десятичными числа-
числами. Мы легко запомним, например, десятичное число 15610, в то вре-
время как его двоичный эквивалент 100111002 чрезвычайно сложен для
моментального считывания с дисплея и последующего запоминания
или для безошибочного ввода его в вычислительную систему. Запом-
Запомнить двоичное представление первых пятнадцати-двадцати десятич-
десятичных чисел среднему человеку обычно удается, но необходимость по-
постоянно вводить в систему и считывать из нее более чем 6-разряд-
6-разрядные двоичные числа угнетающе действует на умственные способно-
способности оператора. Альтернативой является использование так называе-
называемой двоично-десятичной системы. Двоичное число преобразовыва-
преобразовывается в десятичное, однако каждая его десятичная цифра представля-
представляется комбинацией все тех же двоичных цифр, понятных нашим де-
97
шифраторам, отображающим результат на дисплее. В итоге система
представляет на дисплее результаты вычислений в привычной нам
десятичной форме, но на входах дешифраторов присутствуют все те
же нолики и единички. Если для кого из читателей сказанное не со-
совсем понятно, обратитесь к четвертой главе первого тома настоящей
книги — там подробно показано, как это осуществляется на практи-
практике.
Но вернемся к двоично-десятичной системе. Она является так
называемой смешанной системой счисления, в которой каждая цифра
одной (в данном случае десятичной) системы представляется фик-
фиксированным числом цифр другой (двоичной) системы. Минималь-
Минимальное количество двоичных разрядов, необходимое для представления
любой десятичной цифры, равно четырем, поэтому каждая десятич-
десятичная цифра кодируется четырехбитным двоичным числом. Между
десятичными числами и их четырехбитными двоичными эквивален-
эквивалентами устанавливается взаимно-однозначное соответствие. Возмож-
Возможны различные варианты установления этого соответствия, порожда-
порождающие различные двоично-десятичные коды с теми или иными свой-
свойствами. В нашей книге мы будем пользоваться одним из наиболее
распространенных кодов — кодом с естественными весами 8,4, 2, 1,
в котором десятичные цифры кодируются их естественными двоич-
двоичными эквивалентами: 010—это 00002,110—это 00012,2,0—это 00102,
310—этоООП2,410 —это01002,5ш —этоОЮ12,610 —это01102,7ш—
это 01112, 810 — это 10002 и, наконец, 9,0 — это 10012. Этот код пре-
преимущественно применяется при вводе/выводе данных вкупе с
соответствующими преобразованиями из одной системы счисления
в другую, но, как мы далее убедимся, он может использоваться и не-
непосредственно при обработке данных.
Настоящая глава посвящена преобразованию двоичных чисел в
двоично-десятичные и обратному к нему (из двоично-десятичных в
двоичные). В принципе, можно было бы ограничиться рассмотрени-
рассмотрением всего одной подпрограммы преобразования из двоичного в дво-
двоично-десятичное представление и одной — из двоично-десятичного
в двоичное, как это было сделано в первом томе. Но на практике
алгоритмов преобразования может быть довольно много, и было бы
полезно ознакомиться хотя бы с несколькими из них — это расши-
расширит кругозор читателей и позволит им пополнить набор практичес-
практических навыков составления программ. Поэтому в настоящей главе мы
предложим вам пять алгоритмов преобразования 16-битных двоич-
двоичных чисел в двоично-десятичные и два — из двоично-десятичного в
двоичное и, естественно, подпрограммы, их реализующие. Так что
наберитесь терпения и познакомьтесь с тем, сколь широки могут быть
98
возможности программиста при решении даже таких несложных за-
задач, как преобразования чисел между системами счисления.
И последнее, о чем хотелось бы сказать, прежде чем мы перейдем
к подпрограммам преобразования. Давайте договоримся, что если
около числа не указано основание его системы счисления, то это чис-
число является десятичным. Иными словами, число 10—это 1010, ачисло
102 — это двойка, записанная в двоичной системе. При этом в ряде
случаев мы все же будем ставить десятичное основание справа от
числа — это иногда позволяет избежать ошибок, связанных с невни-
невнимательностью. Кстати, мы и раньше придерживались этого правила,
хотя в явном виде его до сих пор не формулировали.
ПРЕОБРАЗОВАНИЕ 16-БИТНЫХ ДВОИЧНЫХ ЧИСЕЛ
В ДВОИЧНО-ДЕСЯТИЧНЫЕ
Начнем с преобразования 16-битных двоичных чисел в двоично-
десятичные. Самым простым алгоритмом подобного преобразова-
преобразования является алгоритм, описанный в первой главе второго тома на-
нашей книги. Исходное двоичное число в полном соответствии с рас-
рассмотренными выше правилами двоичного деления разделим на
10102=10,0. Частное от деления сохраним для последующих делений,
а вот остаток (который, кстати, может быть и нулевым) запомним —
как будет ясно чуть позже, он является младшей цифрой искомого
десятичного числа. Почему десятичного? Да потому, что мы факти-
фактически преобразовываем двоичное число в десятичное, но результи-
результирующие цифры последнего будут представлены двоичными числа-
числами. Если для кого из читателей это все еще не очевидно, вернитесь к
материалу, изложенному в начале текущей главы.
Итак, первый остаток от деления исходного числа на 1010 — это
младший (нулевой) разряд искомого десятичного числа. Идем даль-
дальше. Если частное от упомянутого деления меньше 1010, то это част-
частное и есть первый (старший) разряд искомого числа и на этом пре-
преобразование должно быть завершено. Если же частное не меньше 10,0,
то снова разделим его на 1010. Полученное (теперь уже второе по сче-
счету) частное опять сохраним для последующих делений, а остаток, как
несложно догадаться, запомним как первый разряд искомого деся-
десятичного числа.
Те, кто уже сообразил, как происходит преобразование, потерпи-
потерпите — я опишу еще один цикл последовательности преобразования,
после которого оно должно стать понятным уже для всех читателей.
Если частное от второго деления меньше 10,0, то это частное и есть
второй (в данном случае старший) разряд искомого числа и на этом
преобразование должно быть завершено. Если же частное не меньше
99
Ю10, то снова разделим его на 10,0. Полученное (теперь уже третье по
счету) частное опять сохраним для последующих делений, а остаток,
как несложно догадаться, сохраним как второй разряд искомого де-
десятичного числа.
Рассказывать о том, как в четвертый и в пятый раз нужно делить
полученные частные на 10ш и сохранять остатки, не буду. Но для боль-
большей очевидности подкреплю сказанное следующим простым при-
примером. Пусть нам нужно преобразовать двоичное число 100110100102
в десятичное (скажу заранее, что в результате мы должны получить
1234,0). Разделим исходное двоичное число на 10102=10,0, получим
частное, равное И110112 A23Ш), и 410 в остатке. Посмотрите внима-
внимательно, совпадает ли этот первый остаток с младшим разрядом чис-
числа 123410, которое должно получиться в результате преобразования?
Если совпал, идем далее.
Разделим 11110112 A23Ш) на 10,0. Если вы правильно выполните
деление, то получите частное, равное 11002 A210) и 31о в остатке. Вот
вы и получили следующий (первый) разряд результата преобразо-
преобразования. Далее, разделив 11002 A210) на 1010, вы получите единичное
частное и двойку в остатке. Двойка, как нетрудно сообразить, это вто-
второй разряд искомого десятичного числа, а единица, поскольку она
уже меньше 1010, — это третий, старший разряд.
Почему остатки от последовательных делений исходного двоич-
двоичного числа на 10,0 дают в итоге его десятичный эквивалент? Ответ
становится очевидным, если вы внимательно посмотрите на остатки
и частные в десятичной форме, которые я умышленно привел в рас-
рассмотренном выше примере. Остаток отделения десятичного 1234 на
1010 — это ведь четверка, младший разряд 123410! А остаток отделе-
отделения десятичного 123 на десятку — тройка, следующий разряд! Наде-
Надеюсь, после сказанного алгоритм преобразования стал очевидным те-
теперь уже для всех читателей.
Именно этот алгоритм реализован в подпрограмме B16BCDA.
Исходное 16-разрядное двоичное число расположено в регистрах R5
и R4 (как обычно, старший байт в R5, младший в R4). Деление осу-
осуществляется при помощи подпрограммы DIV16, описанной в пер-
первой главе, расположенный в регистрах R3 и R2 делитель равен 10,0.
Остатки от деления, т. е. цифры искомого результата преобразова-
преобразования но мере получения сохраняются в стеке, каждый цикл нахожде-
нахождения очередной цифры сопровождается увеличением на 1 содержи-
содержимого регистра R1. После того, как будет найдена старшая цифра, в R1
будет храниться число цифр, полученных в результате преобразова-
преобразования, т. е. разрядность полученного в результате десятичного числа. В
дальнейшем содержимое Rl используется для правильной выгрузки
100
полученных в результате преобразования цифр из стека в пятибай-
пятибайтовый буфер, адрес младшего байта которого храниться в регистре
R0. Остальное, надеюсь, очевидно и не требует подробных объясне-
объяснений.
Замечу, что выше мы определяли двоично-десятичные числа как
десятичные числа, каждая из цифр которого представлена ее четы-
четырехбитным двоичным эквивалентом. В подпрограмме B16BCDA де-
десятичные цифры представлены в буфере своими восьмибитными
двоичными эквивалентами. Это чуть расходится с приведенным
выше определением двоично-десятичного числа, но старшие 4 раз-
разряда каждого из упомянутых восьмибитных двоичных эквивален-
эквивалентов — незначащие нули, так что в дальнейшем при необходимости
их можно отбросить, и полученный результат будет соответствовать
введенному определению.
B16BCDA:
Подпрограмма преобразования целого 16-ти битного двоичного
числа без знака в двоично-десятичное
Вход:
R5R4 - 16-ти разрядное двоичное число
R0 - адрес 5-ти байтового буфера длр сохраненир результата
Выход:
R1 - количество цифр в буфере (от 1 до 5)
буфер - содержит десятичные цифры (например: байты 1, 3 если
число в R5R4 было равно 1101 двоичн. A3 десятичн.)
Используемые регистры:
R7, R6. R5, R4. R3, R2, А. В
Требует свободных байт в стеке:
2+2+4=8
Использует подпрограммы:
0IV16
MOV R1, »0 ; счетчик количества значащих цифр
MOV R3, #0
MOV R2. #10 ; R3R2 = 10 (делитель)
MOV R7, «0
B16BCDA 1:
101
MOV R6, #0 ; R7R6 = О (СТС делимого)
LCALL DIV16 ; R6 = остаток от деления (очередная цифра числа)
MOV A, R6
PUSH ЛСС
INC R1
MOV
ORL
JNZ
MOV
еще одна
цифра в стеке
A. R5
A. R4
B16BCDAJ
B, R1
продолжать делить пока частное > О
В16ВС0А_2:
POP
MOV
INC
DJNZ
MOV
CLR
SUBB
MOV
ACC
@RD, A
RO
; вытащить цифру из стека
; сохранить цифру в памяти
B. B16BCDA_2
A, RO
С
A, R1
RD, A
; восстановить адрес в R0
RET
Рис. 55. Подпрограмма преобразования 16-битных двоичных чисел
в двоично-десятичные (вариант 1)
Рассмотренная подпрограмма имеет один недостаток — для де-
деления на 10 она использует довольно медленную подпрограмму
DIV16. Увы, микроконтроллеры семейства х51 не обладают аппарат-
аппаратными средствами для быстрого деления четьфехбайтового числа на
двухбайтовое или даже двухбайтового на однобайтовое. В своей сис-
системе команд они имеют лишь весьма ограниченную по своим воз-
возможностям инструкцию целочисленного деления DIV АВ, которая
осуществляет деление содержимого аккумулятора на содержимое
регистра В. После выполнения инструкции целая часть частного ос-
остается в аккумуляторе, а регистр В содержит остаток от деления.
Однако сколь бы эта команда не была глупой и несовершенной
на первый взгляд, тем не менее она может иногда оказаться полез-
полезной. Автор настоящих строк был немало удивлен тем, что с помо-
102
щью этой, бестолковой с его точки зрения, инструкции, тем не ме-
менее, можно осуществить быстрое целочисленное деление числа лю-
любой длины на число из диапазона от 1 до 15. В качестве примера рас-
рассмотрим подпрограмму DIV_R5R4_10, осуществляющую быстрое
деление на 10 числа, содержащегося в регистрах R5 и R4. Как нетруд-
нетрудно догадаться, эту подпрограмму можно использовать вместо DIV16
в подпрограмме преобразования 16-битных двоичных чисел в дво-
двоично-десятичные, аналогичной приведенной на рис. 55.
0IV_R5R4_10:
Подпрограмма деления целого 16-ти битного двоичного
числа без знака на основание десятичной системы 10
Вход:
R5R4 - 16-ти разрядное двоичное число без знака
Выход:
R5R4 - частное от деления на 10
В - остаток от деленир на 10
Используемые регистры:
А
Требует свободных байт в стеке:
2
MOV
ANL
SWAP
MOV
OIV
SWAP
хсн
SWAP
ANL
ORL
SWAP
MOV
DIV
ORL
MOV
A,
A,
A
B,
AB
A
A,
A
A,
A,
A
B,
AB
A.
R5,
R5
ttOFOH
#10
R5
ttOFOH
В
#10
R5
A
MOV A. R4
103
ANL
ORL
SWAP
MOV
DIV
SWAP
XCH
SWAP
ANL
ORL
SWAP
MOV
DIV
ORL
MOV
A,
A.
A
B.
AB
A
A.
A
A,
A.
A
B,
AB
A,
R4,
ttOFOH
В
«10
R4
#OFOH
В
»10
R4
A
RET
Рис. 56. Подпрограмма быстрого деления двухбайтового числа на 1О1О
Для того чтобы понять, как можно осуществить подобное деле-
деление при помощи инструкции DIV AB, рассмотрим следующее пре-
преобразование. Как мы договорились, делимое находится в регистрах
R5 и R4 и представляется числом
(R5H) • 4096 + (R5L) ¦ 256 + (R4H) ¦ 16 + (R4L), B3)
где заключенные в скобки (R5H), (R5L), (R4H) и (R4L) — это шест-
шестнадцатиричные цифры, хранящиеся в соответствующих тетрадах.
Преобразуем первое слагаемое в выражении B3) следующим об-
образом:
(R5H) • 4096 = [(R5H)/10] • 10 • 4096 +
+ {(R5H)-[(R5H)/10] • 10} • 4096, B4)
где заключенная в квадратные скобки дробь [(R5H)/10] есть ничто
иное, как целое частное от деления (R5H) на 10, а число в фигурных
скобках — остаток от этого деления (в начале книги мы договарива-
договаривались, что если около числа не указано основание его системы счисле-
счисления, то это число является десятичным, т. е. число 10 — это 1010).
104
Если теперь мы осуществим операцию целочисленного деления
числа (R5H)-4096 на 10 при помощи инструкции DIV АВ, то полу-
получим следующее:
(R5H) • 4096/ 10 = ([(R5H)/10] - 4096 ,
{(R5H) - [(R5H)/10] • 10} • 4096 ), B5)
где по-прежнему [(R5H)/10] есть ничто иное, как целое частное от
деления (R5H) на 10, a {(R5H) - [(R5H)/10] - 10} • 4096— остаток от
этого деления.
Итак, мы осуществили деление на 10 первого слагаемого выражения
B3). У нас есть частное, соответствующее весу 4096 (это [(R5H)/1OJ), и
остаток {(R5H) - [(R5H)/10] -10}, который на 10 уже не делится, но
отбросить его мы не можем. Как же быть? Очень просто. Заметьте, что
в выражение B3) этот остаток входит с весовым коэффициентом 4096
(т. е. попросту говоря, перед суммированием мы должны умножить
его на 4096). Но 4096 — это 16 ¦ 256. Иными словами, число с весовым
коэффициентом 4096 эквивалентно этому же числу, умноженному на
16, но имеющему весовой коэффициент 256. Раз так, то остаток {(R5H)
- [(R5H)/10] • 10} нужно не отбрасывать, а умножить на 16 и сложить с
(R5L), и эту сумму взять в B3) с весом 256:
(R5H) • 4096 + (R5L) - 256 + (R4H) - 16 + (R4L) =
[(R5H)/10]-10-4096 +
+ 16 ¦ {(R5H) - [(R5H)/10] - 10} ¦ 256 + (R5L) • 256 +
+ (R4H) • 16 + (R4L). B6)
Как видите, остаток, нас беспокоивший, попросту нужно умно-
умножить на 16 и сложить с цифрой, входящей в число с более низким
весовым коэффициентом.
Итак, что мы с вами сделали? Мы взяли первое слагаемое выра-
выражения B3), входившее в него со старшим весовым коэффициентом
4096, и разбили его на два числа, одно из которых, [(R5H)/10] ¦ 10,
делится нацело на наш делитель 10, а другое, которое уже не делится,
мы умножили на 16 и прибавили ко второму слагаемому, входяще-
входящему в B3) с весом 256. Если ввести обозначение
(R5LS) = 16 • {(R5H) - [(R5H)/10] - 10} + (R5L), B7)
105
то B6) можно записать следующим образом:
(R5H) • 4096 + (R5L) • 256 + (R4H) • 16 + (R4L) =
[(R5H)/10] • 10 • 4096 + (R5LS) • 256 + (R4H) • 16 + (R4L). B8)
Как видите, первое слагаемое кратно 10. Теперь проделаем то же
самое со вторым слагаемым (R5LS) • 256. Опуская промежуточные
вычисления, запишем, что получится в результате этого преобразо-
преобразования:
(R5H) • 4096 + (R5L) ¦ 256 + (R4H) • 16 + (R4L) =
= [(R5H)/10] • 10 - 4096 +
+[(R5LS)/10] • 10 • 256 + (R4HS) - 16 + (R4L), B9)
где (R4HS) = 16 • {(R5LS) - [(R5LS)/10] • 10} + (R4H). C0)
Если вы еще не догадались, к чему мы ведем, объясню. Мы ста-
стараемся представить исходное выражение B3) не просто в виде сум-
суммы цифр с весовыми коэффициентами 4096, 256, 16 и 1, а в виде
суммы чисел с этими же весовыми коэффициентами, но кратных
делимому, т. е. 10. Тогда деление на 10 представленного в виде вто-
второй суммы исходного числа станет предельно простым. Вначале
разделим на 10 первое слагаемое (оно равно [(R5H)/10] • 10 • 4096
и разделится нацело) и возьмем частное от этого первого проме-
промежуточного деления старшей цифрой окончательного частного,
имеющей весовой коэффициент 4096. Далее разделим на 10 вто-
второе слагаемое (оно равно [(R5LS)/10] • 10 • 256 и также разделится
нацело) и возьмем частное от этого второго промежуточного де-
деления второй слева цифрой окончательного частного, имеющей
весовой коэффициент 256. Затем последует черед третьего слагае-
слагаемого, которое к этому моменту также должно быть кратно 10, и
после этого — четвертого. Остаток от деления последнего и будет
окончательным остатком.
Все вышеописанное реализовано в подпрограмме DIV_R5R4_10,
приведенной на рис. 56. Единственное различие между программой
и приведенным выше описанием принципа ее работы заключается в
том, что в вышеприведенных формулах мы осуществляли преобра-
преобразования, дробящее каждое из слагаемых на два сомножителя, один
из которых равен 10, дабы после этого наглядно увидеть, что оста-
106
нется после деления упомянутого слагаемого на эту десятку, а рас-
рассматриваемая подпрограмма сразу осуществляет деление на 10 без
каких-либо предварительных наглядных для нас преобразований,
после чего умножает остаток от деления на 16 командой SWAP А и
складывает со следующей цифрой. Преобразования B3)—B9) нам
понадобились для того, чтобы как можно нагляднее объяснить, как
же работает подпрограмма DIV_R5R4_10.
После того, как мы проанализировали принцип быстрого де-
деления с помощью команды целочисленного деления однобайто-
однобайтового числа на однобайтовое с получением целого частного и ос-
остатка, целесообразно сделать два замечания. Первое заключается
в следующем. Как мы уже убедились, имея команду целочислен-
целочисленного деления однобайтового (восьмибитного) числа на такое же
восьмибитное с получением целого частного и остатка, мы в со-
состоянии организовать деление числа любой длины на любое не-
ненулевое четырехбитное число. Если вы располагаете в системе ко-
команд вашего микроконтроллера командой целочисленного деле-
деления двухбайтового (шестнадцатибитного) числа на такое же шес-
шестнадцатибитное с получением целого частного и остатка, вы смо-
сможете организовать быстрое деление числа любой длины на любое
ненулевое восьмибитное число. Далее по аналогии, если вы рас-
располагаете в системе команд вашего микроконтроллера командой
целочисленного деления четырехбайтового (тридцатидвухбитно-
(тридцатидвухбитного) числа на такое же тридцатидвухбитное с получением целого
частного и остатка, вы сможете организовать быстрое деление чис-
числа любой длины на любое ненулевое шестнадцатибитное число и
т. д., хотя вероятность встретить микроконтроллер с командами
шестидесятичетырехбитного деления пока еще крайне низка.
Принцип организации такого деления описан выше.
И второе, если мы разделим на 10 число, равное, например 54321,
при помощи подпрограммы DIV16, это потребует 484 мкс у стандар-
стандартного х51, работающего на частоте 12 МГц. Подпрограмма
DIV_R5R4_10 потребует на это 51 мкс. Как часто говорят в последнее
время, почувствуйте разницу.
После того, как мы разобрались с DIV_R5R4_10, давайте вспом-
вспомним, зачем она была нам нужна. Мы хотели с ее помощью уско-
ускорить работу первого варианта подпрограммы преобразования 16-
битных двоичных чисел в двоично-десятичные, приведенной на
рис. 55. Получившаяся в результате подпрограмма приведена на
рис. 57. Каких-либо пояснений она не требует. Если вы разобра-
разобрались с подпрограммами, приведенными на рис. 55 и 56, вам в ней
все должно быть ясно.
107
B16BCDE:
Подпрограмма преобразования целого 16-ти битного двоичного
числа без знака в двоично-десятичное
Вход:
R5R4 - 16-ти разрядное двоичное число
R0 - адрес 5-ти байтового буфера длр сохранения результата
Выход:
R1 - количество цифр в буфере (от 1 до 5)
буфер - содержит десятичные цифры (например: байты 1. 3 если
число в R5R4 было равно 1101 двоичн. A3 десятичн.)
Используемые регистры:
R5, R4, А. В
Требует свободных байт в стеке:
2+2+4=8
Использует подпрограммы:
OIV R5R4 10
MOV R1, #0 ; счетчик количества значащих цифр
B16BCDEJ:
LCALL DIV_R5R4_10 ; В = остаток от деления (очередная цифра числа)
PUSH
INC
MOV
ORL
JNZ
В
R1
A.
A.
;
:
R5
R4
B16BCDE 1
еще одна
цифра в стеке
; продолжать делить пока частное > 0
MOV В, R1
B16BCDE_2:
POP
MOV
INC
DJNZ
MOV
CLR
SUBB
ACC ;
©R
R0
B.
A.
С
A,
0, A ;
B16BCDE
RO
R1
вытащить
сохранить
_2
цифру из стека
цифру в памяти
108
MOV RO, A ; восстановить адрес в RO
RET
Рис 57. Подпрограмма преобразования 16-битных двоичных чисел в двоично-
десятичные (вариант 2)
Теперь рассмотрим третий вариант подпрограммы преобразова-
преобразования 16-битных двоичных чисел в двоично-десятичные. Исходное
число будет по-прежнему храниться в регистрах R5 и R4: старшие 8
бит в R5, младшие — в R4. Результат преобразования будет распола-
располагаться в пятибайтовом буфере с адресом, хранимом в регистре R0.
Понять принцип действия рассматриваемой подпрограммы луч-
лучше на конкретном числовом примере. Пусть нам нужно преобразо-
преобразовать 0FF00H (его десятичный эквивалент — 65280) в двоично-деся-
двоично-десятичное число.
Ответьте пожалуйста, сколько раз подряд из числа, принадле-
принадлежащего диапазону от 60000 до 69999, можно вычитать 10000, чтобы
при этом остаток был больше или равен нулю? Ровно 6 раз, не правда
ли. Когда вы попытаетесь это сделать в седьмой раз, у вас получит-
получится отрицательный результат. Соответственно, из числа, лежащего в
диапазоне 20000...29999 можно до получения нулевого или поло-
положительного остатка вычесть 10000 не более 2-х раз, для числа из
диапазона 40000...49999 — не более 4-х раз и т. д. Понимаете, к чему
я клоню?
Для тех, кто еще не догадался, объясню. Вычитая раз за разом из
преобразуемого числа 10000, определим, сколько раз эти 10000 со-
содержатся в нем — это будет разряд десятков тысяч. Далее будем из
остатка точно также вычитать 1000 и так определим, сколько раз в
нем содержится 1000 — это будет разряд тысяч. И так далее.
Отсюда, алгоритм преобразования должен выглядеть следующим
образом:
А) Вначале разряд десятков тысяч преобразуемого числа прини-
принимаем равным 0.
Б) После этого из преобразуемого числа нужно вычесть 10000 и
проверить, остаток больше или равен 0 или нет. Если да, то разряд
десятков тысяч увеличиваем на 1 (т. е. после первого удачного вычи-
вычитания он будет равен 1) и снова повторяем действия, сформулиро-
сформулированные в пункте Б). После второго удачного вычитания разряд де-
десятков тысяч будет равен 2, после третьего — 3 и т. д. Рано или по-
поздно, но после какого-то вычитания остаток станет отрицательным.
Кстати, если преобразуемое число не более 9999, он отрицательным
109
будет уже после первого вычитания. В этом случае переходим к сле-
следующему пункту.
В) Оставляем неизменным разряд десятков тысяч, прибавляем к
полученному отрицательному остатку 10000 и запоминаем его и най-
найденный разряд для дальнейшего преобразования.
В рассматриваемом нами примере с преобразованием числа
OFFO0H перед седьмым вычитанием остаток будет равен 5280, а раз-
разряд десятков тысяч — 6. После 7-го вычитания в остатке остается (про-
(простите за повторение) минус 4720. Отрицательный остаток сигнали-
сигнализирует нам, что с вычитаниями мы слегка переборщили и что надо
действовать по пункту В), т. е. прибавить к этому остатку 10000, по-
получив при этом исходные 5280, запомнить это число и то, что разряд
десятков тысяч в 0FF00H равен 6. А далее, как нетрудно догадаться,
переходим к нахождению разряда единиц тысяч, который определя-
определяется по тому же алгоритму.
Г) Вначале разряд единиц тысяч преобразуемого числа принима-
принимаем равным 0.
Д) После этого из преобразуемого числа нужно вычесть 1000 и
проверить, остаток больше или равен 0 или нет. Если да, то разряд
единиц тысяч увеличиваем на 1 (т. е. после первого удачного вычи-
вычитания он будет равен 1) и снова повторяем действия, сформулиро-
сформулированные в пункте Д). После второго удачного вычитания разряд еди-
единиц тысяч будет равен 2, после третьего — трем и т. д.
Когда после какого-то вычитания остаток станет отрицательным,
переходим к следующему пункту.
Е) Оставляем неизменным разряд единиц тысяч, прибавляем к
остатку 1000 и запоминаем его и полученный разряд для дальнейше-
дальнейшего преобразования.
В рассматриваемом нами примере с 0FF00H мы начали вычитать
тысячи из 5280, доставшегося нам в наследство от этапа определения
разряда десятков тысяч. Перед шестым вычитанием остаток будет
равен 280, а разряд единиц тысяч — 5. После 6-го вычитания в остат-
остатке будет минус 720. Отрицательный остаток опять сигнализирует нам,
что с вычитаниями мы переборщили и что надо действовать по пун-
пункту Е), т. е. прибавить к этому отрицательному остатку 1000, полу-
получив при этом исходные 280, запомнить это число и то, что разряд
единиц тысяч в 0FF00H равен 5. А далее, как очевидно, можно перей-
перейти к нахождению разряда сотен.
Описывать, как мы должны из 280 раз за разом вычитать 100, я не
буду — это должно быть уже очевидным для всех, кто читает настоя-
настоящие строки. Если что-то непонятно, еще раз внимательно прочтите
содержимое последних восьми абзацев. После определения разряда
110
сотен определим разряд десятков, а остаток в результате определение
десятков и будет разрядом единиц. Вот, собствешю, и весь алгоритм.
Числа 10000,1000,100 и 10 для корректного выполнения рассмат-
рассматриваемой подпрограммы должны храниться в оперативной памята
с адреса TABDEC, и помещены они туда должны быть приводимой
ниже подпрограммой INIT.
B16BCDB:
Подпрограмма преобразования целого 16-ти битного двоичного
числа без знака в двоично-десятичное
Вход:
R5R4 - 16-ти разрядное двоичное число
R0 - адрес 5-ти байтового буфера для сохранения результата
Выход:
R1 - количество цифр в буфере (всегда 5)
буфер - содержит десятичные цифры (например: байть 0, 0, 0.
1, 3 если число в R5R4 было равно 1101 двоичн.
A3 десятичн.)
Используемые регистры:
R5. R4. R3, R2, А
Требует свободных байт в стеке:
2
MOV
MOV
B16BCDBJ:
MOV
B16BCDB_2:
CLR
MOV
SUBB
MOV
MOV
INC
SUBB
DEC
MOV
R3,
R1.
R2.
С
A.
A.
R4,
A,
R1
A,
R1
R5.
#4 ; счетчик количества значащих цифр
ttTABDEC ; R1 указывает на таблицу со степенями де«
»0 ; R2 - текущее значение цифры
R4
№1
А
R5
@R1
А ; вычли из числа степень десятки
11
JC B16BCDB_3 ; если был заем, текущая цифра готова
INC R2 ; не было заема, добавили 1 к текущей цифре
SJMP B16BCDB_2 ; продолжить вычитание этой же степени десятки
B16BCDB_3:
MOV A. R4
ADD A, 8R1
MOV R4, А
MOV A, R5
INC R1
ADDC A, W\
MOV R5, A : восстановили число после вычитания с заемом
MOV A, R2 ; сохранили текущув
MOV @R0. Л ; цифру в памяти
INC R0
INC R1 ; перешли к следувщей (меньшей) степени десятки
DJNZ R3, В16ВС0В_1 ; повторяем цикл для следувщей цифры
сохранить ее
TABDEC
INIT:
INIT_1:
112
MOV
MOV
MOV
ADD
MOV
MOV
RET
.EOU
MOV
MOV
MOV
A, R4 ; в разряде единиц уже го
@R0, A ;
A, R0
А, #-4
R0, А ; восстановили адрес в R0
R1, #5 ; R5 - число цифр
ОЗОН
DPTR. «ROMTABOEC
RO. ttTABDEC
R1. «ROMTABDECEND-ROMTABDEC
CLR
MOVC
MOV
INC
INC
DJNZ
RET
ROMTABDEC:
.OW
.DW
.OW
.DW
ROMTABDECENO:
A
A, gA+OPTR
№0, A
DPTR
RO
R1, INIT_1
10000
1000
100
10
Рис. 58. Подпрограмма преобразования 16-битных двоичных чисел в двоично-
десятичные (вариант 3)
Если вы думаете, что описанный выше алгоритм — самый про-
простой, и проще не бывает, вы ошибаетесь. Бывает. Наверное, самый
простой алгоритм — тот, который реализован в подпрограмме
B16BCDC В его основе лежит способность микроконтроллеров се-
семейства х51 (равно как и многих других) напрямую корректно осу-
осуществлять сложение двоично-десятичных чисел.
Реализуется это сложение следующим образом. В аккумуляторе
должно располагаться двоично-десятичное число — первое слагае-
слагаемое. Второе двоично-десятичное число может располагаться в одном
из регистров, внутренней или внешней оперативной памяти или быть
непосредственно адресуемо командой ADD (ADDC). Сразу после вы-
выполнения команды сложения должна стоять команда десятичной кор-
коррекции аккумулятора DA А, которая при необходимости модифи-
модифицирует аккумулятор таким образом, чтобы результат сложения был
правильным.
Например, если аккумулятор содержал 37Н, а регистр R1 —
48Н, то при их суммировании с помощью команды ADD в акку-
аккумуляторе будет 7FH, а после выполнения команды DA А — 85Н,
что является двоично-десятичным представлением числа 85|0. А
что должно было получиться при сложении 37H и 48,0? Не те ли
самые 85|О?
Более подробное описание команды DA А можно найти в пер-
первом томе, в главе, посвященной системе команд. Мы же здесь огра-
ограничимся лишь напоминанием, что эту команду можно располагать
только сразу после команд сложения ADD/ADDC; помещенная после
113
любой другой команды, DA А ни коим образом не меняет ни содер-
содержимого аккумулятора, ни какого-либо другого регистра, ячейки па-
памяти или флага.
Теперь о том, как работает подпрограмма B16BCDC. Б начале под-
подпрограммы мы зануляем регистры Rl, R2 и R3, в которых после завер-
завершения преобразования будет находиться искомый результат. В ходе вы-
выполнения подпрограммы мы раз за разом уменьшаем исходное двоич-
двоичное число, хранимое в R5R4, на J, и если после уменьшения содержимое
R5R4 не стало меньше 0, то прибавляем эту единицу к двоично-деся-
двоично-десятичному числу в R1R2R3 с последующей коррекцией аккумулятора при
помощи команды DA А после каждого суммирования. Как только со-
содержимое R5R4 станет меньше 0, это будет означать, что все преобразу-
преобразуемое число мы как бы перегнали по единичке из двоичного представле-
представления в R5R4 в двоично-десятичное в R1R2R3. Иными словами, в ходе
преобразования мы уменьшали исходное число в двоичном представ-
представлении на единичку, затем прибавляли эту единичку к числу в двоично-
десятичном представлении (с последующей десятичной коррекцией ак-
аккумулятора) и так до тех пор, пока исходное число не уменьшилось до 0.
Вот и весь алгоритм. Не правда ли, оригинально?
Правда, платой за простоту алгоритма является чрезвычайно
большое время выполнения подпрограммы при преобразовании
больших чисел. Так что практическая ценность этой подпрограммы
довольно мала, и подпрограмма интересна в основном оригиналь-
оригинальной идеей, лежащей в основе алгоритма преобразования.
B16BCDC:
Подпрограмма преобразования целого 16-ти битного двоичного
числа без знака в двоично-десятичное
Вход:
R5R4 - 16-ти разррдное двоичное число
Выход:
R1R2R3 - двоично-десятичные цифры (в младшей половине R1 -
десятки тысяч, в старшей R2 - тысячи, в младшей R2 -
сотни, в старшей R3 - десятки, в младшей R3 - единицы)
Используемые регистры:
R5, R4. А
Требует свободных байт в стеке:
2
MOV R1. #0
114
MOV
MOV
B16BCDCJ:
CLR
MOV
SUBB
MOV
MOV
SUBB
MOV
JC
MOV
ADD
DA
MOV
MOV
ADDC
DA
MOV
MOV
ADDC
DA
MOV
SJMP
B16BCDC_2:
RET
R2. #0
R3, #0 ; обнулить результат
С
A. R4
A, «1
R4. A
A, R5
A, #0
R5, A ; вычли 1 из числа
B16BCDC_2 ; конец, если чисг
A, R3
А, #1
А
R3. А
A, R2
А, #0
А
R2. А
A, R1
А, #0
А
R1, А : добавили 1 к двои'
B16BCDCJ
Рис. 59. Подпрограмма преобразования 16-битных двоичных чисел в двоично-
десятичные (вариант 4)
И наконец, последняя, самая изящная и эффективная подпрограм-
подпрограмма преобразования 16-битных двоичных чисел в двоично-десятичные.
Чтобы понять, как она функционирует, давайте вспомним следующее.
Любое число В, записанное п двоичными цифрами Ъ1 @ < i < п-1) как
= п—1 п—2"'^| О > C1}
115
соответствует величине
(и
V(B) = Ъп_х ¦ 2-1 + Ьп_г ¦ 2"~2 + ... + bl-2l + b0- 2°. C2)
Преобразуем выражение C2) по схеме Горнера:
V{B) = (...(&„_, • 2 + Ьп_2) ¦ 2 + ... + Ь,) • 2 + Ьо. C3)
Таким образом, если взять старший разряд двоичного числа, уд-
удвоить его, прибавить к удвоенному второй слева разряд, удвоить сум-
сумму, прибавить к удвоенному третий слева разряд и т. д., мы получим
величину исходного числа.
Теперь давайте сделаем то же самое, но сложения будем осуще-
осуществлять по правилам двоично-десятичной арифметики с использо-
использованием команды DA А после выполнения каждой инструкции сло-
сложения. Вспомним, что удвоение можно осуществить как умножени-
умножением исходного числа на два, так и суммированием числа с самим со-
собой. Таким образом, хотя инструкция двоично-десятичного умно-
умножения в нашем микроконтроллере отсутствует, в данном случае ее
можно заменить сложением, опять же выполняемым по правилам
двоично-десятичной арифметики. Как нетрудно сообразить, если вы
выполните последовательность арифметических операций с соблю-
соблюдением правил двоично-десятичной арифметики, результат также
будет в двоично-десятичной форме. Именно на этом основан способ
преобразования, реализуемый подпрограммой B16BCDD.
В ходе выполнения подпрограммы мы последовательно сдвига-
сдвигаем преобразуемое число влево, получая в бите переноса вначале Ь|5,
затем Ь,4> затем Ь|3 и т. д. Полученный разряд суммируем (с учетом
DA А) с числом, накапливаемым в R1R2R3, после чего удваиваем
содержимое R1R2R3 (опять же с помощью все той же DA А). Когда в
CY окажется Ьо, мы просто прибавим его к содержимому R1R2R3,
после чего завершим преобразование. И все — быстро, просто и эф-
эффективно.
B16BCDD:
Подпрограмма преобразования целого 16-ти битного двоичного
числа без знака в двоично-десятичное
Вход:
R5R4 - 16-ти разрядное двоичное число
Выход:
R1R2R3 - двоично-десятичные цифры (в младшей половине R1 -
116
десятки тысяч, в старшей R2 - тысрчи. в младшей R2 -
сотни, в старшей R3 - десятки, в младшей R3 - единицы)
Используемые регистры:
R5. R4. В, А
Требует свободных байт в стеке:
2
MOV R1. #0
MOV R2. «О
MOV R3, «D ; обнулить результат
MOV
SJMP
B16BCDDJ:
MOV
ADD
DA
MOV
MOV
ADDC
DA
MOV
MOV
ADDC
MOV
B16BCDD_2:
MOV
RLC
MOV
MOV
RLC
MOV
MOV
ADDC
MOV
B, 016
B16BCDD_2
A, R3
A. R3
A
R3, Л ¦
A. R2
A, R2
A
R2. A
A. R1
A. R1
R1, A ;
A, R4
A
R4. A
A, R5
A
R5, A ;
A. R3
A. »0
R3, A
пропустить первое (лишнее) умножение на 2
R1R2R3 умножено на 2 по правилам 10-чной системы
; число сдвинуто влево
добавили старший разряд к единицам результата
R1R2R3
DJNZ В. B16BC0D_1 ; зацикливание
RET
Рис. 60. Подпрограмма преобразования 16-битных двоичных чисел в двоично-
десятичные (вариант 5)
ПРЕОБРАЗОВАНИЕ ДВОИЧНО-ДЕСЯТИЧНЫХ ЧИСЕЛ В 16-БИТНЫЕ
ДВОИЧНЫЕ
Преобразование двоично-десятичных чисел в двоичные в отли-
отличие от предыдущего осуществляется довольно однообразно и три-
виалыго. Как вы помните, любое целое неотрицательное число Д
записанное «десятичными цифрами dt (О < i< n~\) как
Д=Ч-А-2-Ч4>. C4)
соответствует величине
V(D) = dn_, • 10"-' + dn_2 ¦ lO" + ... + dl ¦ 101 + 4, ¦ 10°. C5)
Преобразуем выражение C5) no схеме Гсрнерес.
V(D) = (...(<*„_, • 10 + dn_2 ) • 10 + ... + dx ) ¦ 10 + dy C6)
Таким образом, если взять старший разряд двоично-десятично-
двоично-десятичного числа, умножить на 1010, прибавить к произведению второй слева
разряд, умножить сумму на 1010, прибавить к произведению третий
слева разряд и т. д., и при этом все операции выполнять по правилам
целочисленной двоичной арифметики, мы получим исходное число
в двоичном представлении.
Поскольку нам предстоит в ходе рассматриваемых преобразований
неоднократно умножать двухбайтовое число на 1010> эту операцию по-
полезно оформить в виде самостоятельной подпрограммы. Естественно,
в ее основе лежит инструкция быстрого однобайтного умножения
MUL АВ, выполняемая стандартным микроконтроллером семейства
х51 за четыре машинных цикла. Эта подпрограмма (MUL_R5R4_10) до-
довольно близка к подробно описанной в предыдущей главе подпрограм-
подпрограмме MUL16M, поэтому я воздержусь от описания ее работы.
MUL_R5R4_10:
;; Подпрограмма умножения целого 16-ти битного двоичного ;;
118
; числа без знака
; Вход:
; R5R4 - 16-ти
; Выход:
на основание десятичной системы 10 ;;
разрядное двоичное число без знака ;;
; AR5R4 - произведение ;;
; ©лаги:
; CY - CY=1
: (А>0)
;; Используемые
•; в
признак переполнения 16-ти разрядного результата :;
регистры: ;
.; Требует свободных байт в стеке: ;•
;; 2
M0V
MOV
MUL
M0V
MOV
MOV
M0V
MUL
ADD
MOV
MOV
JZ
А.
В.
АВ
R4,
А.
R5
В,
АВ
А,
R5
А.
R4
йЮ
А
R5
В ; R5R4 = МЛБ • 10
ею
R5
А : R5R4 = МЛБ • 10 + 256 • (СТБ * 10) =
; = (МЛБ + 256 • СТБ) • 10
В ; проверка на переполнение при умножении
MUL R5R4 10 1
SETB С ; переполнение, CY = 1
MUL_R5R4_10_1:
RET
Рис. 61. Подпрофамма умножения двухбайтового числа на основание десятичной
системы 10
Дальше все просто. Подпрограмма BCDB16A осуществляет пре-
преобразование в двоичное двоично-десятичного числа, хранящегося в
буфере с адресом в R0. Она последовательно извлекает из буфера
119
цифры двоично-десятичного числа, обрабатывает их в соответствии
с C6) и в конце концов получает результат в регистрах R5R4.
BCDB16A:
Подпрограмма преобразования целого двоично-десятичного
числа без знака (от 0 до 65535) в 16-ти
разрядное двоичное число
Вход:
R0 - адрес буфера, в котором хранятся цифры двоично-
десятичного числа
R1 - количество цифр в буфере (от 1 до 5)
буфер - содержит десятичные цифры (например: байт 1. 2 и 3
для десятичного числа 123)
Выход:
R5R4 - 16-ти разрядное двоичное число (если CY = 0)
©лаги:
CY - признак переполнения 16-ти разрядного результата,
т.е. CY=1 - признак ошибки (число быго больше 65535)
Используемые регистры:
R3, R2. А, В
Требует свободных байт в стеке:
2+2=4
Использует подпрограммы:
MUL R5R4 10
MOV A, R0
MOV R2, А
MOV A, R1
MOV R3, А ; сохранили R0 и R1 в R2 и R3 соответственно
MOV R5. #0
MOV R4, ВО ; обнуление результата
SJMP BCDB16A_2 ; пропустили не нужное умножение 0 на 10
BCDB16A_1:
LCALL MUL_R5R4_10 ; умножили результат на 10
JC BCDB16A_3 ; переполнение 16-ти разрядного результата
120
BCDB16A_2:
MOV A, R4
ADD A. WO
MOV R4, A
MOV A, R5
ADDC A, »O
MOV R5, A : добавили двоично-десятичную цифру к резулыату
JC BCDB16A_3 ; переполнение 16-ти разрядного результата
INC R0
DJNZ R1. BCDB16A_1 ; перешли к следующей двоично-десятичной
цифре
BCDB16A_3:
MOV A, R2
MOV R0, А
MOV A. R3
MOV R1, А ; восстановили R0 и R1 из R2 и R3 соответственно
RET
Рис. 62. Подпрограмма преобразования двоично-десятичных чисел в 16-битные
двоичные (вариант 1)
Подпрограмма BCDB16B осуществляет аналогичное преобразо-
преобразование в двоичное двоично-десятичного числа, хранящегося в регис-
регистрах R1R2R3. Она последовательно извлекает из соответствующей
тетрады соответствующего регистра цифры двоично-десятичного
числа, обрабатывает их по алгоритму C6) и также в конце концов
получает результат в регистрах R5R4.
BCDB16B:
Подпрограмма преобразования целого двоично-десятичного
числа без знака (от 0 до 65535) в 16-ти
разрядное двоичное число
Вход:
R1R2R3 - двоично-десятичные цифры (в R1 - десятки тысяч,
в старшей половине F2 - тысячи, в младшей R2 - сотни.
в старшей R3 - десятки, в младшей R3 - единицы)
Выход:
121
R5R4 - 16-ти разрядное двоичное число (если CY = 0)
Флаги:
СУ - признак переполнения 16-ти разрядного результата,
т.е. CY=1 - признак ошибки (число было больше 65535)
Используемые регистры:
А. В
Требует свободных байт в стеке:
2+1+2=5
Использует подпрограммы:
MUL_R5R4_10
MOV
PUSH
A, R0
АСС
сохранили R0 в стеке
HOV
ANL
ADD
MOV
MOV
MDV
MOV
BCDB16BJ:
LCALL
JC
MOV
SWAP
ANL
ADD
HOV
MOV
ADDC
MOV
JC
LCALL
JC
A, PSW
A. tA8H
A. »2
RO. A
R5, ffO
A. R1
R4, A
А содержит адрес RO (учитывая банк регистров)
а теперь адрес R2
R0 содержит адрес R2
1-я цифра (десятки тысяч) обработана
MUL_R5R4_10
BCDB16В_2 ; переполнение
А, ОТО
А
A. SOFH
A. R4
R4. А
A, R5
А, КО
R5. А
А = R2 (или R3 на втором проходе через это место)
взяли старшую половину
2-я D-я) цифра обработана
ВС0В16В_2 ; переполнение
MUL_R5R4_10
BCDB16B_2
MOV A. SRO
А = R2 (или R3 на втором проходе через это место)
122
ANL
ADD
MOV
MOV
ADDC
MOV
JC
INC
MOV
ANL
A. flOFH
A, FM
R4, A
A, R5
A, »O
R5, A
BCDB16B
RO
A. RO
A, «1
взяли младшую половину
; 3-я E-я) цифра обработана
переходим к следующему регистру (сгед. паре цифр)
если R0 теперь содержит адрес R3 (нечетный адрес)
; то младший бит А будет равен 1. и требуется
JNZ BCDB16B_1 ; повторить цикл для 4-й и 5-й цифр (в регистре
R3)
BCDB16B_2:
POP ACC
MOV RO, A ; восстановили RO из стека
RET
Рис. 63. Подпрограмма преобразования двоично-десятичных чисел в 16-битные
двоичные (вариант 2)
И в завершение — небольшая табличка, позволяющая протести-
протестировать включенные в вашу программу подпрограммы преобразова-
преобразования чисел из двоичного представления в двоично-десятичное, и на-
наоборот.
Таблица 17
Тесты преобразования чисел из двоичного представления
в двоично-десятичное и наоборот
Представление чисел
Шестнадцатеричное
FFFF
FFFE
FFOO
O0FD
АААА
03FF
Двоично десятичное
0110 0101 0101 0011 0101
0110 O1O1 0101 0011 0100
0110 0101 0010 1000 0000
0000 0000 0010 0101 0011
0100 0011 0110 1001 0000
0000 0001 0000 0010 0011
123
КРАТКИЕ ВЫВОДЫ
Итак, мы рассмотрели пять вариантов преобразования двоичных
чисел в двоично-десятичные и два варианта преобразования двоич-
двоично-десятичных чисел в двоичные. Рассмотренные варианты заметно
различаются по принципу преобразования и по быстродействию.
Одни из них, приведенные на рис. 60 и 62, имеют практическую цен-
ценность, другие, например, на рис. 59, ценны скорее лежащим в их ос-
основе принципом. В любом случае программисту полезно ознакомить-
ознакомиться со всеми способами преобразования, т. к. это обогатит его идеями,
которые могут оказаться полезными в дальнейшем.
Глава 3
МНОГОБАЙТНЫЕ БЕЗЗНАКОВЫЕ "
ЦЕЛЫЕ ЧИСЛА
В первой главе мы рассмотрели двоичное сложение и вычитание, а
также привели примеры подпрограмм для всех арифметических опе-
операций (сложения, вычитания, умножения и деления) двоичных 16-
битовых (или двухбайтовых) чисел. Как мы уже отмечали, в подав-
подавляющем большинстве применений арифметических операций с
двухбайтовыми числами оказывается достаточно для решения всех
стоящих перед системой задач. Погрешность вычислений при этом
составляет величину, меньшую 0,01 %, что обычно меньше погреш-
погрешности вводимых в МК данных, получаемых в результате съема ин-
информации с тех или иных датчиков. Напомним, что единственным
источником погрешности в целочисленной арифметике является опе-
операция деления. Использование дробей также вносит погрешность,
поскольку умножение или деление на дробь подразумевает исполь-
использование целочисленного деления.
Если вам требуется значительно большая точность, придется исполь-
использовать программы арифметики с плавающей десятичной запятой, ко-
которые будут рассмотрены в следующем томе. Но затраты времени на
обработку результата в этом случае сильно возрастают, что является ос-
основной причиной, сдерживающей применение микроконтроллеров в
областях, требующих больших объемов подобных вычислений. Поэто-
Поэтому программистам приходится использовать либо более адаптирован-
адаптированные для подобных вычислений цифровые сигнальные процессоры или
процессоры, аналогичные х86, либо предельно оптимизировать по бы-
быстродействию свои программы. Иногда для этого оказываются полез-
полезными подпрограммы целочисленной беззнаковой многобайтной ариф-
арифметики, которые будут рассмотрены в настоящей главе.
125
Когда мы сказали слово "многобайтной", у вас сразу должен был
бы возникнуть вопрос: много — это в данном случае сколько? Числа
какой разрядности нам предстоит умножать — трехбайтовые, четы-
четырехбайтовые, десятибайтовые, стобайтовые или еще более длинные?
Давайте вспомним, что у стандартного х51 есть всего 128 байт внут-
внутренней оперативной памяти, в которой должны располагаться не толь-
только обрабатываемые числа, но и стек, переменные остальной части про-
программы, буферы для информации, отображаемой на дисплее, и т. д.
Отсюда ясно, что в этой главе бессмысленно рассматривать числа не
только 100-, но и даже 64-байтовые — у нас на это не хватит внутрен-
внутреннего ОЗУ МК. Поэтому ограничимся рассмотрением примеров с чис-
числами, разрядность которых не превышает 31 байт. Ну а на практике
вряд ли кому из читающих настоящие строки придется реально пользо-
пользоваться более чем 10-байтовыми числами — диапазон представления
этих чисел достигает B*I0 = A03)8 = 1024. Так что пусть 31-байтовое
ограничение длины слагаемых или сомножителей вас не смущает —
этого не просто достаточно, а более чем достаточно, причем как для
вычислений, так и для демонстрации того, как можно организовать
подобные подпрограммы для чисел любой длины.
СЛОЖЕНИЕ И ВЫЧИТАНИЕ МНОГОБАЙТНЫХ ДВОИЧНЫХ ЧИСЕЛ
Как было сказано, в настоящей главе мы приведем подпрог-
подпрограммы для сложения, вычитания, умножения, деления и сравне-
сравнения N-байтовых чисел. Вы не увидите ничего принципиально но-
нового в этих подпрограммах, т. к. они основаны на тех же самых
принципах, которые были описаны в первой главе.
Нетрудно догадаться, что первой, естественно, мы рассмотрим
подпрограмму сложения JV-байтовых чисел. Слагаемые располагают-
располагаются во внутренней оперативной памяти микроконтроллера. Младшие
байты слагаемых помещены по меньшим адресам, а старшие по боль-
большим16 . Адреса младших байтов слагаемых передаются в подпрограм-
подпрограмму в индексных регистрах R0 и R1 микроконтроллера. Размер слага-
слагаемых в байтах (N) указывается в регистре R2. Такая организация под-
подпрограммы с явным указанием длины операндов необходима для
16 Такой порядок расположения многобайтных чисел в памяти удобен для выполнения
сложения и вычитания, т. к. и то, и другое начинается с младших байтов чисел и ведет-
ведется в сторону старших, т. е. первое суммирование или вычитание будет делаться прямо
по тому адресу памяти, где начинается число. Также это может быть удобно, когда
производятся операции над многобайтными числами, но результат получается неболь-
небольшим и может уместиться в меньшем количестве байт, например, в одном. Удобство
заключается в том, что этот короткий результат будет адресоваться в памяти тем же
адресом, что и длинный — адресом младшего байта.
126
оптимизации подпрограмм по быстродействию. Если вам необходи-
необходимо оперировать, например, с четырехбайтовыми слагаемыми,.зане-
слагаемыми,.занесите в R2 четверку, и подпрограмма осуществит сложение именно
четырех пар байтов, а не большего их количества, ограничиваемого
предельными возможностями подпрограммы и микроконтроллера.
Сумма помещается на место второго слагаемого.
Программа устроена просто. Cin0 устанавливается в 0 инструк-
инструкцией CLR С. Затем ЛГраз выполняется тело цикла, которое складыва-
складывает байты слагаемых инструкцией ADDC. Сложение начинается с млад-
младших байтов и продолжается до самого старшего. Поскольку при
исполнении цикла регистры R0 и R1 увеличиваются на JV, восста-
восстановление значений этих регистров перед выходом из подпрограммы
происходит путем вычитания JV из каждого из этих регистров.
ADDN:
Подпрограмма сложения двух целых N-байтных чисел
Вход:
R0 - адрес МЛБ 1-го слагаемого
R1 - адрес МЛБ 2-го слагаемого
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R0 и R1 будут
указывать на МЛБ, a R0+1 и R1+1 - на СТБ)
R2 - размер слагаемых в байтах
Выход:
R1 - адрес суммы (сумма помещается на место 2-го слагаемого)
Флаги:
CY - признак переноса при сложении беззнаковых чисел
0V - признак переполнения при сложении чисел со знаком
Используемые регистры:
А, В
Требует свободных байт в стеке:
2
MOV В, R2
CLR С : СУ=О перед сложением младших байт чисел
ADDN.1:
MOV A, @R1
ADDC A, @R0
127
MOV @R1, A ; сложили байты двух чисел
INC RO
INC R1
DJNZ B, ADDN_1 ; повторить для следующего байта
MOV В, PSW ; сохранили флаги
MOV
CLR
SUBB
MOV
MDV
CLR
SUBB
MOV
MOV
A, RO
С
A, R2
RO, A
A, R1
С
A, R2
R1, A
PSW, В
; восстановили
; восстановили
; восстановили
RO
R1
флаги
RET
Рис. 64. Подпрограмма сложения многобайтных беззнаковых чисел
А теперь представим без лишних комментариев аналогичную
подпрограмму вычитания:
SUBN:
Подпрограмма вычитания двух целых N-байтных чисел
Вход:
R0 - адрес МЛБ вычитаемого
R1 - адрес МЛБ уменьшаемого
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R0 и R1 будут
указывать на МЛБ, a R0+1 и R1+1 - на СТБ)
R2 - размер чисел в байтах
Выход:
R1 - адрес разности (раэн. помещается на место уменьшаемого)
Флаги:
СУ - признак эаема при вычитании беззнаковых чисел
0V - признак переполнений при вычитании чисел со знаком
128
Используемые регистры:
А, В
Требует свободных байт в стеке:
2
MOV В, R2
CLR С ; CY=O перед вычитанием младших байт чисел
SUBN_1:
MOV A, (sfl1
SUBB A, @>R0
MOV @R1, A ; вычли байт вычитаемого из байга уменьшаемого
INC . R0
INC R1
DJNZ В, SUBN1 ; повторить для следующего байта
MOV В, PSW ; сохранили флаги
MOV
CLR
SUBB
M0V
MOV
CLR
SUBB
MOV
MOV
А.
С
А,
R0,
А,
С
А.
R1,
R0
R2
, А
R1
R2
, А
PSW, В
; восстановили
; восстановили
; восстановили
R0
R1
флаги
RET
hie. 65. Подпрограмма вычитания многобайтных беззнаковых чисел
^РАВНЕНИЕ И СДВИГИ МНОГОБАЙТНЫХ ДВОИЧНЫХ ЧИСЕЛ
Следующая из рассматриваемых подпрограмм — сравнение мно-
*обайтных чисел. Как вы помните, если для сравнения двух чисел не
Ьредусмотрено специальных команд, то операция сравнения может
быть выполнена путем вычитания одного числа из другого и анализа
129
полученной разности. Поэтому подпрограмма выполняет следующие
действия. Вначале производится вычитание младшего байта одного
числа из младшего байта другого и осуществляется операция «ИЛИ» с
регистром R3, изначально равным нулю. Как нетрудно догадаться,
нулевым результат этой операции будет лишь в том случае, если срав-
сравниваемые байты были идентичны друг другу. Далее подобная опера-
операция повторяется со следующими по млстаршинству байтами сравни-
сравниваемых чисел и т. д., вплоть до старших. После этого восстанавлива-
восстанавливаются необходимые регистры и флаги.
Если после выполнения рассматриваемой подпрограммы CY=1,
то уменьшаемое меньше вычитаемого (в данном случае, как вы по-
помните, речь идет о числах без знака), иначе, если CY=0, уменьшаемое
больше или равно вычитаемому. Далее, если сравниваемые числа
равны, то аккумулятор равен нулю. В противном случае (если срав-
сравниваемые числа не равны) на выходе подпрограммы в аккумуляторе
будет ненулевой результат. Таким образом, для беззнаковых чисел
анализ после выполнения подпрограммы флага CY и аккумулятора
дает информацию о том, равны сравниваемые числа или нет, и если
нет, то какое из них больше, а какое меньше.
Рассматриваемая подпрограмма также осуществляет сравнение
знаковых чисел, для чего в нее включен макрос JNS CMPN_2, опре-
определенный во втором разделе главы 1, и команда CPL OV. Однако
более подробное описание, для чего они нужны и что они делают,
мы дадим в позже, в главе, рассматривающей числа со знаком.
CMPN:
Подпрограмма сравнения двух целых N-байтных чисел
Вход:
R0 - адрес 1-го числа
R1 - адрес 2-го числа
R2 - размер чисел в байтах
Выход:
А - равен 0, если числа равны
не равен 0, если числа не равны
Шпаги:
CY - раввн 1, если 2-е число < 1-го числа
равен 0, если 2-е число >= 1-го числа
(при сравнении чисел без знака)
0V - равен 1, если 2-е число < 1-го числа
равен 0, если 2-е число >= 1-го числа
(при сравнении чисел со знаком)
130
Т.е. для чисел без знака;
W1 = 0RO, если А = О (JZ)
efl1 <> @R0, если А о О (JNZ)
@R1 < @R0. если CY = 1 (JC)
@R1 > @R0, если CY = 0 и А о О
«1 >= «О, если CY = О (JNC)
(@R0 означает 1-е число,по адресу RO; @R1- 2-е, по адресу R1)
Т.е. для чисел со знаком:
№1 = 0RO, если А = О (JZ)
3R1 <> «0. если А о 0 (JNZ)
еЯ1 < @R0. если CY = 1 (J0)
№1 > еЯО, если CY = 0 и А о 0
№1 >= №0, если CY = 0 (JNO)
(PRO означает 1-е число,по адресу RO; @R1- 2-е, по адресу R1)
Используемые регистры:
R3. А, В
Требует свободных байт в стеке:
2
M0V
M0V
CLR
CMPN 1:
INC
INC
DJNZ
В, R2
R3, flO
MOV
SUBB
хсн
ORL
ХСН
A. 3R1
A, @R0
A, R3
A, R3
A, R3
CY=0 перед вычитанием младших байт чисел
вычли байт числа 1 из байта числа 2
вычислили R3 = R3 OR А - зто
для определения равенства чисел:
R3 будет равен 0, если все цифры
разности нулевые, т.е. разность чисел
равна нуле (числа равны), иначе R3
будет отличен от 0 (числа не равны)
R0
R1
В. CMPN_1
повторить для следующего байта
131
JNS CMPN_2 ; установили требуемое
CPL OV ; значени OV для случая чисел со знаком
CMPN_2:
MOV В, PSW : сохранили флаги
MOV
CLR
SUBB
MOV
MOV
CLR
SUBB
MOV
MOV
MOV
А.
С
А.
R0,
А.
С
А.
R1,
R0
R2
А
R1
R2
, А
PSW, В
А.
R3
; восстановили R0
; восстановили R1
; восстановили флаги
RET
Рис. 66. Подпрограмма сравнения многобайтных чисел
Следующая подпрограмма — сравнение многобайтных чисел с
нулем. Во многих случаях она оказывается весьма полезной — не
будешь же занулять одно из сравниваемых чисел, тратя на это время
и память МК, если есть подобная подпрограмма. Каких-либо ком-
комментариев она не требует в силу своей очевидности.
CMPZN:
Подпрограмма сравнения N-байтного числа с нулем
Вход:
R1 - адрес числа
R2 - размер чисел в байтах (N)
Выход:
А - равен 0, если число равно О
не равен 0, если числа не равно О
Используемые регистры:
В
Требует свободных байт в стеке:
132
CLR
MOV
CMPZNJ:
ORL
INC
DJNZ
MOV
MOV
CLR
SUBB
MOV
A
B, R2
A, @R1
R1
B, CMPZNJ
В. А : сохранили резул
A. R1
С
A, R2
R1, A ; восстановили R1
MOV А, В ; восстановили результат
RET
Рис. 67. Подпрограмма сравнения многобайтных чисел с нулем
Следующая из рассматриваемых подпрограмм — подпрограмма
RLAN арифметического сдвига многобайтного числа влево на 1 бит.
При этом в младший бит числа заносится 0. Как нетрудно сообра-
сообразить, исходное многобайтное число в результате этой операции уд-
удваивается, поэтому мбжете пользоваться этой подпрограммой для
Удвоения чисел, учетверения и т. д.
В ряде случаев возникает необходимость в так называемом логи-
логическом сдвиге двухбайтового числа на 1 разряд влево через перенос
(подпрограмма RLCN). В результате этого сдвига в младший бит
исходного числа заносится содержимое флага CY (может быть как 0,
так и 1 в зависимости от результата выполнения предыдущих ко-
команд). В результате этого сдвига в CY оказывается содержимое стар-
старшего бита исходного многобайтного числа.
И наконец, подпрограмма RLN, осуществляющая сдвиг всех
битов многобайтного числа на 1 позицию влево. При этом самый
старший бит сдвигается одновременно как в CY, так и в освобо-
освободившуюся позицию самого младшего байта. Таким образом, дей-
действие ее почти аналогично подпрограмме RLCN за исключением
того, что на место, освободившееся от самого младшего бита, за-
133
носится не содержимое CY, а самый старший бит исходного чис-
числа. Первоначальное же содержимое CY в ходе выполнения под-
подпрограммы RLN теряется.
RLAN:
Подпрограмма арифметического сдвига N-байтного числа влево на 1
разряд: в младший разряд числа задвигается О
Вход:
R1 - адрес числа
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R1 будет
указывать на МЛБ, a R1+1 - на СТБ)
R2 - размер числа в байтах
Выход:
Флаги:
CY - выдвинутый из числа бит
Используемые регистры:
А. В
Требует свободных байт в стеке:
2
Использует подпрограммы:
RLCN
CLR С
SJMP RLCN ; продолжить в RLCN
RLN:
Подпрограмма поворота N-байтного числа влево на 1 разряд
Вход:
R1 - адрес числа
(более старшие байты чисел располагается по более старшим
адресам, т.е. для двухбайтовых чисел R1 будет
указывать на МЛБ. a R1+1 - на СТБ)
R2 - размер числа в байтах
CY - задвигаемый в число бит
Выход:
Флаги:
CY - младший бит повернутого числа
Используемые регистры:
134
А, В
Требует свободных байт в стеке:
2
Использует подпрограммы:
RLCN
MOV A, R1
MOV В, А ; сохранили R1
ADD A, R2
MOV R1, А ; R1 - адрес байта, расположенного после СТБ,
: т.е. R1-1 - адрес СТБ
DEC R1
MOV A. @R1 ; A = СТБ числа
MOV R1, В ; восстановили R1
RLC А ; CY = старший бит числа
RLCN:
Подпрограмма логического сдвига N-байтного числа влево на 1 ;
разряд через перенос ;
Вход: ;
R1 - адрес числа ;
(более старшие байты чисел располагаются по более старшим ;
адресам, т.е. для двухбайтовых чисел R1 будет
указывать на ИЛБ, a R1+1 - на СТБ)
R2 - размер числа в байтах ;
CY - задвигаемый в число бит
Выход:
Флаги:
CY - выдвинутый из числа бит
Используемые регистры:
А. В
Требует свободных байт в стеке:
2
MOV В. R2
RLCN 1:
135
MOV A, @R1
RLC A
MOV @R1. A : сдвинули байт на 1 бит влево с учетом CY
INC R1 ; переход к более старшему байту
DJNZ В, RLCN_1 ; повторить для следующего байта
HOV В, PSW ; сохранили флаги
MOV A, R1
CLR С
SUBB A. R2
MOV R1, А : восстановили R1
MOV PSW, В ; восстановили флаги
RET
Рис 68. Подпрограммы сдвигов влево многобайтных чисел
Схожие подпрограммы приведены на рис. 69 — арифметичес-
арифметический и логический сдвиги многобайтного числа вправо. Правда, под-
подпрограмма RRAN ориентирована на работу со знаковыми числами,
для чего она оставляет неизменным старший бит исходного числа.
Более подробно об этом мы поговорим в главе, рассматривающей
числа со знаком.
RRAN:
Подпрограмма арифметического сдвига N-байтного числа вправо на 1
разряд: старший (знаковый) разряд числа остается неизменным
Вход:
R1 - адрес числа
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R1 будет
указывать на МЛБ, a R1+1 - на СТБ)
R2 - размер числа в байтах
Выход:
Флаги:
CY - выдвинутый из числа бит
Используемые регистры:
А. В
Требует свободных байт в стеке:
136
2
Использует подпрограммы:
RRCN
MOV A, R1
ADD A, R2
MOV R1, А ; R1 - адрес байта, расположенного после СТБ,
; т.е. R1-1 - адрес СТБ
DEC R1
MOV A. @R1
RLC A ; СУ = знак числа
INC R1
SJHP RRCN_O ; продолжить в RRCN
RRN:
Подпрограмма поворота N-байтного числа вправо на 1 разряд
Вход:
R1 - адрес числа
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R1 будет
указывать на МЛБ, a R1+1 - на СТБ)
R2 - размер числа в байтах
CY - задвигаемый в число бит
Выход:
Шлаги:
CY - старший бит повернутого числа
Используемые регистры:
А. В
Требует свободных байт в стеке:
2
Использует подпрограммы:
RRCN
MOV A, №1
RRC А ; СУ = младший бит числа
RRCN:
;; Подпрограмма логического сдвига N-байтного числа вправо на 1 ;;
;; разряд через перенос ;:
137
Вход:
R1 - адрес числа
(более старшие байты чисел располагается по более старшим
адресам, т.е. для двухбайтовых чисел Ri будет
указывать на МЛБ. a R1+1 - на СТБ)
R2 - размер числа в байтах
СУ - задвигаемый в число бит
Выход:
Флаги:
CY - выдвинутый из числа бит
Используемые регистры:
А, В
Требует свободных байт в стеке:
2
MOV В, PSW ; сохранили CY (из-за последующего сложения)
MOV
ADD
MOV
MOV
RRCN_O:
MOV
RRCNJ:
DEC
MOV
RRC
MOV
A, R1
A. R2
R1. A
PSW, В
В. R2
R1
A, @R1
A
@R1. A
; R1 - адрес байта, расположенного после СТБ
; т.е. R1-1 - адрес СТБ
; восстановили CY
; точка входа для подпрограммы RRAN
; переход к более младшему байту
: сшинули байт на 1 бит вправо с учетом CY
DJNZ В, RRCN_1 ; повторить для следующего байта
RET
Рис. 60. Подпрограммы сдвигов вправо многобайтных чисел
138
УМНОЖЕНИЕ МНОГОБАЙТНЫХ
ДВОИЧНЫХ ЧИСЕЛ
Первая из рассматриваемых подпрограмм умножения многобай-
многобайтных чисел — MULNA — не блещет особой оригинальностью. Она
выполнена в соответствии с вычислительной схемой 1 (см. раздел
главы 1, где рассматривались четыре вычислительные схемы умно-
умножения двоичных чисел). Процесс формирования произведения выг-
выглядит следующим образом:
1) обнуление СЧП;
2) анализ младшего разряда множителя: если ув = 1, складываем
ЧП0 = X с СЧП и переходим к п. 3; если у0 = О, то непосредственно
переходим к п. 3;
3) сдвиг СЧП вправо;
4) анализ очередного разряда у, множителя и т. д.
После анализа старшего разряда ytn_f множителя осуществляют-
осуществляются последнее сложение ЧП8яц с СЧП, если yin_l = 1, и последний сдвиг
СЧП вправо, после чего процесс прекращается.
Кстати так же работает подпрограмма умножения двухбайтовых
чисел MUL16A, приведенная там же, в первой главе. Различие состо-
состоит лишь в длине операндов, количестве сдвигов множителя и суммы
частичных произведений и еще в том, что рассматриваемая в насто-
настоящем разделе MULNA использует вышеописанные подпрограммы
многобайтных сложения ADDN, сравнения CMPZN и сдвига RRCN.
MULNA:
Подпрограмма умножения двух беззнаковых цегых N-байтных чисел
(N<32)
Вход:
R0 - адрес МЛБ множимого
R1 - адрес МЛБ множителя, за которым следуют еще N байт для
старшей половины произведения
(более старшие байты чисел располагается по бопве старшим
адресам, т.е. для двухбайтовых чисел R0 и R1 будут
указывать на МЛБ, a R0+1 и R1+1 - на СТБ)
R2 - размер множителя/множимого в байтах (т.е. число N (N<32>)
Выход:
R1 - адрес 2М-байтногс произведения (записывается на место
множителя)
Флаги:
CV - признак переполнения N-байтного произведения
Используемые регистры:
139
R4, R3, А, В
Требует свободных байт в стеке:
2+2=4
Использует подпрограммь;
ADDN, CMPZN. RRCN
H0V
M0V
ADD
MOV
А,
R3
А,
R1
R1
. А
R2
. А
MOV
MULNA 1:
MULNA 2:
сохранипи R1 в R3
R1 - адрес МЛБ старшей половины произведения
В, R2
MOV
INC
DJNZ
M0V
M0V
MOV
RL
RL
RL
MOV
MOV
RRC
JNC
M0V
ADD
MOV
@R1, «0
R1
В. HULK
A, R3
HI, A
A. R2
А
А
А
R4, А
A. @R1
А
MULNA_3
A, R1
A, R2
R1, А
\_1 ; обнупили старшую половину произведения
восстановили R1 из R3
LCALL ADDN
R4 = 8*N. 8«N циклов сложения и сдвига
пропустить спсжение, если бит множителя равен О
R1 - адрес МЛБ старшей половины произведения
добавили множимое к старшей поповине
частичного произведения
MOV A, R3
MOV R1, А
восстановили R1 из R3
140
MULNA 3:
MOV
RL
MOV
A, R2
A
R2, A
; R2 = 2*N
LCALL RRCN ; частичное произведение сдвинуто на разряд вправо
MOV A, R2
RR А
MOV R2. А : R2 = N
DJNZ R4, MULNA_2 ; зацикливание
MOV A, R1
ADD A, R2
MOV R1, А ; R1 - адрес МЛБ старшей половины произведения
LCALL CMPZN ; сравнить старшую половину произведения с нулем
CLR С
JZ MULNA_4 ; CY=O, вели результат поместился в N байт
SETB С ; CY=1, если результат не поместился в N байт
MULNA_4:
MOV A, R3
MOV R1, А ; восстановили R1 из R3
RET
Рис. 70. Подпрограмма умножения многобайтных беззнаковых чисел, вариант 1
Мы рассмотрели, можно сказать, классический вариант под-
подпрограммы умножения многобайтных чисел, использующий толь-
только команды и подпрограммы сложения и сдвигов. По этому алго-
алгоритму можно выполнить подобную подпрограмму для любого
микроконтроллера, ибо нет таких МК, которые не могли бы скла-
складывать и сдвигать 8-разрядные числа. Но вспомним, что микро-
микроконтроллеры семейства х51 располагают командой умножения
друг на друга двух 8-разрядных чисел с получением 16-разрядно-
16-разрядного результата. Возникает законный вопрос, а нельзя ли использо-
использовать эту инструкцию для реализации быстрого умножения мно-
многобайтных чисел?
141
Конечно же, можно. В соответствии с правилами позиционных си-
систем счисления величины многобайтных чисел в двоичной системе
могут быть выражены через величины байтов t чисел следующим об-
образом:
V{BT) = t0 ¦ 256° + t, -2561 +...+ f№2 • 256N+ t^ ¦ 256*"' . C7)
Однобайтовое число, как нетрудно догадаться, записывается так:
V{BT1) = V 256° =50, C8)
где s0 — величина (значение) рассматриваемого байта.
Давайте умножим друг на друга эти два числа:
V(BT1) ¦ V(BT) =
= К - til + К ¦ ti«" 256 + l5c "til - 256 + fso • tin ¦ 2562 + • • •
+ {50 ¦ tNJL ¦ 256*» + {50 • tN_2]H- 256*» + {50 • tNJL ¦ 256^' +
+{50tN.1}H-256". C9)
Здесь [s0- t$L — младший байт двухбайтового произведения од-
однобайтового числа V(BT1) на i-й @ < i< N-l) байт V(BT), {s0 • f.}H —
его старший байт.
Преобразуем произведение следующим образом:
V{BT1) -V{BT) =
= Ц, - tii + ({Jo • tiH + К • tii) • 256 + «*о • ti* + lso ¦ tiJ • 2562 + ...
- +(K-**jH +iva)'2^+ivu-256- D0)
А теперь давайте проанализируем это выражение. Как нетрудно
заметить, каждое из слагаемых за исключением первого и последнего,
состоит из умноженной на соответствующую степень числа 256 сум-
суммы двух байт. Одним из этих байт является младший байт [^ • tt)L двух-
двухбайтового произведения, полученного в результате умножения байта
V{BT1) на i-й байт V(BT), вторым — старший байт {^ • t_,}H двухбай-
двухбайтового произведения, полученного в результате умножения V(BT1) на
(г-1)-й байт V(BT). Иными словами, в каждой из этих сумм присут-
присутствуют младший байт {s0 • f.}t произведения V[BT1) на i-й байт V[BT)
и своеобразный "байт переноса", оставшийся от предыдущего анало-
аналогичного произведения. Таким образом, осуществляя последователь-
последовательное умножение каждого из байтов многобайтного числа на однобай-
142
товый множитель и складывая второй, третий и т. д. младшие байты
произведения с оставшимися от предыдущих произведений старши-
старшими байтами, мы получим окончательный ЛГ-байтовый результат. В
соответствии с этим алгоритмом выполнена подпрограмма
MULNBYTE, осуществляющая умножение целого N-байтного беззна-
беззнакового числа на целое однобайтное число без знака.
MULNBYTE:
Подпрограмма умножения целого N-байтного числа без знака на
целое однобайтовое число без знака
Вход:
R1 - адрес N-байтного целого числа без знака
R2 - N
В - однобайтовьй множитель (целое число без знака)
Выход:
R1 - адрес младших N байт произведения
(они записывается на место множимого)
А - старший (N+1-й) байт произведения
Требует свободных байт в стеке:
2+3=5
PUSH
HOV
MOV
MULNBYTEJ:
PUSH
PUSH
MOV
HUL
ADD
MOV
MOV
ADDC
MOV
A, R3
ACC
R3, #0
A, R2
ACC
В
A, @R1
AB
A, R3
mt. a
А, В
A, »0
R3. A
; сохранили R3
; «байт переноса., в начале равен 0
; сохранили счетчик циклов
; сохранили байт-множитель
; умножили одун байт множимого на множитель
; учли «байт переноса»
; байт произведения готов
: следующий «байт переноса» готов
143
INC R1
POP В
POP ACC
перешли к следующему байту множимого
восстановили байт-множитель
восстановили счетчик циклов
DJNZ ACC. MULNBYTEJ
MOV A. R1
SUBB A. R2
MOV R1, А ; восстановили R1
POP ACC
ХСН A, R3 ; восстановили R3, получили в А старший (N+1-й)
; байт произведения
RET
Рис. 71. Подпрограмма умножения целого многобайтного беззнакового числа на
целое однобайтное беззнаковое число
Для чего это все было нужно? Да вот для чего. Осуществить
умножение друг на друга двух многобайтных чисел можно следу-
следующим образом. Сначала умножим множимое на младший байт
множителя и запомним результат. Затем умножим множимое на
второй справа байт множителя, сдвинем результат этого умноже-
умножения на один байт влево и сложим с первым произведением. После
этого умножим множимое на третий справа байт множителя, сдви-
сдвинем результат этого умножения теперь уже на два байта влево и
сложим с предыдущей суммой. И так далее, до тех пор, пока не
доберемся до старшего байта множителя и не прибавим к накап-
накапливаемой сумме его произведение на все то же множимое. Если
кто не верит, что так тоже можно получить правильное значение
произведения двух многобайтных чисел — запустите использую-
использующую этот алгоритм рассматриваемую ниже подпрограмму MULNB
на эмуляторе или симуляторе МК х51 и убедитесь на десятке при-
примеров, что она дает правильный результат. А мы с теми, кто по-
понял, как работает эта подпрограмма, познакомимся с ней побли-
поближе. Большое количество комментариев, которыми она снабжена,
дает возможность любому из вас разобраться с ее функциониро-
функционированием без каких-либо дополнительных разъяснений.
MULNB:
;; Подпрограмма умножения двух беззнаковых целых N-бзйтных чисел ;;
144
(N<32)
Вход:
RO - адрес МЛБ множимого
R1 - адрес ИЛБ множителя, за которым следуют еще N байт для
старшей половины произведения
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R0 и R1 будут
указывать на МЛБ, a R0+1 и R1+1 - на СТБ)
F2 - размер множителя/множимого в байтах (т.е. число N (N<32))
Выход:
R1 - адрес гЫ-байтного произведения (записывается на место
множителя)
©лагу:
CY - признак переполнения N-байгного произведения
Используемые регистры:
R4, R3, А, В
Требует свободных байт в стеке:
2+2=4
Использует подпрограммы:
• CHPZN
MULNB 1:
MOV
MOV
ADD
MOV
MOV
MOV
A.
R3
A.
R1
B,
@R
R1
, A
R2
, A
R2
1, BO
; Сохранили R1 в
; R1 - адрес МЛБ
R3
старшей половины произведения
INC R1
DJNZ В, MULNB_1 ; обнулили старшую половину произведения
MOV
M0V
MOV
MULNB_2-
PUSH
MOV
А.
R1,
А,
АСС
А.
R3
А
R2
т
; Восстановили R1 из R3
; A=N. N циклов сложени
145
MOV R4, A ; R4 - МЛ6 множителя
MOV
ADD
MOV
MOV
MOV
MULNBJ:
PUSH
MOV
MOV
MUL
ADD
XCH
ADDC
XCH
A. R1
A. R2
R1, A
R3. SO
A, R2 ;
ACC
A, PRO
B. R4
AB
A, R3
А, В
A. BO
А, В
; R1 - адрес МЛБ старшей половины произведения
; «байт переноса» в начале равен 0
A=N. N однобайтовых умножений в цикле слох./сДв
; учли «байт лереноса»
ADO A. №1
HOV @R1, А ; байт частичного Произведения готов
MOV А. В
ADDC А, яО
MDV R3. А ; следующий «байт переноса» готов
INC R0
INC R1
POP ACC
DJNZ ACC, MULNBJ3 ; зацикливание
MOV A. RO
SUBB A, R2
MOV RO, A ; RO - адоес МЛБ мнохимого
MOV A, R2
RL A
MOV В, А
MULNB_4:
146
DEC
MOV
XCH
MOV
UNZ
POP
DJNZ
MOV
MOV
ADD
MOV
LCALL
CLR
R1
A. @R1
A. R3
€«1, A
B, MULN
ACC
ACC, W
A. R1
R3, A
A, R2
R1, A
CMPZN
С
DJNZ B, MULNB_4 ; частичное произведение сдвинуто на байт вправо
АСС
ACC, MULNB_2 ; зацикливание
сохранили R1 в R3
R1 - адрес МЛБ старшей половины произведения
сравнить старшую половину произведения с нулем
JZ MULNB_5 ; CY=0, если результат поместился в N байт
SETB С ; CY=1. если результат не поместился в N байт
MULNB 5:
KOV
MOV
RET
A. R3
R1, А ; восстановили R1 из R3
Рис. 72. Подпрограмма умножения многобайтных беззнаковых чисел, вариант 2
ДЕЛЕНИЕ МНОГОБАЙТНЫХ ДВОИЧНЫХ ЧИСЕЛ
Также не блещет особой оригинальностью подпрограмма деле-
деления двух беззнаковых целых чисел DIVN (рис. 73). Она выполнена
по схеме с восстановлением остатка, как и рассмотренная в главе 1
подпрограмма DIV16. Каких-либо подробных объяснений она не тре-
требует, поскольку принцип ее функционирования был рассмотрен в
первой главе, а сама подпрограмма снабжена большим количеством
комментариев.
DIVN:
Подпрограмма деления двух беззнаковых целых чисел
Вход:
R0 - адрес МЛБ N-байтного делителя
147
R1 - адрес МЛБ 2N-6auTHOro делимого
(более старшие байты чисел располагаются по более старшим
адресам)
R2 - размер делителя в байтах (т.е. число N (N<32))
Выход:
R1 - адрес N-байтного частного (записывается на место младшей
половины делимого, в старшую половину делимого
записывается остаток)
Олаги:
CY - признак переполнения N-байтного частного или деление на
нуль. т.е. CY=1 - признак ошибки v результат не определен
Используемые регистры:
R4, R3. А. В
Требует свободных байт в стеке:
2+2=4
Использует подпрограммы:
ADDN. SUBN. CMPN. RLAN
сохранили R1 в R4
R1 - адрес КЛБ старшей половины делимого
сравнить старшую половину делимого и делитель
восстановили R1 из R4
JC DIVN_O ; старшая половина делимого < делителя, значит
; частное умещается в N байтах
CY=1 - переполнение N-байтного частного или
деление на нуль
да
M0V
ADD
MOV
LCALL
MOV
MOV
A, R1
R4, A
A, R2
R1, A
CMPN
A, R4
R1, A
SETB
RET
DIVNJ):
MOV
RL
RL
RL
MOV
С
A,
A
A
A
R4,
R2
A
; CY=1 - г
; л
; R4 = 8*N
148
DIVNJ:
MOV
RL
MOV
LCALL
PUSH
MOV
RR
MOV
ADD
MOV
A, R2
A
R2. A
RLAN
PSW
A, R2
A
R2, A
A, R1
R1. A
R2 = 2«N
остаток и частное сдвинуты на разряд влево
сохранили CY
R2 = N
R1 - адрес МЛБ старшей половины делимого
POP PSW : восстановили CY
JC DIVN_2 ; если первполнение остатка, вычесть ДЛ, +1 в частное
LCALL SUBN ; вычли делитель из остатка
JNC DIVN_3 ; +1 в частное, восстанавливать остаток не нужно
LCALL ADDN ; восстановили остаток
MOV A, R1
CLR С
SUBB A, R2
MOV R1, А ; R1 - адрес МЛБ делимого
SJMP DIVN_4 ; +0 в частное
DIVN_2:
LCALL SUBN ; вычли делитель из остатка
DIVN_3:
MOV A, R1
CLR С
SUBB A. R2
MOV R1, А : R1 - адрес МЛБ делимого
149
INC @R1 ; добавили 1 в частное
DIVNjt:
DJNZ R4. DIVNJ ; цикл
CLR С ; CY-0 - нет переполнения N-байтного частного
RET ; и нет деления на нуль
Рис. 73. Подпрограмма деления многобайтных беззнаковых чисел, вариант 1
На рис. 74 приведена подпрограмма деления двух беззнаковых
целых многобайтных чисел, выполненная по схеме без восстановле-
восстановления остатка. Комментарии приведены в тексте подпрограммы. Сле-
Следует только отметить, что для длинных чисел быстродействие деле-
деления по схеме без восстановления остатка выше, чем по схеме с вос-
восстановлением.
DIVNB:
• ¦¦(¦¦•¦tllll>Illlt1|l«fl1|ltfWtrifl**»»>>r>»ll1 I I I ¦ ¦ I I • I 1 t
Подпрограмма деления двух беззнаковых целых чисел
Вход:
R0 - адрес МЛБ N-байтного делителя
R1 - адрес МЛБ 2N-байтного делимого
(более старшие байты чисел располагается ло более старшим
адресам)
R2 - размер делителя в байтах (т.е. число N (N<32)>
Выход:
R1 - адрес N-байтного частного (записывается на место младшей
половины делимого, в старшую лоловину делимого
записывается остаток)
Флаги:
CY - признак переполнения N-байтного частного или деление на
нуль, т.е. CY=1 - признак ошибки и результат не определен
Используемые регистры:
R4. R3. А. В
Требует свободных байт в стеке:
2+2+1=5
Мслользует подпрограммы:
ADDN. SUBN, CMPN. RLAN
MOV A. R1
150
MOV R4. A
ADD A, R2
MOV R1. A
LCALL CMPN
MOV A, R4
MOV R1. A
JC
DIVNB О
DIVNB 0:
DIVNB 1:
SETB
RET
MOV
RL
RL
RL
MOV
CLR
PUSH
MOV
RL
MOV
С
A. R2
A
A
A
R4. A
A
ACC
A. R2
A
R2, A
LCALL RLAN
POP
RR
RLC
PUSH
MOV
RR
MOV
ACC
A
A
ACC
A. R2
A
R2, A
сохранили R1 в R4
R1 - адрес МЛБ старшей половины делимого
сравнить старшую половину делимого и делитель
восстановили R1 из R4
старшая половина делимого < делителя, значит
частное умещается в N байтах
CY=1 - переполнение N-байтного частного или
деление на нуль
R4 = 8.N
АСС.1 - знак предыдущего остатка
R2 = 2*N
остаток и частное сдвинуты на разряд влево
АСС.1 - знак предыдущего остатка,
АСС.О - перенос от сдвига
R2 = N
151
ADD A, R1
MOV R1, A ; R1 - адрес МЛБ старшей половины делимого
POP ACC
PUSH ACC
JB ACC.1, DIVNB_2 : если предыдущий остаток < О
LCALL SUBN ; вычли делитель из остатка
SJMP DIVNB_3
DIVNB 2:
LCALL ADDN ; лрибавили делитель к остатку
DIVNB 3:
POP
RLC
ADD
ACC
А
А, »2
АСС.2 - знак предьщущего остатка,
АСС.1 - перенос от сдвига,
АСС.О - перенос от сложения/вычитания
поправка на смещение таблицы
M0VC
SJMP
.DB
.DB
DIV16B_4:
PUSH
MOV
CLR
SUBB
HOV
POP
PUSH
ANL
ORL
MOV
A, ®A+PC
DIV16B_4
01B, 10B,
10B. 10B,
acc :
A. R1
с
A, R2
R1. A ;
ACC
ACC
A, »1
A, ®R1
№1. A ;
01B. O1B
10B, 01B
ACC. 1 - новый знак остатка.
АСС.О - что нужно добавить
R1 - адрес МЛБ делимого
добавили 0 или 1 к частному
DJNZ R4. DIVNBJ ; цикл
152
POP ACC
JNB ACC.1, DIVNB_5 ; если остаток >= О
MOV A, R2
ADD A, R1
MOV R1, A ; R1 - адрес МЛБ старшей половины делимого
LCALL ADDN ; прибавили делитель к остатку
MOV A, R1
CLR С
SUBB A, R2
MOV R1, А ; R1 - адрес МЛБ делимого
DIVNB_5:
CLR С ; CY=0 - нет переполнения N-байтного частного
RET ; и нет деления на нуль
Рис. 74. Подпрограмма деления многобайтных беззнаковых чисел, вариант 2
ДВОИЧНОЕ И ДВОИЧНО-ДЕСЯТИЧНОЕ ПРЕОБРАЗОВАНИЕ
МНОГОБАЙТНЫХ ДВОИЧНЫХ ЧИСЕЛ
И напоследок рассмотрим подпрограммы преобразования N-
байтового целого двоичного числа в М-байтовое целое беззнаковое
упакованное двоично-десятичное число BNBCD (рис. 75), и наобо-
наоборот, М-байтового целого беззнакового упакованного двоично-деся-
двоично-десятичного числа в N-байтовое целое двоичное число BCDBN (рис. 76).
Они также выполнены аналогично своим 16-битным прообразам, рас-
рассмотренным в главе 2, в связи с чем мы не будем их комментировать,
а ограничимся лишь комментариями в тексте подпрограмм.
BNBCD:
Подпрограмма преобразования N-байтного целого беззнакового
двоичного числа в М-байтное целое беззнаковое упакованное
двоично-десятичное число.
Вход:
R0 - адрес буфера для результата. М-байтного упакованного BCD
числа
R1 - адрес N-байтного буфера с двоичным целым числом без знака
R2 - N
R3 - м
153
Используемые регистры:
А. В
Требует свободных байт в стеке:
2+1+2=5
Использует подпрограммы:
RLN
M0V
BNBCD.1:
DEC
MOV
RL
RL
RL
A. R3
MOV
INC
DJNZ
mo.
RO
ACC,
«0
BNBCDJ
; обнулили
результат
RO
A. R2
A
A
A
PUSH ACC
8-N циклов т.к. 8'N бит в исходном числе
BNBCD_3:
MOV
CLR
MOV
ADDC
DA
NOV
DEC
DJNZ
MOV
ADD
MOV
LCALL
MOV
B. R3
С
A, «0
A. «0
A
«o. a
RO
B, BNBCD_3
A, RO
A. R3
RO. A
RLN ;
A. @R0
; умножили результат на 2 по правилам десятичной
; арифметики (результат получается
; в двоично-десятичном формате)
повернули исходное число на один бит влево
154
ADDC A, #0
MOV @R0, A ; добавили к результату выдвинутый бит
POP ACC
DJHZ ACC, BNBCD_2 ; повторить для всех битов исходного числа
MOV A, R0
INC A
SUBB A, R3
MOV R0. А ; восстановили R0
RCT
Рис. 75. Подпрофамма преобразования /V-байтового целого двоичного числа
в М-байтовое целое беззнаковое упакованнов двоично-десятичное число
BCDBN:
Подпрограмма преобразования Н-байтного целого беззнакового
упакованного двоично-десятичного числа в N-байтное целое
беззнаковое двоичное число-
Вход:
R0 - адрес М-байтного буфера с упакованным BCD числом
R1 - адрес буфера для результата. N-байтного двоичного целого
числа без знака
R2 - N
R3 - К
Используемые регистры:
А. В
Требует свободных байт в стеке:
2+1+5=В
Использует г.одпрограммы:
MULNBYTE
MOV A. R2
BCDBN_1:
MOV «1. »0
INC R1
DJNZ ACC, BCDBN_1 ; обнулили результат
MOV A. R1
155
CLR С
SUBB A, R2
MOV R1. A
MOV
BCDBN_2:
; восстановили R1
A, R3
PUSH ACC
MOV В. В1ОО
LCALL MULNBYTE ; умножили результат на 100
BCDBN_3:
MOV
SWAP
ANL
MOV
MUL
MOV
MOV
ANL
ADD
CLR
MOV
ADDC
MOV
CLR
INC
DJNZ
MOV
CLR
SUBB
MOV
A. @R0
A
A, »OFH
B. #10
AB
В, А
A. PRO
A. »OFH
А. В
С
В. R2
A, @R1
@R1, A
¦A
R1
B. BCDE
A, R1
С
A, R2
R1, A
; A = пара десятичных цифр, преобразованная в
; двоичную систему
В, BCDBN_3 ; добавили к результату преобразованную
; пару десятичных цифр
; восстановили R1
INC R0 ; перешли к следующей паре десятичных цифр
POP ACC
DJNZ ACC, BCDBN_2 : цикл ло всем ларам
MOV
A. R0
156
CLfi С
SUBB A, R3
MOV RO. A ; восстановили RO
RET
Рис. 76. Подпрограмма М-байтового целого беззнакового упакованного
двоично-десятичного числа в /V-байтовое целое двоичное число
КРАТКИЕ ВЫВОДЫ
Итак, мы рассмотрели подпрограммы арифметических операций
и преобразований для многобайтных целых беззнаковых чисел. Как
отмечалось в начале главы, они имеют достаточно ограниченную
применимость, ибо в подавляющем большинстве микроконтроллер-
микроконтроллерных применений арифметических операций с двухбайтовыми чис-
числами оказывается достаточно для решения всех стоящих перед сис-
системой задач. Погрешность вычислений в последнем случае состав-
составляет величину, меньшую 0,01 %, что обычно меньше погрешностей
вводимых в МК данных, получаемых в результате съема информа-
информации с тех или иных датчиков. Напомним, что единственным источ-
источником погрешности в целочисленной арифметике является опера-
операция деления. Использование дробей также вносит погрешность, по-
поскольку умножение или деление на дробь подразумевает использо-
использование целочисленного деления.
Но тем не менее материал, приведенный в настоящей главе, имеет
определенную практическую ценность. Во-первых, ограниченное бы-
быстродействие микроконтроллеров семейства х51 не дает возможность
широко использовать в них подпрограммы с плавающей десятичной
запятой, которые позволяют работать с числами, большими, чем 65535.
Поэтому многобайтная арифметика оказывается незаменимой, если
вы, например, обрабатываете данные от 24-разрядных АЦП, которые
сейчас выпускаются практически всеми производителями аналого-
цифровых и цифро-аналоговых преобразователей. В самом деле, в упо-
упомянутом случае вам вряд ли придется оперировать с более чем шести-
шестибайтовыми числами. Поэтому рассмотренные выше подпрограммы
вполне справятся с подобными операндами, причем быстродействие
вычислений будет как минимум на порядок выше, чем в случае, если
бы обработка велась при помощи подпрограмм арифметики с плава-
плавающей запятой, знакомство с которой у нас еще впереди.
Во-вторых, в рассмотренных подпрограммах использованы те же
Принципы и алгоритмы, что и в подпрограммах 16-разрядной ариф-
арифметики, рассмотренной в первой главе. Знакомство с подпрограмма-
157
ми многоразрядной арифметики и сравнение их с 16-разрядными
аналогами позволяет глубже понять заложенные в основе подпрог-
подпрограмм алгоритмы и технику их реализации.
В настоящей главе мы ограничились подпрограммами для чисел,
длина которых не превышает 31 байт. Причина этого ограничения
следующая. Как мы знаем, у стандартного х51 есть всего 128 байт
внутренней оперативной памяти, в которой должны располагаться
не только обрабатываемые числа, но и стек, переменные остальной
части программы, буферы для информации, отображаемой на дис-
дисплее, и т. д. Отсюда ясно, что в этой главе бессмысленно было бы
рассматривать числа не только 100-, но и даже 64-байтовые — для
работы с числами такой разрядности у вас попросту не хватит внут-
внутреннего ОЗУ МК. Поэтому мы ограничились рассмотрением приме-
примеров с числами, длина которых не превышает 31 байт, тем более что
на практике вряд ли кому из читающих настоящие строки придется
реально пользоваться более чем 10-байтовыми числами, поскольку
диапазон представления этих чисел достигает B8)|0= A03)8 = 1024. Так
что 31 -байтовое ограничение длины слагаемых или сомножителей —
это вполне разумно, ибо диапазон представления 31-байтных чисел
с гарантией превышает все практические потребности, и оперирова-
оперирование с числами большей длины представляет лишь чисто теоретичес-
теоретический интерес.
Глава 4
ЗНАКОВЫЕ ЦЕЛЫЕ ЧИСЛА
Как мы уже дважды отмечали, в подавляющем большинстве мик-
микроконтроллерных применений для решения всех стоящих перед сис-
системой задач оказывается вполне достаточно уметь оперировать имен-
именно с беззнаковыми двухбайтовыми целыми числами. И при наличии
некоторых навыков вы вполне обойдетесь без арифметики знаковых.
чисел, т. е. чисел как положительных, так и отрицательных.
В самом деле, пусть вашасистема, к примеру, управляет тем или иным
регулирующим устройством. Пусть также регулируемый параметр мо-
может быть как больше, так и меньше некоторого заданного значения. При-
Примеров таких регуляторов не счесть—регуляторы освещенности, мощно-
мощности, скорости вращения, температуры, давления и т. д. (если начать пере-
перечислять все возможные применения регулирующих устройств в совре-
современной технике, потребуется не менее десятка страниц).
Если мы определим отклонение регулируемой величины от задан-
заданного значения как разность между заданным и текущим значением ре-
регулируемого параметра, то очевидно, что определенное таким обра-
образом отклонение может быть величиной как положительной, так и
отрицательной. Например, при включении системы стабилизации ча-
частоты вращения вала двигателя, когда регулируемый параметр еще да-
далек от требуемого значения, отклонение будет положительным и умень-
шающимся по модулю по мере выхода системы на режим
стабилизации. Спустя некоторое время после включения частота вра-
вращения достигнет требуемого значения, после чего на определенный
промежуток времени несколько превысит его. В последнем случае от-
отклонение станет отрицательным (в подавляющем большинстве слу-
случаев). Если вы используете пропорционально-интегрирующую сис-
159
тему регулирования, то при упомянутом превышении вам нужно бу-
будет вычитать модуль этого отклонения, умноженный на некоторую
константу, из формируемого управляющего сигнала, в то время как
вначале, пока отклонение было положительным, вам нужно было скла-
складывать эти величины. Таким образом, вы и в том, и в другом случае
будете оперировать с беззнаковыми целыми числами. Но в одном слу-
случае алгоритм обработки должен будет включать в себя операции вы-
вычитания, а в другом—операции сложения. Иными словами, вам нуж-
нужно будет разработать фактически две программы — одну для случая,
когда регулируемый параметр меньше заданного, т. е. отклонение по-
положительно, и вторую—для отрицательного отклонения, когда регу-
регулируемый параметр перевалил за требуемую отметку.
Но есть и другой путь. Как мы помним из школьного курса алгеб-
алгебры, вычитание одного числа другого равносильно сложению чисел,
второе слагаемое в котором — отрицательное число, модуль которо-
которого равен уменьшаемому. Иными словами, разность а - в эквивалент-
эквивалентна сумме а + (-в). Если мы научимся оперировать со знаковыми чис-
числами, то в рассмотренном выше случае вполне можно будет обойтись
единственным алгоритмом, который окажется одним и тем же как для
положительных отклонений регулируемого параметра от заданного зна-
значения, так и для отрицательных. Преимущества, которые мы при этом
получаем, очевидны — более короткая программа, меньшие затраты на
ее разработку и отладку. Поэтому арифметика знаковых чисел востре-
востребована, имеет право на существование, и настоящая глава посвящена
арифметическим операциям со знаковыми целыми числами.
ОСОБЕННОСТИ ПРЕДСТАВЛЕНИЯ ЗНАКОВЫХ ЧИСЕЛ
В ДВОИЧНОЙ СИСТЕМЕ
Как вы должны помнить, в алгебре десятичных чисел, которую
все мы так или иначе изучали в школе, отрицательные числа пред-
представлялись единственным, причем на первый взгляд совершенно ес-
естественным образом. Они состояли из знаковой и цифровой части.
Знаковая часть — это знак минус, записывавшийся вначале отрица-
отрицательного числа. Затем шла его цифровая часть или модуль, который
состоял из десятичных цифр, до боли знакомых из курса арифмети-
арифметики. Цифры эти формировали (с учетом своих весовых коэффициен-
коэффициентов) значение модуля. Например, рассмотрим отрицательное число
-343. Отрицательное оно потому, что перед цифрами стоит знак
минус, являющийся знаковой частью числа; модуль же его равен трем-
тремстам сорока трем. И все, никаким другим образом отрицательное
число в курсе школьной алгебры мы не представляли и даже не заду-
задумывались о том, что это возможно.
160
Вооруженные этими знаниями, вернемся к двоичной системе
счисления. Каким образом мы должны (или могли бы) представить
В ней отрицательное число? Если вы еще не знаете ответа на постав-
поставленный вопрос, он может оказаться весьма сложным. В самом деле, в
только что вспомненном представлении отрицательных десятичных
чисел мы фактически задействовали уже не десять, а одиннадцать
цифр (ведь минус в начале записи отрицательного числа играет роль
дополнительной цифры„поскольку он не совпадает ни с единицей,
ни с двойкой, ни с тройкой и т. д.). В то же время в рамках двоичной
системы ввести дополнительную, третью цифру мы не можем, т. к.
логические микросхемы оперируют только с единицей (высоким
уровнем) и нулем (низким), промежуточные состояния запрещены.
Следовательно, знаковую часть отрицательного числа нам придется
кодировать либо единицей, либо нулем, третьего не дано.
Так сложилось, что изображение знака "+" в знаковом разряде числа
Принято кодировать для двоичных чисел цифрой 0, а знака "-" — циф-
цифрой 1. Почему так? Видимо, во внимание была принята следующая
аналогия. При записи положительного числа знак "+" перед ним обыч-
обычно опускается, т. е. является незначащим. Нуль же, стоящий вначале
числа, тоже является незначащим, и при записи числа на бумаге сто-
стоящие перед ним нули также можно опустить. В общем, по этой ли
причине или по какой-либо другой, но знаковый разряд отрицатель-
отрицательного двоичного числа принято выбирать равным 1, и это просто надо
запомнить.
Как нетрудно догадаться, знаковым разрядом выбирают самый
старший (крайний слева) бит числа. Остальные разряды составляют
Цифровую часть числа, т. е. несут информацию о его модуле.
Вот здесь и начинается самое интересное. Дело в том, что воз-
возможны по меньшей мере три различных варианта разумного пред-
представления цифровой части знакового двоичного числа. Давайте рас-
рассмотрим их поподробнее.
Первый вариант идентичен рассмотренному выше представлению
десятичных чисел. Он предполагает, что цифровая часть как положи-
положительного, так и отрицательного числа всегда содержит абсолютную
величину этого числа. Например, +810 = 010002, -8,0 = 110002. Двоич-
Двоичное представление плюс восьми и минус восьми отличаются лишь са-
самым старшим, знаковым битом — в первом случае он равен 0, во вто-
втором 1. Цифровая же часть в обоих случаях одинакова и равна 10002.
Такой способ представления знаковых чисел, естественный и наибо-
наиболее приемлемый на первый взгляд, называют прямым кодом.
Однако такой естественный, казалось бы, прямой код не получил
Широкого распространения в реализуемой микроконтроллерами и
161
иными цифровыми вычислительными устройствами двоичной ариф-
арифметике. Почему? Во-первых, обработка чисел, представленных в пря-
прямом коде, требует отдельных операций над цифровой и знаковой час-
частями, а также альтернативного выполнения операций сложения и вы-
вычитания. Во-вторых, появляются два различных представления числа
0: +0 и -0. Например, для чисел +010= 000...002, -0,0 = 100...002. Ука-
Указанные недостатки сдерживают применение прямого кода в микро-
микроконтроллерной и микропроцессорной технике, и он используется пре
имущественно в операциях ввода/вывода данных.
Второй вариант — так называемый инверсный код. Представле-
Представление положительного числа в инверсном коде идентично таковому в
прямом коде: знаковый разряд равен 0, а цифровая часть представ-
представляет из себя стандартную двоичную форму записи модуля числа. Так,
+8,0= 010002, +4810= 01100002 и т. д. (не забудьте, крайний слева
ноль — это знаковый бит). Различия же между прямым и инверс-
инверсным кодом проявляются при записи отрицательных чисел. Все биты
двоичного отрицательногочисла являются результатом инверсии со-
соответствующих битов (включая знаковый) положительного числа с
тем же модулем. Например, +8,0= 010002, -81(/^ 101П2, +4810 =
= 01100002, -4810 = 10011112.
Инверсный код еще называют дополнением до 1, поскольку п-
разрядное отрицательное двоичное число К получают вычитанием
эквивалентного положительного числа Р из B"-1): К = B"-1) - Р. В
самом деле,-8,0= 111112-010002= 101112,-48,0= 111 111 12-01100002 =
2
Зачем нужно представление знаковых чисел в инверсном коде?
Достоверного ответа на этот вопрос я не знаю. Думаю, для более под-
подробного объяснения, что такое дополнительный код, о котором пой
дет речь в следующем абзаце.
Представление положительного числа в дополнительном коде
идентично таковому в прямом коде: знаковый разряд равен 0, а циф-
цифровая часть представляет собой стандартную двоичную форму запи-
записи модуля числа. Так, в дополнительном коде по-прежнему +810 =
= 010002> +48,0= 01100002 и т. д. В то же время «-разрядное отрица-
отрицательное двоичное число L получают вычитанием эквивалентного
положительного числа Риз 2": L — 2" — Р. То есть, в дополнительном
коде ^8Ю= 1ОО0О02-О10О02= 110002,-4810 = 100000002-01100002 =
= 10100002.
Теперь давайте обратим внимание на следующее очевидное со-
соотношение: L = К + 1. Убедились? Так вот, из этого соотношения
следует, что самый простой способ получить запись отрицательного
числа в дополнительном коде — это проинвертировать все биты
162
(включая знаковый О) равного ему по модулю положительного числа
(т. е. представить отрицательное число в инверсном коде), после чего
прибавить к последнему 1. В самом деле, в инверсном коде -810= 10111,;
прибавим к этому результату единицу: 101112 + 12 = 110002. А как
мы отмечали выше, в дополнительном коде -810 = 1000002 - 010002 =
110002. Еще раз: в инверсном коде-48,0= 10011112; прибавим к этому
результату единицу: 10011П2 + 12 = 10100002. А как мы отмечали
выше, в дополнительном коде -48,0= 100000002- 01100002 =
= 10100002. Все сошлось?
Еще один способ получения записи отрицательного числа в до-
дополнительном коде заключается в следующем. Возьмем двоичную
запись положительного числа с тем же модулем (она одинакова и в
прямом, и в инверсном, и в дополнительном коде) и проанализиру-
проанализируем ее побитно справа налево в поисках самой правой из входящих в
число единиц. Эту единицу и стоящие правее нее нули (если таковые
имеются) оставим без изменений, а все цифры, стоящие левее этой
оставшейся неизменной единицы, проинвертируем. Таким образом
получим запись отрицательного числа в дополнительном коде. На-
Например, для уже многократно рассмотренного -4810 процедура запи-
записи его в дополнительном коде выглядит так: +4810 = 01100002; край-
крайняя справа единица стоит в четвертом справа разряде. Ее и четыре
расположенных правее ее нуля оставляем неизменными, а единицу
в пятом справа разряде и ноль в шестом проинвертируем. Получа-
Получаем -4810= 10100002. Сошлось с полученным ранее результатом?
Доказать справедливость подобного преобразования мы предла-
предлагаем читателям в качестве самостоятельного упражнения.
Давайте теперь рассмотрим более общее определение дополни-
дополнительного кода и его свойства. Пусть знаковое число содержит п +1
Д-ичных разрядов (R =2, 3,.... 10,..., 16,...), причем крайний слева
разряд в формате числа — разряд знака, в которой плюс закодиро-
закодирован цифрой 0, а минус — цифрой Л—1 A — для двоичной системы,
9 — для десятичной, F — для шестнадцатеричной). Тогда дополни-
дополнительный код числа [AR]don определяется соотношением:
[AR] don = AR, если AR > 0; D1)
[AR] don = Г + AR, если AR < 0,
Где Г — граница числа, для целых чисел равная соответственно:
Г = 2"*1 для Д=2, Г = 10"+' для Д=10, Г = 16Я+1 для Д=16.
Иными словами, в дополнительном коде запись положительно-
положительного числа идентична его записи в прямом коде, а запись отрицатель-
163
ного числа представляет собой результат операции вычитания мо-
модуля числа AR из границы: Г - IARI.
Поскольку граница Г представляет собой фиксированную степень,
легко показать, что указанная операция вычитания сводится к по-
поразрядной операции дополнения каждой цифры числа до старшей
цифры системы счисления (поиску взаимно обратной цифры Ьр та-
такой, что а; + b; = jR-1) и суммированию полученного обратного кода
с единицей. Например, для положительного десятичного числа
04321 ю его отрицательный эквивалент в дополнительном коде равен
95678ю+1 = 9567910. Чтобы убедиться в правильности преобразова-
преобразования, сложим эти два числа: 9567910 + 04321 ю = 10000010. Поскольку
формат чисел содержит 5 разрядов (знаковый и 4 цифровых), то по-
полученная в результате сложения единица в старшем разряде выходит
за рамки формата представления и должна быть отброшена. Поэто-
Поэтому истинный результат сложения равен 00000,0. Он подтверждает пра-
правильность результата преобразования — сумма положительного и
отрицательного чисел с равными модулями всегда нулевая.
Добавлю, что алгоритм обратного перевода (дополнительного
кода отрицательного числа в прямой код) точно такой же: нужно про-
провести ту же самую поразрядную операцию дополнения каждой циф-
цифры числа до старшей цифры системы счисления и просуммировать
полученный результат с единицей. Подобное преобразование допол-
дополнительного кода отрицательного числа в прямой код необходимо для
нахождения модуля отрицательного числа. Для проверки справед-
справедливости алгоритма последнего преобразования найдем модуль от-
отрицательного десятичного числа 9567910: 19567910 I = 0432010 + 1 =
= 0432110. Как видите, сходится.
После того, как мы подробно познакомились с представлением
знаковых чисел в дополнительном коде, у тех, кто столкнулся с пред-
представленным материалом впервые, должен возникнуть следующий
вопрос: а зачем, собственно, нужно подобное представление? Ответ
прост. Как будет показано ниже, применение дополнительного кода
позволяет производить сложение и вычитание знаковых чисел по
одному и тому же алгоритму, при этом вычитание заменяется сло-
сложением уменьшаемого с отрицательным числом, равным по моду-
модулю вычитаемому. И главное, при использовании дополнительного
кода обработка при сложении знаковой и цифровой частей чисел
производится по одним и тем же правилам, причем правильный знак
результата формируется автоматически. Увы, ни прямой, ни обрат-
обратный коды такими свойствами не обладают, в связи с чем при их ис-
использовании вам нужно проводить соответствующую коррекцию
результата после проведения операции суммирования или просто
164
оставить попытки складывать знаковые числа, представленные в пря-
прямом или инверсном коде, и работать только с дополнительным ко-
кодом. И наслаждаться тем, что результат сложения при этом сразу
получается правильный и не требует каких-либо коррекций.
И в заключение приведу таблицу интерпретации 4-битовых зна-
знаковых чисел в прямом, инверсном и дополнительном кодах.
Таблица 18
Интерпретация 4-битовых знаковых чисел в различных кодах
Биты
0111
0110
0101
0100
0011
0010
0001
0000
1000
1001
1010
1011
1100
1101
1110
1111
Прямой код
+7
+6
+5
+4
+3
+2
+1
+0
-0
-1
-2
-3
-4
-5
-6
-7
Инверсный код
+7
+6
+5
+4
+3
+2
+1
+0
-7
-6
-5
-А
-3
-2
-1
-0
Дополнительный код
+7
+6
+5
+4
+3
+2
+1
+0
-8
-7
-6
-5
-А
-3
-2
-1
СЛОЖЕНИЕ И ВЫЧИТАНИЕ ЗНАКОВЫХ ЧИСЕЛ
В ДВОИЧНОЙ СИСТЕМЕ
После того, как мы ознакомились с различными вариантами пред-
представления знаковых чисел, давайте разберемся, как осуществлять над
ними арифметические операции. Естественно, начнем со сложения и
вычитания.
Для чисел, представленных в прямом коде, все просто. Предпо-
Предположим, что мы складываем два знаковых числа. Как нетрудно сооб-
сообразить, при этом возможны два различных варианта соотношения
слагаемых между собой. Первый — когда оба слагаемых имеют оди-
одинаковые знаки, и второй — когда знаки слагаемых различны. Также
Нетрудно сообразить, что алгоритмы сложения для первого и второ-
второго случая будут разными. Вкратце рассмотрим их.
В первом случае нам достаточно выделить модули обоих слага-
слагаемых, сложить их как простые беззнаковые целые числа, подобно
тому, как это делалось в предыдущих главах, и приписать этой сум-
сумме знак, который имели наши слагаемые (ведь сумма положитель-
165
ных чисел всегда положительна, а отрицательных чисел всегда от-
отрицательна).
Во втором случае алгоритм гораздо более сложный. Вначале нуж-
нужно определить, какое из слагаемых имеет больший модуль, а какое
меньший. Далее из большего модуля нужно вычесть меньший в пол-
полном соответствии с рассмотренной ранее арифметикой беззнаковых
чисел. И на завершающем этапе нужно присвоить разности тот знак,
который был у числа с большим (ударение на первом слоге) моду-
модулем.
Итак, сложение двух чисел в прямом коде предполагает целый
ряд операций. Вначале нужно выяснить, одинаковые знаки у слагае-
слагаемых или нет. Если да, то нам предстоит сохранить знаки (точнее знак)
слагаемых, выделить их модули, сложить последние, после чего при-
присвоить сумме сохраненный знак. Если знаки слагаемых разные, то
нужно выделить их модули, сравнить их между собой, сохранить знак
большего из них, вычесть из большего модуля меньший и припи-
приписать ему сохраненный знак. Вот так, ни больше, ни меньше.
Не стану утомлять вас описанием того, как осуществляется вы-
вычитание знаковых чисел, представленных в прямом коде — оно не
проще сложения, и увы, его алгоритмы отличны от алгоритмов сло-
сложения. Для сложения и вычитания знаковых чисел в прямом коде
нам нужно иметь четыре различных алгоритма, каждый из которых
разбивается на 4-5 подпунктов. Это весьма неэффективно. Как мы
увидим в дальнейшем, сложение и вычитание знаковых чисел в до-
дополнительном коде осуществляются быстрее и проще.
С инверсным кодом также есть свои проблемы. На рис. 77 при-
приведены четыре различных варианта сложения 4-разрядных знаковых
чисел, представленных в инверсном коде. При этом в каждом из ва-
вариантов справа представлены складываемые "в столбик" двоичные
знаковые слагаемые и их суммы, а слева — десятичные эквиваленты
записанных слагаемых и суммы последних в прямом десятичном коде.
Нетрудно заметить, что результаты сложения верхних двух ва-
вариантов правильны, чего не скажешь о нижних. В обоих последних
вариантах наблюдается переполнение двоичной суммы — она уже
не умещается в 4 разрядах и требует для размещения 5 бит. В то же
время правильные десятичные суммы C10 и -710), полученные в ре-
результате сложения десятичных эквивалентов наших двоичных сла-
слагаемых, будучи переведенными в двоичные знаковые числа @0112 и
10002), вполне комфортно уместились бы в 4 битах.
Как видим, сложение чисел, представленных в инверсном коде,
имеет некоторые преимущества в сравнении с прямым кодом. Оно
осуществляется весьма просто, причем не по двум, а всего по одно
166
0101 г .(-5)ю .1010,
ОО1Ог (+ 2),0 оою2
0111, (-3)ю 1100,
Рис. 77. Сложение знаковых чисел в инверсном коде
му-единственному алгоритму. К этому можно добавить, что оно даже
дает правильные результаты... Но, увы, не всегда. И это основной
недостаток представления знаковых чисел в инверсном коде.
Конечно, рассмотренные ошибочные результаты могут быть
скорректированы. Если, например, в двух нижних вариантах на
рис. 77 перенос, сформировавший единицу в старшем разряде,
прибавить к сумме, то в итоге получится правильный результат.
Однако и здесь не все так просто. Если переполнение возникает не
только в результате сложения знаковых чисел, но и в результате
сложения их модулей, то корректировать сумму этим переполне-
переполнением нельзя. Таким образом, сложение чисел в инверсном коде без
дополнительной коррекции результата не всегда правильно, а кор-
коррекция неоднозначна и требует действий, определяющих ее при-
применимость. Словом, инверсный код тоже не лишен существенных
недостатков.
Вот мы и добрались до сложения чисел в дополнительном коде.
На рис. 78 приведены примеры сложения тех же самых знаковых чи-
чисел, что и на рис. 77, но при этом двоичные числа представлены в
дополнительном коде. Как нетрудно убедиться, все четыре рассмат-
рассматриваемых варианта двоичного сложения в данном случае дают пра-
правильный результат! Таким образом, сложение знаковых чисел, пред-
представленных в дополнительном коде, осуществляется совершенно ана-
аналогично тому, как мы складывали целые беззнаковые числа, причем
знаковые разряды складываются не отдельно от цифровой части, а в
том же цикле сложения, и при этом мы автоматически, без необходи-
необходимости что-то корректировать, получаем правильное значение как
Цифровой части суммы, так и ее знака.
167
0101, .(-5)„ 1011г
0010, (+ 2)„ 00Ю2
0111 г (-3I0 1101,
.0101, .(-5I0 1011,
1110, (-2I0 111Q,
10011г (-7),0 11001,
! !
игнорировать игнорировать
Рис. 78. Сложение знаковых чисел в дополнительном коде
Теперь сравним описанные коды. Сложение знаковых чисел в
прямом коде требует предварительного анализа модулей слагаемых,
и в зависимости от соотношения между ними сумма определяется
по тому или иному алгоритму, причем непростому, требующему
различных действий над знаковыми частями слагаемых и цифровы-
цифровыми. Сложение знаковых чисел, представленных в инверсном коде,
осуществляется намного проще, так же, как мы складываем обыч-
обычные беззнаковые двоичные числа, но результат сложения требует от-
относительно непростой корректировки (и предварительного выясне-
выяснения, нужна ли она или сумма верна). И лишь сложение знаковых чи-
чисел, представленных в дополнительном коде, не только автомати-
автоматически, без каких-либо коррекций дает правильный результат, но и
осуществляется предельно просто: вне зависимости от соотношения
модулей и знаков слагаемых они складываются точно также, как мы
складываем обычные беззнаковые целые числа. В общем, и просто, и
правильно. Так что теперь и вам очевидно, почему именно дополни-
дополнительный, а не прямой или инверсный коды, получил столь широкое
распространение в цифровой технике.
Пошли дальше. Мы уже говорили о том, что вычитание может
быть заменено сложением, при котором у второго слагаемого дол-
должен быть инвертирован знак. То есть, при замене вычитания сложе-
сложением положительное вычитаемое должно быть заменено отрицатель-
отрицательным вторым слагаемым с тем же модулем, а отрицательное вычита-
вычитаемое— положительным вторым слагаемым (см. рис. 79).
Таким образом, если мы сможем легко превращать положительное
число в равное ему по модулю отрицательное, и наоборот, мы можем
168
010Ь 0101,
'РОЮ, > 11102
10011,
101U
001Q2
010h
'11Ю2
. 10112
ООЮг
1101 2
Рис. 79. Вычитание знаковых чисел в дополнительном коде
вообще обойтись без команд вычитания, сведя их к сложению! Именно
благодаря этому в ряде микроконтроллеров (в частности, в очень попу-
популярных некогда МК семейства х48) команды вычитания напрочь отсут-
отсутствовали. И это не мешало разработчикам реализовывать на подобных
МК довольно сложные вычислительные алгоритмы — подумаешь, нет
Вычитания, и без него справимся! Конечно, прогресс микроэлектрони-
микроэлектроники привел к тому, что "безвычитательные" МК практически исчезли из
обращения. Но наличие команд вычитания не упростило арифметику
знаковых чисел. По-прежнему при работе с ними часто оказывается го-
гораздо удобнее изменить знак у числа, чем менять весь алгоритм, встав-
вставляя в него команды и подпрограммы вычитания вместо сложения.
А теперь, после рассмотрения теоретического примера, как вы-
вычитание может быть заменено сложением, давайте посмотрим, как с
учетом этого реализуется аппаратное вычитание в арифметическо-
логических устройствах (АЛУ) микроконтроллеров и микропроцес-
микропроцессоров. Вспомним, что во втором подразделе первой главы мы знако-
169
мились с тем, как устроена одна ячейка полного одноразрядного дво-
двоичного сумматора (рис. 8) и как из восьми последовательно соеди-
соединенных таких сумматоров формируется полный восьмибитный сум -
матор (рис. 9), реализующий в МК команды ADD, ADDC. Как
явствовало из рис. 8, однобитный сумматор требовал для реализа-
реализации 12 отдельных логических элементов малой степени интеграции,
а восьмибитный 12-8 = 96. Поскольку на реализацию каждого такого
логического элемента уходит 5-8 транзисторов, то нетрудно посчи-
посчитать, что сумматор, реализующий команды ADD, ADDC, "обходит-
"обходится" микроконтроллеру почти в тысячу транзисторов. А ведь поми-
помимо сумматора МК должен еще иметь память, регистры, шинные
формирователи, буферные элементы, логическую часть АЛУ и т. д.
В то же время возможности микроэлектроники восьмидесятых го-
годов прошлого столетия были весьма ограничены, и МК тех времен
состояли всего из двух-трех десятков тысяч транзисторов, больше
разместить на кристалле было трудно.
Очевидно, что разработчики всячески пытались экономить на
внутренних узлах МК, оставляя лишь те, без которых действительно
было не обойтись. И уж тем более они никогда не делали узел на ты-
тысяче транзисторов, если его, слегка подумав, можно было сделать на
двух сотнях. Именно поэтому в составе МК обычно отсутствует са-
самостоятельный восьмибитный вычитатель, аналогичный суммато-
сумматору, рассмотренному на рис. 8 и 9, а команды вычитания реализует
этот самый сумматор, дополненный восемью логическими элемен-
элементами "исключающее ИЛИ" (рис. 80).
На рис. 80 собственно сумматор, структура которого приведена
на рис. 9, изображен в виде трапеции с изломом в середине большего
основания. Нижняя часть этого основания—это входы уп_1.. .у0, мень-
меньшее основание—выходы sn_i...sQ. Сигналы xn_i...Xq подаются на сум-
сумматор не напрямую, а каждый через свой элемент "Исключающее
ИЛИ", второй вход которого управляется логическим сигналом вы-
читание/сложение. Когда необходимо осуществить операцию сложе-
сложения, этот сигнал имеет нулевой логический уровень. При этом на
выходах элементов хп_1 ¦ ¦ .х^, сигналы хп_1.. .х^ имеют те же логические
уровни, что и были у них на входах элементов. Соответственно, ког-
когда необходимо осуществить операцию вычитания, упомянутый уп-
управляющий сигнал единичный, и элементы "Исключающее ИЛИ"
из повторителей превращаются в инверторы, подавая в АЛУ инвер-
инвертированные сигналы xn_v..xfr
Обратите внимание на то, что этот управляющий сигнал еще и
используется в качестве первого переноса с^. При сложении он равен
0, а при вычитании — единице.
170
Суммирование/вычитание
Сто
V,
|
I
IS
A
X
Coufn
Рис. 00. Сумматор/вычитатель арифметическо-логического устройства МК
Что все это нам дает? А вот что. Когда мы осуществляем опера-
операцию сложения, в рассматриваемом АЛУ мы, как обычно, складываем
^h-i-'-^b с Уп-\ •• -Уо в полном соответствии с тем, что было рассказано
в первой главе. При вычитании же мы вместо вычитаемого подаем
на сумматор число, все биты которого проинвертированы. Кроме
171
того, при этом еще и с0 установлен в 1, что увеличивает на 1 резуль-
результат сложенияу„_уу0 спобитно проинвертированным *:„_,...х^,. До
гадались? Если нет, то напомню, что в любимом и единственно при-
применяемом нами представлении в дополнительном коде для того, что-
чтобы изменить знак числа, нужно проинвертировать все его биты и
прибавить к результату инвертирования 1. Следовательно, цепочка
элементов "Исключающее ИЛИ" с реализованным, как показано па
рис. 80, сигналом управления осуществляет аппаратное изменение
знака второго слагаемого, заменяя отрицательное слагаемое положи-
положительным, а положительное — отрицательным (алгоритм изменения
знака и в том, и в другом случае, как мы убедились выше, одинако-
одинаковый). Поэтому самостоятельный тысячетранзисторный вычитатель
оказался попросту не нужен — дополненный рассмотренным логи-
логическим узелком сумматор вполне справляется не только со сложени-
сложением, но и с вычитанием. А что нам было нужно, как не это?
Устройство суммирования/вычитания, приведенное на рис. 80,
без каких-либо изменений может использоваться для выполнения
инструкций ADD и SUBB, причем инструкция вычитания выполня-
выполняется без учета заема, т. е. также как если бы мы вначале очистили флаг
переноса, а потом сделали SUBB. Кстати, при выполнении вычита-
вычитания подобным устройством Coutn принимает инверсное значение
флага заема, т. е. противоположное тому, что мы ожидаем увидеть
во флаге переноса после выполнения SUBB.
Чтобы это устройство могло выполнять сложение и вычитание с
учетом переноса и заема соответственно, нужно отсоединить вход Cin0
от сигнала «суммирование/вычитание» и непосредственно управлять
входом Cin0. Случай сложения тривиален, Сте — это перенос, который
мы хотим учесть при сложении. Несложно увидеть, что в случае вычи-
вычитания на вход Cin0 мы должны подать инвертированный сигнал заема
(т. е. 1, если мы вычитаем без заема или 0, если мы вычитаем с заемом).
Итак, мы запомнили, что операция вычитания вполне может быть
заменена операцией сложения, нужно лишь обратить знак у вычита-
вычитаемого, а затем сложить его с первым слагаемым. Мы также познако-
познакомились с тем, как аппаратно обращается знак операнда внутри АЛУ
микроконтроллера. Теперь настала пора познакомиться с подпрог-
подпрограммами, при помощи которых мы с вами в любой момент можем
обратить знак у одного из операндов, превратив положительное чис-
число в отрицательное, и наоборот.
NEG16A:
;; Подпрограмма обращения знака у числа ;;
172
Вход:
R5R4 - число со знаком (например, 1)
Выход:
R5R4 - число с противоположным знаком (-1)
Используемые регистры:
А
Требует свободных байт в стеке:
2
MOV
CPL
MOV
MOV
CPL
MOV
MOV
ADD
MOV
MOV
ADDC
MOV
A, R4
A
F4, A
A. F5
A
R5, A
A. R4
A, «1
R4. A
A, F5
A. «0
R5, A
; инвертировали все биты числа
; добавили 1
RET
Рис. 81. Подпрограмма обращения знака двухбайтового числа, вариант 1
NEG16B:
Подпрограмма обращения знака у числа
Вход:
R5R4 - число со знаком (например, 1)
Выход:
R5R4 - число с противоположным знаком (-1)
Используемые регистры:
А
Требует свободных байт в стеке:
2
HOV A, R4
173
CPL
ADD
MOV
MOV
CPL
ADDC
MOV
A
A.
R4,
A,
A
A,
R5,
«1
A
R5
«0
A
инвертировали все биты числа и добавили 1
RET
Рис. 82. Подпрограмма обращения знака двухбайтового числа, вариант 2
NEGN:
Подпрограмма обращения знака у N-байтного числа
Вход:
R1 - адрес числа со знаком (например, числа 1)
(более старшие байты чисел располагаются по более стариим
адресам, т.е. для двухбайтовых чисел R1 будет
указывать на МЛБ, a R1+1 - на СТБ)
R2 - размер числа в байтах
Выход:
R1 - адрес числа с противоположным знаком (-1)
Используемьс регистры:'
А, В
Требует свободных байт в стеке:
2
MOV В. R2
SETB С ; сложение с учетом CY (будет добавлена 1)
NEGN_1:
MOV A, ©R1
CPL A
ADDC A, «0
MOV @F1, A ; сложили байты двух чисел
INC R1
DJNZ В, NEGN_1 ; повторить для следующего байта
MOV A, R1
174
CLR С
EUBB A, R2
MOV R1, A ; восстановили R1
RET
Рис. 83. Подпрограмма обращения знака многобайтового числа
Мы познакомились практически со всем, что необходимо знать о
представлении, сложении и вычитании знаковых целых чисел. Одна-
Однако это знакомство для первого раза довольно сложное. Чтобы легче
понять все то, о чем говорилось в текущем разделе, можно воспользо-
воспользоваться одним довольно любопытным графическим представлением.
Прежде, чем перейти к нему, давайте обратим внимание на сле-
следующий факт. Числа, с которыми оперирует микроконтроллер, все-
всегда ограничены. Так, если под хранение чисел, используемых микро-
микроконтроллером при вычислениях, мы отводим лишь по одному байту
на число, это означает, что самое большое из чисел не может превы-
превысить +127 @11111112), а самое малое (самое большое по модулю от-
отрицательное число) не может быть менее -128 A00000002). Если для
хранения числа мы отведем два байта, то самое большое из чисел не
сможет превысить +32767 @1111111111111112), а самое малое (са-
(самое большое по модулю отрицательное число) не сможет быть
менее -32768 A0000000000000002) и т. д. Но в любом случае, сколько
бы байт не отводилось под хранение числа, мы заранее можем ука-
указать самое большое и самое малое из чисел, с которыми будет опери-
оперировать наш микроконтроллер — с большими и меньшими числами,
чем определенные аналогично вышеописанному, МК не справится.
Иными словами, в отличие от нас, которые могут на бумажке напи-
написать сколь угодно большое и сколь угодно малое число, хоть сто-, хоть
двестиразрядное (хватило бы бумаги!), микроконтроллеры принци-
принципиально оперируют с числами ограниченной величины, и понятия
плюс бесконечность или минус бесконечность для них неопределены.
Что из этого следует? А вот что. Если мы, люди, для графическо-
графического представления знаковых чисел используем две идущие в беско-
бесконечность из точки 0 полуоси (одна, положительная, вправо, а другая,
отрицательная — влево), то у микроконтроллеров эти бесконечные
полуоси вырождаются в два отрезка, приставленные друг к другу в
нуле. На одном (правом) расположены положительные числа и ноль,
На другом (левом) отрицательные числа. Все это изображено на
рис. 84, где для простоты числа, с которыми оперирует микроконт-
микроконтроллер, ограничены 4 разрядами (от +7 до -8).
175
-00 0 +00
4 ¦ >
а)
-В -6 -4 -2 0 *2 *4 +6
I I I I I I I I I I I I И I I
-7 -5 -3 -1 *1 +3 +5 *7
Рис. 84. Числовые полуоси и числовые отрезки
Далее, коль скоро представление чисел, с которыми оперирует МК,
ограничено, и их графическое представление конечно как слева, так
и справа, давайте "завернем" эти два отрезка в окружность, как пока-
показано на рис. 85.
1100+-4 *4+0100
1011 \ ''°101
V*6 *6
0110
0111
1000
Рис. 65. Числовая окружность для знаковых целых чисел ограниченной разрядности
И вот здесь вы можете воочию увидеть все те свойства знаковых
целых чисел в дополнительном коде, о которых мы говорили выше.
Крайнюю верхнюю точку окружности занимает число 0. Вправо от него,
по часовой стрелке расположены по возрастанию числа +1, +2,..., +7.
Слева, против часовой стрелки, расположены по убыванию числа -1,
-2, ..., -7, -8. При этом если двигаться от 0 по часовой стрелке, мы
последовательно пройдем по возрастанию все положительные числа
от +1 до +7 после чего переместимся в область отрицательных чисел
на самое маленькое из них (-8). Дальнейшее движение по часовой
стрелке переместит нас на -7, затем на -6 и т. д. (обратите внимание,
176
и здесь движение по часовой стрелке соответствует росту чисел, по-
поскольку -6 больше, чем -7, -5 больше, чем -6 и т. д.).
Кстати, если расположенные на этой числовой окружности дво-
двоичные числа 00002,00012,00102,..., 11102,11112 интерпретировать как
беззнаковые, они также расположены при движении по часовой стрел-
стрелке по возрастанию.
Теперь рассмотрим свойства этой числовой окружности. Во-пер-
Во-первых, на ней всего один нуль, а не два, как в случае с представлением
знаковых чисел в прямом коде. Кстати, если вы попытаетесь полу-
получить дополнение до двух числа 0 (т. е. как бы минус 0), вы получите
все тот же нуль. Не верите? Инвертируйте все биты числа 0 и полу-
получите 11112. Далее прибавьте к полученной 1, выйдет 100002. Но по-
поскольку мы ограничены четырехразрядным представлением, край-
крайнюю слева единицу мы должны отбросить. Что в итоге получилось?
00002, как и должно было быть — плюс ноль и минус ноль совпада-
совпадают друг с другом.
Помимо нуля, у нас есть еще одно число, которое после опера-
операции дополнения до двух переходит само в себя— это 10002. В самом
деле, инвертируем все его биты, и получим 01П2- Прибавим к полу-
полученному единицу, и как в руках у фокусника увидим неизменивше-
неизменившееся после описанной процедуры 10002.
Что означает последний факт? То, что в представлении в допол-
дополнительном коде самому маленькому числу нельзя подобрать равное
ему по модулю положительное число. Всем остальным числам мож-
можно, а самому маленькому — нельзя, нет такого среди чисел заданной
разрядности. Подтверждение тому — диапазон представления одно-
однобайтных (от-128 до +127) и двухбайтных (от -32768 до +32767) чи-
чисел. Как видите, в первом случае действительно нет числа +128, а во
втором +32768. Для чисел большей разрядности мы не рассматрива-
рассматривали диапазон представления> но поверьте на слово, там все то же са-
самое — модуль самого маленького числа на 1 больше модуля самого
большого.
Идем дальше. Сложение знаковых двоичных чисел интерпрети-
интерпретируется на цифровой окружности как движение по часовой стрелке на
количество шагов, равное второму слагаемому. Например, -5 + 2.
Станем в точке, соответствующей числу -5, и переместимся на два
шага по часовой стрелке. Нетрудно увидеть, что получим -3, в пол-
полном соответствии с правилами знакомой нам всем арифметики.
Теперь давайте прибавим 3 к числу 6. Если мы попробуем реали-
реализовать это на нашей цифровой окружности, мы получим ... —7!
Что это означает? То, что произошло переполнение, сумма 3 и 6
больше самого большого из положительных чисел, с которым мы
177
можем оперировать (в выбранном примере это число равно +7). Та-
Таким образом, при сложении знаковых чисел нужно иметь критерий
того, что сумма не вышла за границы представления (т. е. не больше
самого большого, в данном случае +7, и не меньше самого маленько-
маленького, -8). О том, каков этот критерий, мы поговорим чуть ниже.
Кстати, интерпретация операций сложения и вычитания при по-
помощи цифровой окружности наглядно демонстрирует, каким об-
образом операция вычитания может быть заменена операцией сло-
сложения с дополнением вычитаемого до 2. Например, мы хотим
вычесть из беззнакового числа 6 беззнаковое число 3. Для этого рас-
рассмотрим беззнаковый вариант цифровой окружности, где 00002 —
этоО,00012 — это110,....,01112 — это710,10002 — это810,..., 11102—
это 14,0, 11112 — это 1510. Дополнение двоичной тройки до 2 в дан-
данном случае — 11012=1310. Станем в точке 6 и совершим 13 шагов
вдоль окружность по часовой стрелке. Конечная точка этого путе-
путешествия — число 3. Как и должно было быть — если из 6 вычесть 3,
получим 3. Таким образом, вычитание 3 из 6 эквивалентно сложе-
сложению шестерки с тройкой в дополнительном коде, что является след-
следствием того, что микроконтроллеры оперируют с числами ограни-
ограниченной разрядности. -
И в заключение, как было обещано, несколько слов о том, как
выявлять ситуацию арифметического переполнения при сложении
и вычитании знаковых чисел. Очевидно, что результат сложения или
вычитания не должен выходить за пределы представления чисел. Если
мы используем «битовое представление знаковых чисел, нужно что-
чтобы они находились в пределах от -2" до 2"~!-1. Если это оказывает-
оказывается не так, то говорят, что произошло арифметическое переполнение.
Чтобы быть уверенным в том, что наши вычисления корректны, нуж-
нужно уметь детектировать факт наступления этого переполнения.
На рис. 86 представлены четыре возможных случая сложения двух
чисел с модулями 7 и 2. Мы используем в данном случае четырехби-
четырехбитовое представление, с тремя цифровыми битами и одним знаковым.
Очевидно, что когда слагаемые имеют разные знаки, переполнения
произойти не может, поскольку модуль суммы в этом случае не боль-
больше модуля каждого из слагаемых, а последние находятся внутри за-
заданных пределов представления. Переполнение возможно тогда, ког-
когда знаки слагаемых совпадают. В этом случае модуль суммы превы-
превышает модуль каждого из слагаемых и, следовательно, может выйти
за пределы диапазона от -2 до 2"~1-\.
Рассмотрим левый верхний пример, где складываются положи-
положительные +7 и +2. В результате двоичного сложения возникает пере-
переполнение цифровой части A112 + 0Ю2 = 10012). Иными словами, сум-
178
7I0 Ю012
2)ю 00102
5IС 10112
с4=0
сЗ=0
.011U .(-7)ю .10012
1110г (-2I0 11Юг
101012 (-9I0 1011U
с4=1 с4=1
С3=1 сЗ=О
Рис. 86. Пример, демонстрирующий арифметическое переполнение
ма 7 и 2 не умещается в трех битах и требует для представления че-
четырех бит. Сейчас на время забудем, что четвертый бит у нас знако-
знаковый, и зафиксируем лишь то, что в результате сложения возник пе-
перенос из второго разряда в третий (напомню, нумерация разрядов
начинается с нулевого). Обозначим перенос из второго разряда в тре-
третий как су В данном случае сэ=1.
Далее, поскольку оба слагаемых — положительные числа, знако-
знаковые разряды их равны 0. При сложении два нуля дадут 0, к ним при-
прибавим перенос с3 и получим единицу в третьем разряде. Перенос из
третьего в четвертый разряд, который мы обозначим как с4, в рас-
рассматриваемом случае равен 0: с4=0. Итак, при сложении +7 и +2, где у
нас возникает арифметическое переполнение, Cj-1 и с4-0.
Теперь рассмотрим правый нижний пример, где складываются
-7 и -2. Сумма равна -9, и она вновь не умещается в трех цифровых
битах, требуя четырех, что свидетельствует об арифметическом пе-
переполнении. Но при этом Cj=O, и это принципиально. Обратите вни-
внимание, что при любом сложении отрицательных чисел, сопровожда-
сопровождающемся переполнением, Cj=O. В самом деле, самое большое по моду-
модулю отрицательное число в нашем четырехбитовом представлении —
это -8 A0002). Переполнение наступит, какое бы отрицательное чис-
число мы не прибавили бы к нему: -1 A1112),-2 A1102), —3 A1012),...,
-7 A0012),-8 A0002). Но если мы сложим с тремя цифровыми (млад-
(младшими) битами числа -8 три цифровых бита любого из перечислен-
перечисленных отрицательных чисел, то мы получим число, не превышающее
1112, без какого-либо переполнения.
179
Если мы будем прибавлять отрицательное число к -7, то пере-
переполнение возникнет, если вторым слагаемым будут -2, -3, ..., -8.
Прибавьте к трем цифровым битам числа —7 три цифровых бита
любого из перечисленных в предыдущем предложении отрицатель-
отрицательных чисел и убедитесь, что и в этом случае с3=0. Можно продолжить
рассмотрение и для остальных отрицательных чисел, но думаю, что
не стоит, и без этого уже всем должно стать ясно, что при любом
сложении отрицательных чисел, сопровождающемся переполнени-
переполнением, с3=0.
В то же время оба слагаемых — отрицательные числа, И знаковые
биты их — единичные. Складывая их, получаем 0 и перенос в следу-
следующий разряд, т. е. с4=1.
Итак, в первом случае, сопровождающемся арифметическим пе-
переполнением, с3=1 и с4=0. Во втором случае с3=0 и с4=1. Для случаев,
когда не было переполнения, переносы из второго разряда в третий
Cj и из третьего в четвертый с4 имели одинаковые знаки — либо оба
нули, либо оба единицы. Таким образом, мы на простом примере
убедились, что критерием арифметического переполнения при сло-
сложении знаковых чисел является неравенство переносов из второго
по старшинству разряда суммы в старший (знаковый) разряд и из
третьего во второй. Если же эти переносы (в нашем случае Cj и с4)
одинаковы, т. е. оба нули или оба единицы, то переполнения при сло-
сложении не было и сумма корректна.
А как практически определять, корректна ли сумма или в резуль-
результате сложения было арифметическое переполнение? Искать оба упо-
упомянутых переноса и анализировать их? К счастью, в этом нет необ-
необходимости. Оба эти переноса внутри арифметическо-логического
устройства микроконтроллера поступают на элемент "Исключающее
ИЛИ", осуществляющий логическую функцию XOR (рис. 87).
х„ V.
С«
ov(psw.2)
Рис. 87. Формирование флага OV в микроконтроллере
180
Выход элемента "Исключающее ИЛИ" соединен с разрядом PSW.2
регистра PSW нашего микроконтроллера, называемого битом (или
флагом) переполнения. Таким образом, анализ на наличие арифме-
арифметического переполнения осуществляется в нашем микроконтролле-
микроконтроллере аппаратно, и нам только нужно после операции сложения прове-
проверить, равен бит PSW.2 нулю или нет. Если да, то результат сложения
корректен, если нет (т. е. бит равен 1), то было переполнение и нуж-
нужно принимать какие-то меры.
СРАВНЕНИЕ ЧИСЕЛ СО ЗНАКОМ
Нами уже были приведены и частично рассмотрены подпрограм-
подпрограммы СМР16 и CMPN (рис. 48 и 66), позволяющие сравнивать целые
числа. Необъясненным осталось лишь сравнение чисел со знаком.
Идея тут простая. Мы вычисляем разность сравниваемых чисел. По
знаку разности мы можем судить о том, какое из двух чисел меньшее
(большее). Кажется просто? В общем-то, так и есть. Хотя, существует
небольшой нюанс, связанный с тем, что при вычитании чисел со зна-
знаком может произойти переполнение, и мы получим неправильный
знак у разности. А что значит неправильный? Это значит противо-
противоположный, т. е. знаковый разряд получится равным нулю, когда дол-
должен был быть равен 1, и наоборот. Что можно сделать? Можно вы-
вычислить функцию «исключающее ИЛИ» для знака разности, полу-
полученного при вычитании сравниваемых чисел, и флага переполнения,
установленного последней инструкцией вычитания. А что получит-
получится в результате? Автоматическая коррекция неправильного знака раз-
разности при переполнении. Это реализовано следующими инструкци-
инструкциями в подпрограмме СМР16: JNS CMP16_1, CPL OV и в подпрограм-
подпрограмме CMPN: JNS CMPN_2, CPL OV.
УМНОЖЕНИЕ ЗНАКОВЫХ ЧИСЕЛ В ДВОИЧНОЙ СИСТЕМЕ
После ознакомления с различными вариантами сложения и вы-
вычитания двоичных знаковых чисел перейдем, как нетрудно догадать-
догадаться, к умножению. Здесь мы рассмотрим три различные вычислитель-
вычислительные схемы.
Первая—самая очевидная, та, которой мы пользовались в школь-
школьном курсе алгебры. Напомню, что заключалась она в следующем.
Вначале мы умножали друг на друга модули сомножителей, получая
модуль произведения. Далее мы присваивали ему знак плюс, если оба
сомножителя были одного знака (либо положительные, либо отри-
отрицательные). Если же один из сомножителей был положительным, а
другой отрицательным, то мы присваивали произведению знак ми-
минус. Вспомнили? Если нет, то еще одно напоминание:
181
(+5) (+7) = (+35)
(-5) • (-7) = (+35)
(+5) (-7) = (-35)
(-5) (+7) = (-35)
Рис. 88. Формирование знака произведения знаковых чисел
Многие уже, наверное, догадались, что организовать умноже-
умножение знаковых двоичных чисел можно точно также — перемножить
модули сомножителей, оставив произведение положительным, если
знаки сомножителей совпадают, и преобразовав произведение в от-
отрицательное число (с тем же модулем), если знаки сомножителей
разные. Именно таким образом организована подпрограмма
SMUL16A. Однако прежде, чем рассмотреть ее, напомню, что наи-
наиболее простой способ найти модуль отрицательного двоичного чис-
числа (равно как и само это число по его модулю) — это инвертиро-
инвертировать все биты числа (модуля), после чего прибавить к результату
инверсии 1.
SMLL16A:
Подпрограмма умнохения двух целых 16-ти разрядных чисел со
знаком
Вход:
R3R2 - множимое
R5R4 - множитель
Выход:
R7R6R5R4 - 32-х разрядное произведение (мнохимое*мнохитель)
Флаги:
CY - признак переполнения 16-ти разрядного произведения
Используемые регистры:
А. В
Требует свободных байт в стеке:
2+1+2=5
Использует подпрограммы:
MUL16A
MOV A, R3
RLC А ; CY = знак множимого (R3R2)
182
MOV A, R3
XRL A, R5 ; ACC.7 = знак произведения
RRC A ; ACC.7 = знак множимого, АСС.6 = знак произведен""
PUSH ACC ; сохранили знаки ММ и ПР, они понадобятся позже
JNS SMUL16A_1 ; если множимое >= 0, его модуль у нас уме есть
MOV
CPL
ADO
MOV
MOV
CPL
ADDC
MOV
SMUL16A_1:
MOV
JNS
MOV
CPL
ADO
MOV
MOV
CPL
ADOC
MOV
A.
A
A.
R2,
A,
A
A.
R3,
A.
SMU
A,
A
A,
R4.
A.
A
A,
R5.
R2
#1
A
R3
«0
A
R5
Lie
R4
#1
A
R5
КО
A
получили модуль множимого в R3R2
SMUL16A_2 ; если множитель >= 0. его модуль у нас уже есть
получили модуль множителя в R5R4
SMUL16A_2:
LCALL MUL16A ; вычислили произведение модулей МН и ММ
В.7 = знак множимого, В.6 = знак произведения
В. 7, EMUL16A_3 ; множимое в R3R2 было >= 0, мы его не
изменяли
POP
JNB
MOV
CPL
ADO
В
A,
A
A,
B.i
R2
#1
183
MOV
MOV
CPL
ADDC
MOV
EMUL16A_3:
JNB
MOV
CPL
ADD
MOV
MOV
CPL
ADDC
MOV
MOV
CPL
ADDC
MOV
MOV
CPL
ADDC
MOV
SMUL16A_4:
MOV
RLC
MOV
ADDC
MOV
MOV
ADDC
ORL
CLR
JZ
R2, A
A, R3
A
A, #0
R3, A
B.6.
A. R4
A
A, It1
R4, A
A, R5
A
A, flO
R5. A
A, R6
A
A, DO
R6, A
A, R7
A
A, flO
R7, A
A. R5
A
A, R6
A, flO
В, А
A. R7
A. 00
А. В
С
SMUL1
восстановили отрицательное множимое в R3R2
В.6, SMUL16A_4 ; если произведение >= 0, результат готов
; сделали произведение модулей отрицательным
CY = стариий бит R5
сложили R7R6 со старшим битом R5
сбросили CY
конец, если R7R6+R5.7 = 0, т.к. R7R6 и R5.7
содержат либо bcg нули, либо все единицы;
т.е. R7R6 и R5.7 содержат только знак числа,
иными словами, результат целиком укладывается
184
; в 16 бит R5R4, и R7R6 можно игнорировать
SETB С ; установили CY, т.к. результат но укладывается
; в 16 бит R5R4, и R7R6 игнорировать нельзя, т.к.
; R7R6 содержит кроме знака еще и значащие
; разряды
SMUL16A_5:
RET
Рис. 89. Подпрограмма умножения 16-разрядных двоичных знаковых чисел, вариант 1
Сама же подпрограмма SMUL16A довольна-проста. Вначале опера-
операцией XOR над знаковыми битами сомножителей определяется знак про
изведения и вместе со знаком множимого сохраняется в стеке. Затем
выделяются модули сомножителей и умножаются друг на друга при
помощи подпрограммы умножения двоичных беззнаковых чисел
MUL16A. В завершение произведение преобразуется в отрицательное
число, если сомножители имели разные знаки, и остается без измене-
изменений, если знаки совпадали. И под конец устанавливается в 1 флаг CY,
если произведение требует для своего представления больше, чем 16 бит.
Подробности сказанного очевидны из комментариев к подпрограмме.
Напомню, что макрокоманды JS и JNS определены в главе 1.
Отмечу, что аналогичным образом работает и подпрограмма
SMULN, осуществляющая умножение двух целых многобайтных
знаковых чисел.
SMULN:
Подпрограмма умножения двух целых N-байтньх чисел (N<32)
со знаком
Вход:
R0 - адрес МЛБ множимого
R1 - адрес МЛБ множителя, за которым следуют еще N байт для
стариой половины произведения
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R0 и R1 будут
указывать на МЛБ, a R0+1 и R1+1 - на СТБ)
R2 - размер мнохителя/мнохимого в байтах (т.е. число N (NO2))
Выход:
185
R1 - адрес 2Ы-байтного произведения (записьвается на место
множимого)
Флаги:
CY - признак переполнения N-байтного произведения
Используемые регистры:
R4, R3, А, В
Требует свободных байт в стеке:
2+1+4=7
Использует подпрограммы:
MULNB. NEGN
M0V
PUSH
MOV
ADD
DEC
MOV
MOV
MOV
RLC
A, R1
ACC
A, RO
A, R2
A
R1. A
A, №1
R3, A
A
; сохранили R1 (адрес множителя)
R3.7 = знак множимого
CY = знак множимого
MOV A. R0
MOV R1, А ; R1 = адрес мнохимого
JNC SMULN_1 ; если мнохимое >= О
LCALL NEGN
SMULN 1:
получили модуль множимого
POP
PUSH
ADO
DEC
MOV
MOV
MOV
RLC
ACC
ACC
A, R2
A
R1, A
A, SR1
R4, A
A
; A = адрес множителя
; сохранили адрес мнохителя
; R4.7 = знак множителя
; CY = знак мнохителя
186
POP ACC
MOV R1, A ; восстановили R1 (адрес множителя)
JNC EMULN_2 ; если множитель >= О
LCALL NEGN ; получили модуль множителя
SHULN 2:
; CY = знак мнокииого
; АСС.7 = знак произведения
; АСС.7 = знак множимого, АСС.6 = знак произведения
; сохранили знаки множимого и произведения
; вычислили модуль произведения
CY = знак множимого
R3.7 = знак произведения
JNC SMULN_3 ; если множимое >= О
сохранили R1 (адрес произведения)
R1 = адрес множимого
восстановили отрицательное множимое
восстановили R1 (адрес прЬиэведения)
EMULN 3:
MOV
MOV
XRL
RRC
PUSH
LCALL
POP
RLC
MOV
A, R3
C, SG
A, R4
A
ACC
MULNB
ACC
A
R3, A
MOV
PUSH
MOV
MOV
LCALL
POP
MOV
A, R1
ACC
A, RO
R1, A
NEGN
ACC
R1. A
MOV
JNS
MOV
RL
MOV
A.
R3
EMULN_4 ;
A,
A
R2
R2
. A ;
если произведение >= 0
удвоили N
187
LCALL NEGN
сделали произведение отрицательным
MOV A, R2
RR A
MOV R2. А
SMULN 4:
MOV
PUSH
ADD
PUSH
DEC
MOV
MOV
RLC
POP
MOV
MOV
MOV
SMULN 5:
MOV
ADOC
XCH
ORL
XCH
INC
DJNZ
A, R3
A. R3
A. R3
восстановили N
A. R1
ACC ; сохранили R1 (адрес произведения)
A. R2
ACC
A
R1, A
A. m
A ; CY = старший бит младшей половины произведени
ACC
R1, А ; R1 = адрес МЛБ старшей половины произведения
R3, DO ; R3 = О
В, R2
A, @R1
А, ЯО
вычислили R3 = R3 OR A - это для определения
равенства нулю суммы старшей половины
произведения и старшего бита младшей половины
произведения. Если сумма окахется равной нулю,
то это значит, что (N/2)*8+1 старших разрядов
произведения равны, т.е. содержат только
знак произведения. Это в свою очередь означает
то, что результат вмещается в N байт.
R1
В, SMULN_5 ; цикл по все* байтам старшей половины произве-
произведения
188
POP ACC
MOV R1, A ; восстановили R1 (адрес произведения)
MOV A, R3
CLR С
JZ SMULN_6 ; результат укладывается в N байт, CY=0
SETB С ; результат не укладывается в N байт, CY=1
SMULN_6:
RET
Рис. 90. Подпрограмма умножения многобайтных двоичных знаковых чисел
Рассмотренные выше подпрограммы умножения, основанные на
умножении модулей сомножителей, очевидны и работоспособны, но
вряд ли могут быть названы оптимальными. В самом деле, при умно-
умножении нам приходится хранить знаковые биты, выделять модули от-
отрицательных чисел, преобразовывать при необходимости произведе-
произведение в отрицательное число, а это требует затрат времени. Как правило,
более быстрыми оказываются подпрограммы с корректирующим вы-
вычитанием, с алгоритмом которых мы сейчас и познакомимся.
Рассмотрим произведение С- А -В двух знаковых двоичных чи-
чисел. Как мы выяснили чуть выше, если оба сомножителя положи-
положительны, то каких-либо преобразований, выделений модулей и т. д.
осуществлять не надо, и произведение может быть найдено путем
умножения их друг на друга при помощи подпрограммы умноже-
умножения беззнаковых целых двоичных чисел. Проблемы возникают тог-
тогда, когда хотя бы один из сомножителей отрицателен.
Прежде, чем мы двинемся дальше, давайте кое-что вспомним. Как
мы договорились выше, для чисел в дополнительном коде модуль
отрицательного n-разрядного двоичного числа Р определяется сле-
следующим образом:
\р\ =2"-Р = Г-Р, D2)
где Г =2" — так называемая граница представления числа.
Соответственно, модуль положительного двоичного числа совпа-
совпадает с самим числом. Аналогично, если у нас есть модуль отрица-
отрицательного числа \Р\, то само число Р находится по той же формуле:
189
Р =2"-1Р1=Г-1Р1, D3)
Вспомнили? Идем дальше. Пусть для определенности А — поло-
положительное число, а В—отрицательное. Тогда их произведение будет
равно произведению их модулей, взятому со знаком "минус". А при-
принимая во внимание D3), получим:
С=1А1-(-1В1) = Гг-А-(Г-В). D4)
Правая часть соотношения D4) содержит Г2 вместо Г, думаю,
ясно, почему — граница представления произведения двух «-раз-
«-разрядных чисел равна 22и = B"J = Г2. Также, полагаю, очевидно, что
(Г- В) — это модуль двоичного отрицательного числа В D2). Ну а
А-(Г-В) — это, как вы понимаете, произведение модулей рассмат-
рассматриваемых чисел А и В.
Раскроем скобки в D4), учитывая при этом, что Г2 находится
за пределами диапазона представления чисел и поэтому может
быть опущено:
С=АВ- ГА. D5)
Итак, если мы умножаем друг на друга два знаковых числа, пер-
первое из которых положительно, а второе отрицательно, то произведе-
произведение равно разности беззнакового произведения сомножителей и сдви-
сдвинутого на и разрядов влево первого сомножителя (произведение Г- А
эквивалентно сдвигу А на и разрядов влево).
Теперь рассмотрим вариант, когда число А отрицательно, я. В —
положительно. По аналогии с предыдущим случаем получим:
С={-\А\)-\В\ = Р-В-{Г-А) = АВ - Г-В. D6)
Что мы получили? Если мы умножаем друг на друга два знако-
знаковых числа, первое из которых отрицательно, а второе положительно,
то произведение равно разности беззнакового произведения сомно-
сомножителей и сдвинутого на п разрядов влево второго сомножителя (как
и в предыдущем случае, произведение Г- В эквивалентно сдвигу В на
и разрядов влево).
Проведя аналогичные выкладки для случая умножения друг на
друга двух отрицательных чисел, можно получить следующее:
С=АВ = (-1А1)-(-1В1) = А-В- Г-А - Г-В. D7)
190
То есть, если мы умножаем друг на друга два отрицательных зна-
знаковых числа, то произведение равно беззнаковому произведению
сомножителей минус сдвинутый на и разрядов влево каждый из со-
сомножителей.
Теперь давайте попытаемся вывести общую формулу для ум-
умножения друг на друга двух целых знаковых двоичных чисел,
учитывая сказанное выше и то, что произведение двух положи-
положительных чисел совпадает с беззнаковым произведением их мо-
модулей.
Обозначим через iзнаковыйбитчисла А, через)— знаковый бит
числа В (эти биты равны 0 для неотрицательных чисел и 1 — для
отрицательных). Тогда произведение двух целых знаковых двоич-
двоичных чисел определяется соотношением:
С = А-В - Г-Ц-А+ i -В). D8)
Так, если А положительно, В отрицательно, то i = О, j = 1. Под-
Подставив эти значения в соотношение D8), получаем D5). Для отри-
отрицательного А и положительного В i = 1, j — 0. Подставив эти значе-
значения в D8), получаем D6). Если оба сомножителя отрицательны, то
i = 1, j = 1. Подставив эти значения в D8), получаем D7). Ну а если
оба сомножителя положительны, то i = 0, j = 0, и мы получим, как и
должно быть, просто произведение А • В.
Мы вывели общую формулу для умножения друг на друга дво-
двоичных знаковых чисел. Чтобы найти произведение, нужно внача-
вначале умножить друг на друга оба сомножителя при помощи соот-
соответствующей подпрограммы целочисленного беззнакового умно-
умножения, приведенной в первой (для двухбайтовых чисел) или тре-
третьей (для многобайтовых) главах. Далее нужно проанализировать
знаковый бит первого сомножителя, и если он равен 1, то вычесть
из произведения сдвинутый на и разрядов влево второй сомно-
сомножитель. Естественно, если этот бит равен 0, ничего вычитать не
надо. После этого необходимо проанализировать знаковый бит
второго сомножителя, и если он равен 1, то вычесть из произведе-
произведения сдвинутый на я разрядов влево первый сомножитель. Опять
же, ничего не вычитаем, если знаковый бит второго сомножителя
равен 0. И все. То есть, вы используете рассмотренные ранее под-
подпрограммы беззнакового умножения с последующей достаточно
простой корректировкой полученного результата. Именно так и
работает предлагаемая вашему вниманию подпрограмма
SMUL16B.
191
SMUL16B:
Подпрограмма умножения двух целых 16-ти разрядных чисел со
знаком
Вход:
R3R2 - мнохимое
R5R4 - мнокитель
Вьход:
R7R6R5R4 - 32-х разрядное произведение (множимое«мнохитель)
Флаги:
CY - признак переполнения 16-ти разрядного произведения
Используемые регистры:
А, В
Требует свободных байт в стеке:
2+2+2=6
Испопьзует подпрограммы:
MUL16A
MOV A, R4
PUSH ACC
MOV A, R5
PUSH ACC ; сохранили множитель R5R4 в стеке, понадобится позже
LCALL MUL16A ; вычислили беззнаковое произведение знаковых МН и W
POP
POP
PUSH
ACC
В
ACC
; A = СТБ
; В = МЛБ
; в стеке
МН
МН
остался
только
СТБ
МН
JNS SMUL16B_1 ; если множитель >= 0, не вычитать множимое
CLR
M0V
SUBB
M0V
M0V
SUBB
HOV
SMUL16B 1:
С
А.
А,
R6
А,
А,
R7
R6
R2
, А
R7
R3
. А
вычли множимое из старшего слова произведения
192
MOV
JS
POP
SJHP
A, R3
SMUL16B_2 ; если множимое < 0, вычесть множитель
В ; убрать СТБ МН со стека
EMUL16B_3 ; результат готов
SMUL16B_2:
CLR
MOV
SUBB
MOV
MOV
POP
SUBB
MOV
С
A. R6
А, В
R6. A
A, R7
В
А. В
R7, A
SMUL16B 3:
в В был МЛБ МН
в стеке был СТБ МН
вычли множитель из старшего слова произведения
; CY = старший бит R5
сложили R7R6 со старшим битом R5
сбросили CY
конец, если R7R6+R5.7 = 0, т.к. R7R6 и R5.7
содержат либо все нули, либо все единицы;
т.е. R7R6 и F5.7 содержат только знак числа,
иными словами, результат целиком укладывается
в 16 бит F5R4, и R7R6 можно игнорировать
установили CY, т.к. результат не укладывается
в 16 бит R5R4, и R7R6 игнорировать нельзя, т.к.
R7R6 содержит кроме знака еще и значащие
разряды
SMUL16B_4:
RET
Рис. 91. Подпрограмма умножения 16-разрядных двоичных знаковых чисел, вариант 2
193
MOV
RLC
MOV
ADDC
MOV
MOV
ADDC
ORL
CLR
JZ
A, R5
А
A, F6
A, »0
B. А
A, R7
А, КО
А, В
С
SMUL16B 4
ЕЕТВ
Теперь познакомимся с еще одним интересным алгоритмом зна-
знакового умножения, называемым алгоритмом Бута (или Бутса17). Для
того, чтобы понять происхождение реализующих его соотношений,
рассмотрим следующее преобразование.
Вспомним, что двоичное число Xможет быть представлено сле-
следующим образом:
яЧ
D9)
¦"-о
Тогда произведение двух двоичных чисел У и X может быть за-
записано так:
Г2'- E0)
1=0
Внеся Увнутрь суммы, получим:
Z = $^"-Xi'- E1)
i=0
Это основное соотношение, справедливое для умножения беззна-
беззнаковых целых чисел. Однако Уможет быть и знаковым, поскольку сло-
сложение чисел, представленных в дополнительном коде, осуществля-
осуществляется точно также, как и беззнаковое сложение. Если мы хотим скор-
скорректировать E1) для работы со знаковым X, то в соответствии с D8)
мы должны записать:
X.-2I-y-Xn.1-2". E2)
1=0
Учитывая, что 2" = 2'+ 1 - 2', трансформируем E2) следующим
образом:
-Х;-[г+1-21"]-У.Хя_1-2". E3)
i=0
Разобъем сумму на две:
Z = ЕУ • Xt ¦ 2'+1 - ХУ"Х,i ¦2<'- Y ¦ Хп-х -2" • E4)
i-0 i=0
Теперь для упрощения сделаем замену переменных: j= i+ 1, от-
откуда i — j— 1. Подставив это в E4), получим:
"Booth
194
H.Xi.2i--y.X№.I.2". E5)
Вынесем из первой суммы слагаемое, соответствующее) = и, а из
второй — слагаемое, соответствующее i = 0. Тогда E5) преобразует-
преобразуется в следующее выражение:
.2'+Y.Xn_l.2"-tY.Xi.2i-Y.Xe.2°-Y.Xn_l.2" . E6)
Если мы его сократим, получится:
. ^Я-^У-Х.-Т-У-Х^.г*. E7)
Очевидно, что обе суммы в E7) могут быть объединены в одну,
поскольку они имеют одинаковые пределы суммирования. То, что в
первой суммирование ведется по/, а во второй по «, пусть вас не сму-
смущает — индекс суммирования можно обозначить как угодно и как
угодно переименовать, сумма от этого не изменится. Поэтому в пер-
первой сумме мы могли бы заменить j на i, при этом справедливость
E7) ничуть бы не изменилась. В итоге получаем:
[Х,_1-Х]-2'-УЛ-2°. E8)
Теперь давайте к правой части соотношения E8) прибавим про-
произведение У • \Xf_y - Xj]- 2' (где i = 0), а затем, чтобы соотношение
осталось неизменным, вычтем из нее это же самое произведение.
Первое произведение, которое мы прибавили к правой части E8),
внесем под знак суммы, вследствие чего нижняя граница индекса сум-
суммирования изменится с i = 1 на i = 0:
2i-y.[X_I-XII].20-y.XII.20 E9)
Раскрыв скобки в E9) и сократив его, получим:
i_1-Xi]-2i-y.X_1.2°. F0)
¦¦=0
Если рассматривать справедливость произведенных до сих пор
Математических преобразований, то пока все было корректно. Вот
только если задуматься, что же такое Х_, ? Ведь это минус первый раз-
разряд нашего двоичного числа. А мы при определении двоичных (да и
Десятичных) чисел говорили о нулевом, первом, втором и т. д. раз-
разрядах, но никак не о минус первом. И как же с ним быть?
195
И вот здесь начинается самое интересное. Для начала обратите
внимание на то, что значение этого бита, ноль ли, единица ли, роли
не играет. В самом деле, если мы раскроем сумму в F0), записав все
слагаемые не под знаком суммирования, а каждое в отдельности, раз-
разделенные плюсом, то среди них мы найдем
Г.[*_,-Яу .2°,
причем с положительным знаком. Раскрыв в этом члене скобки, мы
получим
У- Х_, ¦ 2°- У- ^ • 2°.
Как нетрудно заметить, первый член записанной в предыдущем
предложении суммы благополучно сократится с -У • Хч ¦ 2°. Таким
образом, испугавшее нас в F0) слагаемое, соответствующее минус
первому разряду нашего двоичного числа X, никоим образом не вли-
влияет на величину произведения Z. А раз так, то его, к примеру, можно
выбрать равным нулю и попросту забыть о нем, не ломая голову по
поводу его физического смысла. Этот разряд—математическая улов-
уловка, позволяющая упростить выкладки, дабы наглядно показать про-
происхождение интересующего нас алгоритма.
После того, как мы положили, что Х_, = 0, соотношение F0) при-
приобретет окончательный вид:
Z = gy-[X,._1-Xi].2i. F1)
Итак, мы получили выражение для определения произведения
знаковых двоичных целых чисел. Для его вычисления нам нужно
последовательно прибавлять к накапливаемой сумме сдвигаемый
влево сомножитель У, если (i- 1)-й разряд X равен 1, а i-й разряд
равен 0, или вычитать его из накапливаемой суммы, если (i— 1)-й
разряд Xравен 0, а i-й разряд равен 1. При равенстве i-ro и (i— 1)-го
разрядов X никаких действий по суммированию или вычитанию Y
производить не надо.
Напомню, что умножение на 21 эквивалентно сдвигу У влево на i
разрядов. Также напомню, что мы договорились, что X_t = 0.
Если помните, нечто похожее на рассмотренный выше алгоритм
нам уже встречалось в главе 1, когда мы говорили о вычислительной
схеме целочисленного деления без восстановления остатка. Там в за-
зависимости от знака результата вычитания делителя из оставшейся
196
части делимого мы либо оставляли разность неизменной, если ре-
результат вычитания был неотрицательный, либо производили сдвиг
И суммирование при отрицательной разности. Здесь при умноже-
умножении, мы тоже либо прибавляем У к накапливаемой сумме, если раз-
разность Хм - Х; соседних разрядов числа X положительна, либо вычи-
вычихаем из нее, если разность отрицательна.
Подпрограмма, реализующая рассмотренный алгоритм, приве-
приведена на рис. 92.
SMUL16C:
Подпрограмма умножения двух целых 16-ти разрядных чисел со
знаком
Вход:
R3R2 - множимое
R5R4 - множитель
Выход:
R7R6R5R4 - 32-х разрядное произведение (множимое*множитель)
Флаги:
CY - признак переполнения 16-ти разрядного произведения
Используемые регистры:
А, В, PSW.5
Требует свободных байт в стеке:
2+1=3
M0V
MOV
M0V
M0V
ORL
JNZ
M0V
M0V
CLR
RET
EMUL16C_0:
CLR
PUSH
R6. «0
R7, ffO ; обнулили СТС произведения
В, 016 ; 16 циклов сложения и сдвига
A, R2
A. R3
SMUL16C_0 ; если множимое о 0
R4, А
R5, А
С
А
АСС ; АСС.О = 0 - знак предьщущег
197
; «-1»-й бит считается нулевым
CLR PSW.5 ; начальное значение задвигаемого бита
EMUL16CJ:
MOV A. R4
RRC А
POP ACC
RLC А
ANL А, КЗ ; АСС.1 = знак предыдущего бита множителя,
; АСС.О = знак текущего бита множителя
PUSH ACC
CJNE A. »2, SMUL16C_2 : если АСС.1 АСС.О о 10Ь
H0V
ADD
MOV
MOV
ADDC
MOV
MOV
FLC
MOV
A.
A,
R6,
A.
A,
R7
A.
A
R6
R2
A
R7
R3
, A ;
R3
PSW.5. С
добавили множимое к частичному п
; будем задвигать знак множимого
SJHP SMUL16C.3
SMUL16C_2:
CJNE
CLR
MOV
SUBB
MOV
MOV
SUBB
MOV
MOV
RLC
CPL
MOV
A, «1
С
A, R6
A. R2
R6, A
A, R7
A, R3
R7, A
A. R3
A
С
PSW.5
A, 01, EMUL16C_3 ; если АСС.1 АСС.О о 01b
; вычли множимое из частичного произведения
PSW.5, С ; будем задвигать обратный знак множимого
198
SMUL16C 3:
MOV
С. PSW 5
MOV
RRC
MOV
MOV
RRC
HOV
MOV
RRC
MOV
MOV
RRC
MOV
A. R7
A
R7. A
A. R6
A
R6. A
A, R5
A
R5. A
A. R4
A
R4, A
R7R6R5R4 сдвинуто вправо
DJN2 В, SMUL16CJI ; зацикливание
POP ACC
MOV
RLC
MOV
ADDC
MOV
MOV
ADDC
ORL
CLR
JZ
A. R5
A
A. R6
A. «0
В. А
A, R7
A. #0
А. В
С
SMUL16C 4
EETB
CY = старший бит R5
сложили R7R6 со старшим битом R5
сбросили CY
конец, если R7R6+R5.7 = 0, т.к. R7R6 и R5.7
содержат либо все нули, либо все единицы;
т.е. R7R6 и R5.7 содержат только знак числа,
иными словами, результат целиком укладывается
В 16 бит R5R4-, и R7R6 можно игнорировать
установили CY, т.к. результат не укладывается
в 16 бит R5R4, и R7R6 игнорировать нельзя, т.к.
R7R6 содержит кроме знака еще и значащие
разряды
199
SMUL16C_4:
RET
Рис. 92. Подпрограмма умножения 16-разрядных двоичных знаковых чисел, вариант з
Мы рассмотрели три различных вычислительных схемы для на-
нахождения произведения знаковых целых чисел, представленных ц
дополнительном коде. Первые две опираются на рассмотренные ра-
ранее подпрограммы целочисленного беззнакового умножения, одна-
однако требуют корректировки полученного результата. Третья не тре-
требует каких-либо корректирующих действий. Она построена на пос-
последовательности сдвигов и сложений/вычитаний и наиболее удобна
для реализации "в железе", т. е. в аппаратных умножителях микро-
микроконтроллеров и процессоров.
В частности, иногда нам приходится решать задачи, где необ-
необходимы одновременно различные варианты умножений — двух
беззнаковых 16-разрядных чисел друг на друга, двух знаковых 16-
разрядных, а также одного 16-разрядного знакового, другого без-
беззнакового такой же разрядности. Для того, чтобы все перечислен-
перечисленные выше задачи мог решать один и тот же аппаратный умножи-
умножитель, он должен уметь реализовывать алгоритм умножения зна-
знаковых чисел (например, только что рассмотренный алгоритм Бута)
и иметь формат 17-17=34 бита. Зачем нужен 17-разрядный фор-
формат? Вот зачем. Если один из беззнаковых сомножителей больше
3276810, его старший разряд равен 1. Но беззнаковый сомножи-
сомножитель — это положительное число, а оно, как мы договаривались,
характеризуется нулевым старшим разрядом. Что делать? Наибо-
Наиболее простой вариант заключается в том, чтобы это 16-разрядное
беззнаковое число преобразовать в 17-разрядное положительное
знаковое, старший разряд которого — нуль. Иными словами, пре-
преобразовав 16-разрядные целые беззнаковые числа в их 17-разряд-
17-разрядные знаковые эквиваленты, мы можем осуществить их умноже-
умножение в рассматриваемом знаковом аппаратном умножителе, взяв
результат из 32-х младших бит с его выхода. Как нетрудно дога-
догадаться, если бы умножитель имел формат 16-16=32 бита, то мы с
его помощью могли бы умножать лишь те беззнаковые целые чис-
числа, которые не превышают 3276710.
Для умножения 16-разрядных знаковых чисел на рассмотренном
17-разрядном аппаратном умножителе нам нужно преобразовать
знаковое 16-разрядное число в знаковое 17-разрядное. Делается это
простым копированием старшего разряда Ь,5 исходного 16-разряд-
16-разрядного числа в старший разряд bl6 расширенного до 17 разрядов ново-
200
го числа (рис. 93). То есть, для положительного числа в 17-й разряд
мы должны поместить 0, для отрицательного — 1. И в том, и в дру-
другом случае младшие разряды остаются неизменными.
\b14\b13b12b1i\bio\b9\b8 Ь7\Ь6 Ь5 Ь4\ьЗ Ь2 Ы ЬО
Рис. 93. Алгоритм расширения знакового числа
Более общий случай — преобразование 16-разрядных знако-
знаковых чисел в 32-разрядные знаковые — реализует приведенная на
рис. 94 подпрограмма Х16_32. Аналогичная подпрограмма для пре-
преобразования N-разрядных знаковых чисел в 2и-разрядные приве-
приведена на рис. 95. Как нетрудно догадаться из сказанного в предыду-
предыдущем абзаце, в этих подпрограммах просто происходит трансля-
трансляция старшего знакового бита исходного числа во вновь добавлен-
добавленные биты.
Х16 32:
Подпрограмма преобразования целого 16-ти разрядного числа со
знаком в целое 32-х разрядное число со знаком
Вход:
R5R4 - 16-ти разрядное число со знаком
Вьход:
R7R6R5R4 - 32-х разрядное число со знаком
Используемые регистры:
А
Требует свободных байт в стеке:
2
MOV
RLC
5UBB
MOV
MOV
RET
А.
А
А.
R6,
R7.
R5
АСС
А
А
; CY =
; А = О
; R7R6
знак числа
если >¦+»
заполнены
или А
знаком
= OFFH если «-
числа
Рис. 94. Подпрограмма преобразования целого 16-разрядного числа со знаком в
целое 32-разрядное число со знаком
201
XN 2N:
Подпрограмма преобразований целого N-байтного числа со
знаком в целое 2N-6auTHOe число со знаком
Вход:
R1 - адрес числа со знаком, за которым следуют еще N байт для
старшей половины результата
(более старшие байты чисел располагаются по более старшим
адресам, т.е. для двухбайтовых чисел R1 будет
указывать на МЛБ. a R1+1 - на СТБ)
R2 - размер числа в байтах
Выход:
R1 - адрес 2М-байтного числа со знаком
Выход:
Используемые регистры:
А, В
Требует свободных байт в стеке:
2
MOV
ADO
DEC
MOV
MOV
RLC
SUBB
MOV
XN_2N_1:
INC
MOV
DJNZ
MOV
CLR
SUBB
MOV
A,
A,
A
R1.
A.
A
A.
B.
R1
mi
B.
A.
С
A.
R1,
R1
R2
A ;
€>R1 ;
ACC ;
R2
, A
XN_2N.
R1
R2
a :
R1
A =
CY
A =
.1 ;
- адрес СТБ числа
СТБ числа
= знак числа
0 если -+- или А = OFFH если —•
заполнили старшую половину знаком числа
восстановили R1
RET
Рис. 95. Подпрограмма преобразования целого N-байтового числа со знаком в целое
2М-байтовое число со знаком
202
Подпрограммы Х16_32 и XN_2N могут пригодиться перед вы-
выполнением деления чисел со знаком, если разрядности имеющихся
делимого и делителя совпадают, поскольку обычно наши подпрог-
подпрограммы Целочисленного деления ожидают в делимом вдвое больше
разрядов, чем в делителе. Подпрограммы Х16_32 и XN_2N помогут
удвоить разрядность делимого.
И в заключение — тестовая таблица для операции умножения
целых знаковых чисел формата 16 16=32.
Таблица 19
Тесты умножения формата 16-16-32 (целью двоичны* числа со знаком)
Представление чисел
Шестнадцатеричное
FFFFFFFF=0OOO0OO1
FFFF-O001=FFFFFFFF
FOOOOOOF=FFFF1000
FFOOOOFF=FFFF0100
5555-AAAA=E38E1 C72
7FFF-7FFF=3FFF0001
Десятичное
(-1H-1)=+1
MV(+D=-1
(-4096)(+15)=-61440
(-256W+255)= -65260
(+21845)-(-21846)= -477225870
(+32767)-(+32767)= +1073676289
ДЕЛЕНИЕ ЗНАКОВЫХ ЧИСЕЛ В ДВОИЧНОЙ СИСТЕМЕ
После ознакомления с различными вариантами умножения зна-
знаковых целых чисел нам остается лишь рассмотреть, как осуществля-
осуществляется их деление друг на друга.
Здесь, увы, особого разнообразия алгоритмов нет. Первый, един-
единственный предлагаемый здесь и самый простой способ деления знако-
знаковых чисел друг на друга заключается в делении друг на друга их моду-
модулей с последующим преобразованием полученного частногб в отри-
отрицательное число с тем же модулем, если знак делимого отличался от
знака делителя, и сохранением частного как положительного знаково-
знакового числа, если знаки делимого и делителя совпадали. Именно так рабо-
работают приводимые ниже подпрограммы деления SDIV16 и SDIVN.
Существует алгоритм деления чисел со знаком, не требующий
взятия модулей чисел. Этот алгоритм похож на уже рассмотренное
деление по схеме без восстановления остатка, но он более сложный и
неудобный для программной реализации.
SDIV16:
Подпрограмма деления двух целых чисел со знаком
Вход:
R7R6R5R4 - 32-х разрядное делимое (со знаком)
203
R3R2 - 16-ти разрядный делитель (со знаком)
Выход:
R5R4 - 16-ти разрядное частное (со знаком) от деления
(если CY=O)
R7R6 - 16-ти разрядный остаток (со знаком) от деления
(если CY=O)
Флаги:
CY - признак переполнения 16-ти разрядного частного
или деление на нуль, т.е. CY=1 - признак ошибки и
результат в R7R6 и R5R4 не определен
Используемые регистры:
А. В
Требует свободных байт в стеке:
2+1+2=5
Использует подпрограммы:
DIV16
MOV
RLC
M0V
XRL
RRC
MOV
JNS
M0V
CPL
ADD
MOV
MOV
CPL
ADDC
MOV
SDIV16_1:
MOV
CLR
JNS
A. R3
A
A, R3
A, R7
A
В. А
SDIV1
A, R2
A
A. #1
R2, A
A, R3
A
A, #0
R3, A
A, R7
B.5
SDIV1
; CY = знак делителя
; ACC.7 = знак частного
; ACC.7 = знак делителя, АСС.6 = знак частного
; сохранили знаки ДЛ и Ч в В, они понадобятся позже
SDIV16_1 ; если делитель >= 0, его модуль у нас уже есть
; получили модуль делителя в R3R2
; В.7 = знак ДЛ. В.6 = знак Ч, В.5 = 0 (знак ДМ)
SDIV16_2 ; если делимое >= 0, его модуль у нас уже есть
SETB B.5 ; В.7 = знак ДЛ, В.6 = знак Ч. В.5 = 1 (знак ДМ)
204
MOV
CPL
ADD
MOV
MOV
CPL
ADDC
MOV
MOV
CPL
ADDC
MOV
MOV
CPL
ADDC
MOV
SDIV16_2:
PUSH
LCALL
A,
A
A.
R4.
A,
A
A,
R5,
A,
A
A,
R6.
A,
A
A,
R7
В
R4
#1
A
R5
#0
A
R6
#0
A
R7
#0
. A
DIV16
получили модуль делимого в R7R6R5R4
сохранили знаки ДЛ, Ч и ДМ
вычислили частное и остаток от делений модулей
ДМ и ДЛ
В.7 = знак делителя, В.6 = знак частного,
В.5 = знак делимого
сохранили CY временно в стеке
POP В
PUSH PSW
JNB В.7, SDIV16_3 ; делитель в R3R2 был >= 0. мы его не изменяли
MOV
CPL
ADD
MOV
MOV
CPL
ADDC
MOV
SDIV16_3:
POP
JC
MOV
ORL
A,
A
A,
R2.
A,
A
A,
R3
R2
#1
. A
R3
#0
, A
PSW
SDIV16_7
A,
A.
R4
R5
восстановили отрицательный делитель в R3R2
конец, если была ошибка при беззнаковом делении
205
JZ SDIV16_5 ; частное = О, осталось обработать остаток
JNB
MOV
CPL
ADD
MOV
MOV
CPL
ADDC
A,
B.
A,
A
A.
R4
A,
A
A,
R5
6,
R4
Jt1
, A
R5
»0
B.6, SDIV16_4 ; если частное > 0. оно готово
HOV
R5, А
сделали частное отрицательным
SDIV16_4:
; дополнительная проверка на возможное переполнение
; 16-ти разрядного частного как числа со знаком:
RR
XRL
MOV
JC
SDIV16_5:
JNB
.MOV
CPL
ADD
MOV
MOV
CPL
A
А, В
С,
\
ACC.6
SDIV16_7
B.5.
A.
A
A,
R6.
A,
A
; АСС.6 = получившийся знак частного
АСС.6 = 1, если знак не совпадает с нужным из В.6
; конец, если переполнение числа со знаком
SDIV16_6 ; если делимое >= 0, остаток готов, т.к. знак
R6
#1
А
R7
; остатка должен быть равен знаку делимого
ADDC А. ВО
MOV
SDIV16_6:
CLR
SDIV16_7:
RET
R7, А
сделали остаток отрицательным
CY=O - не было ошибки при делении
в CY признак ошибки
Рис. 96. Подпрограмма деления двух целых чисел со знаком
206
SDIVN:
Подпрограмма деления двух целых чисел со знаком
Вход:
R0 - адрес НЛБ N-байтного делителя
R1 - адрес МЛБ 2Ч-баИпиого делимого
(более старшие байты чисел располагаются по более старшим
адресам)
R2 - размер делителя в байтах (т.е. число N (N<32))
Выход:
R1 - адрес N-байтного частного (записывается на место младшей
Головины делимого, в старшую половину делимого
записывается остаток)
Шлаги:
CY - признак переполнения N-байтного частного или деление на
нуль, т.е. CY=1 - признак ошибки и результат не определен
Используемые регистры:
R4, R3. А, В
Требует свободных байт в стеке:
2+1+4=7
Использует подпрограммы:
NEGN, DIVN, CMPZN
M0V
PUSH
MOV
ADD
DEC
MOV
MOV
MOV
RLC
MOV
MOV
A,
R1
ACC
A,
A.
A
R1,
A,
R3
A
A.
R1
RO
R2
, A
@R1
, A
RO
, A
; сохранили R1 (адрес делимого)
; R3.7 = знак делителя
; CY = знак делителя
R1 = адрес делителя
JNC SDIVN_1 ; если делитель >= О
LCALL NEGN ; получили модуль делителя
207
SDIVN.1:
POP
PUSH
ADD
ADD
DEC
MOV
MOV
MOV
RLC
POP
MOV
ACC
ACC
A, R2
A, R2
A
R1, A
A, №1
R4, A
A
ACC
R1. A
A = адрес делимого
сохранили адрес делимого
JNC
R4.7 = знак делимого
CY = знак делимого
; восстановили R1 (адрес делимого)
SDIVN_2 ; если делимое >= О
HOV A. R2
RL А
MOV R2. А
IXALL NEGN
SDIVN_2:
удвоили N
получили модуль делимого
M0V
RR
MOV
MOV
RLC
MOV
RRC
PUSH
LCALL
POP
PUSH
A, R2
А
R2. А
A, R3
А
A, R4
А
АСС
DIVN
В
PSW
; восстановили N
; А. 7=знак делителя.
; сохранили знаки
; получили частное и
: ДМ и ДЛ
; В.7=знак делителя,
А. 6=знак делимого
остаток от деления мо
В.6=знак делимого
; сохранили CY временно в стеке
208
JNB В.7, SDIVN3 ; делитель был >= 0, мы его не изменяли
сохранили R1 (адрдс делимого)
сохранили знаки
восстановили отрицательный делитель
восстановили знаки
SDIVN 3:
MOV
PUSH
MOV
MOV
PUSH
LCALL
POP
POP
MOV
POP
JC
A. R1
ACC
A. RO
R1. A
В
NEGN
В
ACC
R1, A
PSW
SDIVN
восстановили R1 (адрдс делимого)
SDIVN_7 ; конец, если Оьпа ошибка при беззнаковом делении
PUSH В ; сохранили знаки
LCALL CMPZN
POP В ; восстановили знаки
JZ SDIVN_5 ; частное = 0, осталось обрабатать остаток
MOV
RL
XRL
M0V
JNS
PUSH
А, В
А
А, В ; А.7 = нужный знак частного
R3. А ; R3.7 = нужный знак частного
SDIVN_4 ; если нужный знак частного «+», частное готово
В
; сохранили знаки
LCALL NEGN ; сделали частное отрицательным
POP В : восстановили знаки
SDIVN_4:
; дополнительная проверка на возможное переполнение
; 16-ти разрядного частного как числа со знаком:
MOV
A, R1
PUSH
ADD
DEC
ACC
A, R2
A
сохранили R1 (адрес частного)
209
MOV R1, A
MOV A. §R1
MOV R4, A ; R4.7 = получившийся знак частного
POP ACC
MOV R1, A ; восстановили R1 (адрес частного)
MOV A, R3
XRL A, R4 ; A. 7 = 1, если получившийся знак не совпадает
; с нужным знаком
RLC А
JC SDIVN_7 ; конец, если переполнение числа со знаком
SDIVN 5:
JNB В.Б, SDIVN_6 ; если делимое >= 0, остаток готов, т.к. знак
; остатка должен бьть равен знаку делимого
MOV
PUSH
ADD
MOV
LCALL
POP
MOV
A, R1
ACC
A, R2
R1. A
NEGN
ACC
R1, A
сохранили R1 (адрес частного)
сделали остаток отрицательным
восстановили R1 (адрес частного)
SDIVN 6:
CLR
CY=O - не было ошибки при делении
SDIVN 7:
RET
в CY признак ошибки
Рис. 97. Подпрограмма деления двух многобайтных целых чисел со знаком
Хочу обратить ваше внимание на следующую особенность деле-
деления знаковых чисел. Она связана с тем, что диапазон представления
«-разрядных знаковых чисел — от -2я до 2"~'-1. В частности, для
двухбайтовых чисел это -3276810 и +3276710. Обратите внимание на
то, что максимально допустимое положительное знаковое число вдвое
меньше максимально допустимого беззнакового числа той же раз-
210
рядности. Например, максимально допустимое двухбайтовое беззна-
беззнаковое число 6553510, а знаковое — 32767,0. Это является возможным
источником переполнения частного. Например, вы делите
100000000000000002=6553610 на 2. Осуществив стандартную проце-
процедуру целочисленного деления, вы получите частное
1О0О00О0О00О0ООО2=32768ш с нулем в остатке и без каких-либо пере-
переполнений. Беззнаковое 3276810 не выходит за допустимые границы
(О < 327681D < 65535,0). Но знаковое 3276810 выходит за границы до-
допустимых значений для двухбайтовых знаковых чисел C276810 >
32767,0), и полученный результат должен трактоваться как отрица-
отрицательное число (ведь его старший, знаковый разряд равен единице!).
Следовательно, произошло переполнение, причем именно знаково-
знакового частного (с беззнаковым-то 10000000000000002=3276810 все в по-
порядке). Поэтому рассматриваемые подпрограммы с меток SDIV16_4
и SDIVN_4 осуществляют подобную проверку на переполнение и
при обнаружении переполнения устанавливают в 1 флаг переноса.
Кстати, обратите внимание на то, что после этого они корректиру-
корректируют знак остатка, который, естественно, должен совпадать со знаком
делимого.
И в заключение — тестовая таблица для операции деления це-
целых знаковых чисел формата 32:16=A6,16).
Таблица 20
Тосты деления формата 32:16=116,16) (целью двоичные числа со знаком)
Представление чисел
Шестнадцатеричное
FFFFFFFR00014FFFFJJOOO)
00000001:0001 =@001, 0000)
FFFF1000:0011=(F1E2, FFFE)
05555555:AAAA=(F001, 4AAB)
FOF0FOFtt8O13=AE2a B46A)
FFFF8O027FFF=(OO0O, 8002)
Десятичное
(-Ш+1Н-1, 0)
(+1):(+1)=(+1, 0)
(-61440): (+17)=( -3614, -2)
(+89478485W -21846) = (-4095, +19115)
(-25264513Ш -32749) = (+7714, -19350)
(-32766):(+32767) = @, -32766)
АРИФМЕТИЧЕСКИЕ СДВИГИ ЧИСЕЛ СО ЗНАКОМ
В конце первой главы мы рассмотрели различные варианты част-
частных случаев умножения и деления целого беззнакового числа на це-
целые положительные степени двойки. Делалось это при помощи сдви-
сдвигов числа влево (для умножения) и вправо (для деления) и позволяло
заметно сократить время умножения и деления в этих случаях. Анало-
Аналогичные действия можно осуществлять и со знаковыми числами.
При умножении на 2 нужно сдвинуть влево на 1 разряд все биты
знакового числа, записав при этом 0 в младший бит числа. Если в
211
результате этой операции значение знакового разряда изменилось,
это значит, что произошло переполнение формата представления
числа и, очевидно, он должен быть увеличен с п разрядов до я+1. То
есть практически, умножение на два (и вообще на любую положи-
положительную целую степень двух) выполняется для чисел со знаком так
же, как и для чисел без знака. Как мы уже упоминали, это является
прямым следствием одинаковости алгоритма сложения чисел со зна-
знаком и чисел без знака. Используйте для этого арифметического сдвига
влево подпрограммы RLA16 и RLAN (рис. 49 и 68).
При делении знакового числа на 2, очевидно, также должен со-
сохраняться знак числа, т.е. просто сдвинуть все биты числа вправо на
1 разряд, а в старший бит числа задвинуть 0 (как мы бы это сделали с
беззнаковыми числами) уже нельзя. Подпрограммы RRA16 и RRAN
(рис. 50 и 69) производят сдвиг числа на 1 разряд вправо, при этом в
старший (знаковый) разряд числа записывается значение знакового
разряда числа предшествующее сдвигу.
Интуитивно вы можете предположить, что это должно работать.
Действительно, если вы возьмете число 11112 = -1 ]0 и сделаете ариф-
арифметический сдвиг влево на 2 позиции, то получите 11002 = -4|0. Если
же теперь вы арифметически сдвинете 11002 вправо на 2 позиции (не
забудьте про сохранение знака при сдвиге!), то опять получите 11112=
= -110. Кажется все хорошо? На самом деле, не совсем все хорошо.
Попробуйте арифметически сдвинуть вправо число 11012 = -3,0, и
получите 11102 = —2]0, если не ошибетесь. А нам бы нужно получить
11112 = -110, коль скоро мы собрались -3 делить на 2. Попробуйте
проделать то же самое для 11112 = -1|0. Минус единица вообще никак
не изменилась, а мы ведь хотели получить 0! Что же не так?
Проблема на самом деле заключается в округлении. Если мы
арифметически сдвигаем на разряд вправо четное число, то резуль-
результат получается правильный, а если мы берем нечетное число, то сдвиг
на разряд вправо дает число меньшее на 1, чем нам нужно. Получа-
Получается это потому, что при сдвиге отрицательного числа вправо, мы
фактически производим округление не в сторону нуля (как мы ожи-
ожидаем по аналогии с положительными числами), а в сторону минус
бесконечности. Это не сложно увидеть, если представить себе отри-
отрицательное число как разность Г и модуля числа. При сдвиге разность
округляется к нулю, но это возможно лишь при округлении модуля
в сторону плюс бесконечности. Таким образом, в результате получа-
получается, что при сдвиге вправо с сохранением знака округление исход-
исходного числа происходит к минус бесконечности.
Как исправить положение? Если мы делаем сдвиг всего на один
разряд вправо, то если число отрицательное и перед сдвигом оно было
212
нечетное (т. е. младший бит перед сдвигом равен 1 или флаг перено-
переноса равен 1 после сдвига вправо), нужно добавить единицу к тому, что
получилось после сдвига. Если же мы сдвигаем отрицательное число
на п разрядов вправо, то после сдвига мы будем должны добавить
единицу к результату, если хотя бы один из и младших разрядов ис-
исходного числа был равен 1. Следует отметить, что эта погрешность в
самом младшем бите числа может быть проигнорирована, если она
не превышает допустимой ошибки вычислений в вашей задаче. Час-
Часто после арифметического сдвига вправо никакой коррекции не де-
делается, если она производится над числами с фиксированной точ-
точкой, которые, по сути, мало чем отличаются от целых чисел.
КРАТКИЕ ВЫВОДЫ
Итак, мы завершили знакомство со знаковой двоичной целочис-
целочисленной арифметикой. Напомню, что во многих практических случа-
случаях оказывается гораздо удобнее оперировать со знаковыми числами,
чем с беззнаковыми с постоянным анализом, какие величины нужно
суммировать, а какие вычитать.
Очень часто целочисленная знаковая двоичная арифметика ис-
используется при разработке регулирующих устройств, поскольку ре-
регулируемый параметр может быть как больше, так и меньше некото-
некоторого заданного значения. Примеров таких регуляторов не счесть —
регуляторы освещенности, мощности, скорости вращения, темпера-
температуры, давления и т. д., если начать перечислять все возможные при-
применения регулирующих устройств в современной технике, потребу-
потребуется не менее десятка страниц.
Определив отклонение регулируемой величины от заданного зна-
значения как разность между заданным и текущим значением регули-
регулируемого параметра, мы фактически определяем отклонение как зна-
знаковую величину, которая может быть как положительной величиной,
так и отрицательной. Используя подпрограммы знаковой арифме-
арифметики, мы можем предельно упростить алгоритм работы подобного
устройства, ибо в этом случае он потребует выполнения одной и той
же последовательности операций как при превышении регулируе-
регулируемым параметром заданной величины, так и при меньшем его значе-
значении, и не нужно в каждом конкретном случае анализировать, какая
из величин больше, а какая меньше, и что из чего нужно вычитать.
Преимущества, которые мы при этом получаем, очевидны — более
короткая программа, меньшие затраты на ее разработку и отладку.
Поэтому арифметика знаковых чисел востребована, имеет право на
существование, и настоящая глава была посвящена арифметическим
операциям со знаковыми целыми числами.
213
В алгебре десятичных чисел, которую все мы изучали в школе,
знаковые числа представлялись единственным образом. Они состоя-
состояли из знаковой и цифровой части. Знаковая часть — это знак (плюс
или минус), записывавшийся вначале числа. Затем шла его цифро-
цифровая часть или модуль, который состоял из десятичных цифр. Цифры
эти формировали (с учетом своих весовых коэффициентов) значе-
значение модуля. Например, рассмотрим отрицательное число —343: от-
отрицательное оно потому, что перед цифрами стоит знак минус, яв-
являющийся знаковой частью числа; модуль же его равен тремстам
сорока трем. Перед положительным числом знак плюс обычно опус-
опускают, и модуль его равен самому числу: 343 — это положительное
число, модуль которого равен тремстам сорока трем.
В двоичной системе счисления знаковые числа представляются
несколько иначе. Если в представлении отрицательных десятичных
чисел мы фактически задействовали уже не десять, а одиннадцать
цифр (ведь минус в начале записи отрицательного числа играет роль
дополнительной цифры, поскольку он не совпадает ни с единицей,
ни с двойкой, ни с тройкой и т. д.), то в рамках двоичной системы
ввести дополнительную, третью цифру мы не можем — логические
микросхемы оперируют только с единицей (высоким уровнем) и
нулем (низким), промежуточные состояния запрещены. Следователь-
Следовательно, знаковую часть отрицательного числа нам приходится кодиро-
кодировать либо единицей, либо нулем, третьего не дано.
Так сложилось, что изображение знака "+" в знаковом разряде числа
принято кодировать для двоичных чисел цифрой 0, а знака "-" —
цифрой 1. Знаковым разрядом выбирают самый старший (крайний
слева) бит числа. Остальные разряды составляют цифровую часть
числа, т. е. несут информацию о его модуле.
Возможны по меньшей мере три различных варианта разумного
представления цифровой части знакового двоичного числа. Первый
вариант идентичен рассмотренному выше представлению десятич-
десятичных чисел. Он предполагает, что цифровая часть как положительно-
положительного, так и отрицательного числа всегда содержит абсолютную величи-
величину этого числа. Например,+8,0= О Ю002,-8 ю= 110002. Двоичноепред-
ставление плюс восьми и минус восьми отличаются лишь самым стар-
старшим, знаковым битом — в первом случае он равен 0, во втором 1.
Цифровая же часть в обоих случаях одинакова и равна 10002. Такой
способ представления знаковых чисел, естественный и наиболее при
емлемый на первый взгляд, называют прямым кодом.
Прямой код не получил широкого распространения в реализуе-
реализуемой микроконтроллерами и иными цифровыми вычислительными
устройствами двоичной арифметике. Причин этому несколько. Во-
214
первых, обработка чисел, представленных в прямом коде, требует
отдельных операций над цифровой и знаковой частями, а также аль-
альтернативного выполнения операций сложения и вычитания. Во-вто-
Во-вторых, появляются два различных представления числа 0: +0 и -О. На-
Например, для чисел +0ш= 000...002,-0,0= 100...002. Указанные недо-
недостатки сдерживают применение прямого кода в микроконтроллер-
микроконтроллерной и микропроцессорной технике, и он используется преимуще-
преимущественно в операциях ввода/вывода данных.
Второй вариант — так называемый инверсный код. Представле-
Представление положительного числа в инверсном коде идентично таковому в
прямом коде: знаковый разряд равен 0, а цифровая часть представ-
представляет из себя стандартную двоичную форму записи модуля числа. Так,
+8Ю = 010002, +48]0 = 01100002 и т. д. (не забудьте, крайний слева
ноль — это знаковый бит). Различие же между прямым и инверс-
инверсным кодом проявляется при записи отрицательных чисел. Все биты
двоичного отрицательного числа являются результатом инверсии со-
соответствующих битов (включая знаковый) положительного числа с
тем же модулем. Например, +810 = 010002, -810 = 101112, +4810 =
= О11О0002,-48,0= 10011112.
Инверсный код еще называют дополнением до 1, поскольку п-
разрядное отрицательное двоичное число К получают вычитанием
эквивалентного положительного числа Р из B"-1): К = B"-1) - Р. В
самом деле, -в,0= Ш112-ОЮ002= ЮШ2,-4810= 11ШН2-01100002 =
2
Представление положительного числа в дополнительном коде
идентично таковому в прямом коде: знаковый разряд равен 0, а циф-
цифровая часть представляет собой стандартную двоичную форму запи-
записи модуля числа. Так, в дополнительном коде по-прежнему +810 =
010002, +48,0 = 01100002 и т. д. В то же время «-разрядное отрица-
телъное двоичное число L получают вычитанием эквивалентного по-
положительного числа Р из 2": L = 2" - Р. То есть, в дополнительном
коде -810= 1000002-010002= 110002,-4810 = 100000002 - 01100002 =
= 10100002.
Еще раз отметим, что самый простой способ получить запись отри-
отрицательного числа в дополнительном коде — это проинвертироватъ все
биты (включая знаковый 0) равного ему по модулю положительного числа
(т. е. представить отрицательное число в инверсном коде), после чего
прибавить к последнему 1. В самом Деле, в инверсном коде —8Ш= 101112;
прибавим к этому результату единицу: 101112 + 12 = 110002. А как мы
отмечали в предыдущем абзаце, в дополнительном коде
-8,0 = 1000002 - 010002 = 110002. Еще пример: в инверсном коде
-48,0 = 10011112; прибавим к этому результату единицу: 10011112 + 12 =
215
= 10100002. А как мы отмечали в предыдущем абзаце, в дополнитель-
дополнительном коде -48Ш= 100000002-0110000-,= 10100002. Все сошлось?
Еще один способ получения записи отрицательного числа в до-
дополнительном коде заключается в следующем. Возьмем двоичную
запись положительного числа с тем же модулем (она одинакова что в
прямом, что в инверсном, что в дополнительном коде) и проанали-
проанализируем ее побитно справа налево в поисках самой правой из входя-
входящих в число единиц. Эту единицу и стоящие правее нее нули (если
таковые имеются) оставим без изменений, а все цифры, стоящие ле-
левее этой оставшейся неизменной единицы, проинвертируем. Таким
образом получим запись отрицательного числа в дополнительном
коде. Например, для уже многократно рассмотренного -4810 проце-
процедура записи его в дополнительном коде выглядит так: +4810 = 01100002;
крайняя справа единица стоит в четвертом справа разряде. Ее и че-
четыре расположенных правее ее нуля оставляем неизменными, а еди-
единицу в пятом справа разряде и нуль в шестом проинвертируем. По-
Получаем -4810= 1010000.,.
Алгоритм обратного перевода дополнительного кода отрицатель-
отрицательного числа в прямой код точно такой же: нужно провести ту же са-
самую поразрядную операцию дополнения каждой цифры числа до
старшей цифры системы счисления и просуммировать полученный
результат с единицей. Подобное преобразование (дополнительного
кода отрицательного числа в прямой код) необходимо для нахожде-
нахождения модуля отрицательного числа.
Помимо рассмотренных, дополнительный код обладает следую-
следующими свойствами. Во-первых, в дополнительном коде у нас всего
один нуль (а не два, как в случае с представлением знаковых чисел в
прямом коде). Кстати, если вы попытаетесь получить дополнение до
двух числа 0 (то есть, как-бы минус 0), вы получите все тот же нуль.
Помимо нуля, у нас есть еще одно число, которое после опера-
операции дополнения до двух переходит само в себя — это 10002- В самом
деле, инвертируем все его биты и получим 01112. Прибавим к полу-
полученному единицу и увидим не изменившееся после описанной про-
процедуры 10002.
Последний факт означает, что в дополнительном коде самому
маленькому числу нельзя подобрать равное ему по модулю положи-
положительное число. Всем остальным числам можно, а самому маленько-
маленькому — нельзя, нет такого среди чисел заданной разрядности. Подтвер-
Подтверждение тому — диапазон представления однобайтных чисел (от -
128 до +127) и двухбайтных (от -32768 до +32767). Как видите, в
первом случае действительно нет числа +128, а во втором Н32768.
Так же и для чисел большей разрядности — самое малое «-разрядное
216
знаковое число равно ~2"~\ а самое большое равно 2"—1, т. е. его
модуль на 1 меньше, чем модуль самого малого.
Сложение знаковых чисел в прямом коде требует предваритель-
предварительного анализа модулей слагаемых, и в зависимости от соотношения
между ними сумма определяется по тому или иному алгоритму, при-
причем непростому, требующему различных действий над знаковыми
частями слагаемых и над цифровыми. Сложение знаковых чисел,
представленных в инверсном коде, осуществляется намного проще,
также, как мы складываем обычные беззнаковые двоичные числа, но
результат сложения требует относительно непростой корректировки
(и предварительного выяснения, нужна ли она, или сумма верна). И
только сложение знаковых чисел, представленных в дополнитель-
дополнительном коде, не только автоматически без каких-либо коррекций дает
правильный результат, но и осуществляется предельно просто: вне
зависимости от соотношения модулей и знаков слагаемых они скла-
складываются точно также, как мы складываем обычные беззнаковые
целые числа.
Иными словами, применение дополнительного кода, как было
показано в настоящей главе, позволяет производить сложение и
вычитание знаковых чисел по одному и тому же алгоритму, при
этом вычитание заменяется сложением уменьшаемого с отрица-
отрицательным числом, равным по модулю вычитаемому. И главное, при
использовании дополнительного кода обработка при сложении
знаковой и цифровой частей чисел производится по одним и тем
же правилам, причем правильный знак результата формируется
автоматически. Увы, ни прямой, ни обратный коды такими свой-
свойствами не обладают.
Как было отмечено, сложение знаковых чисел, представленных в
дополнительном коде, осуществляется совершенно аналогично тому,
как мы складывали целые беззнаковые числа, причем знаковые разряды
складываются не отдельно от цифровой части, а в том же цикле сло-
сложения, и при этом мы автоматически, без необходимости что-то кор-
корректировать, получаем правильное значение как цифровой части сум-
суммы, так и ее знака.
Выше говорилось о том, что вычитание может быть заменено
сложением, в котором у второго слагаемого должен быть инвертиро-
инвертирован знак. То есть, при замене вычитания сложением положительное
вычитаемое должно быть заменено отрицательным вторым слагае-
слагаемым (с тем же модулем), а отрицательное вычитаемое — положи-
положительным вторым слагаемым.
Таким образом, если мы умеем легко превращать положитель-
положительное число в равное ему по модулю отрицательное, и наоборот, мы
217
можем вообще обойтись без команд вычитания, сведя их к сложе-
сложению! (Напомню, что наиболее простой способ найти модуль отрица-
отрицательного двоичного числа, равно как и само это число по его моду-
модулю — это инвертировать все биты числа (модуля), после чего
прибавить к результату инверсии 1). Именно благодаря этому в ряде
микроконтроллеров (в частности, в очень популярных некогда МК
семейства х48) команды вычитания напрочь отсутствовали. И это не
мешало разработчикам реализовывать на подобных МК довольно
сложные вычислительные алгоритмы. Следует заметить, что прогресс
микроэлектроники привел к тому, что "безвычитательные" МК прак-
практически исчезли из обращения. Но наличие команд вычитания не
упростило арифметику знаковых чисел — по-прежнему при работе
с ними часто оказывается гораздо удобнее изменить знак у числа, чем
менять весь алгоритм, вставляя в него команды и подпрограммы вы-
вычитания вместо сложения.
Что касается умножения, то в настоящей главе были рассмотре-
рассмотрены три различные вычислительные схемы, реализующие умноже-
умножение знаковых чисел в дополнительном коде.
Первая— самая очевидная. Чтобы организовать умножение зна-
знаковых двоичных чисел, нужно перемножить модули сомножителей,
оставив произведение положительным, если знаки сомножителей
совпадают, и преобразовав произведение в отрицательное число (с
тем же модулем), если знаки сомножителей разные.
Рассмотренный алгоритм умножения, основанный на умноже-
умножении модулей сомножителей, очевиден и работоспособен, но вряд ли
может быть назван оптимальными. В самом деле, при умножении
нам приходится хранить знаковые биты, выделять модули отрица-
отрицательных чисел, преобразовывать при необходимости произведение
в отрицательное число, а это требует затрат времени. Как правило,
более быстрыми оказываются подпрограммы с корректирующим
вычитанием.
Суть их состоит в том, что для того, чтобы найти произведение,
нужно вначале умножить друг на друга оба сомножителя при помо-
помощи соответствующей подпрограммы целочисленного беззнакового
умножения, приведенной в первой (для двухбайтовых чисел) или тре-
третьей (для многобайтовых) главах. Далее нужно проанализировать зна-
знаковый бит первого сомножителя, и если он равен 1, то вычесть из
произведения сдвинутый на и разрядов влево второй сомножитель.
Естественно, если этот бит равен 0, ничего вычитать не надо. После
этого необходимо проанализировать знаковый бит второго сомно-
сомножителя, и если он равен 1, то вычесть из произведения сдвинутый на
п разрядов влево первый сомножитель, опять же ничего не вычитая,
218
если знаковый бит второго сомножителя равен 0. И все. То есть, вы
используете рассмотренные ранее подпрограммы беззнакового ум-
умножения с последующей достаточно простой корректировкой полу-
полученного результата.
Еще более интересен алгоритм умножения знаковых чисел — ал-
алгоритм Бута. Как было показано, в соответствии с ним для определе-
определения произведения знаковых двоичных целых чисел нам нужно внача-
вначале занулить накапливаемый результат, а затем последовательно или
прибавлять к накапливаемой сумме сдвигаемый влево сомножитель
У, если (i- 1)-й разряд Хравен 1, а i-й разряд равен 0, или вычитать
его из накапливаемой суммы, если (i - 1)-й разряд X равен 0, а i-й
разряд равен 1. При равенстве i-ro и (i- l)-ro разрядов X никаких
действий по суммированию или вычитанию Упроизводить не надо.
Алгоритм Бута не требует взятия модулей перемноженных чисел и
безо всяких коррекций дает правильное произведение чисел со зна-
знаком.
Что касается деления, то здесь, увы, особым разнообразием алго-
алгоритмов не пахнет. Первый, единственный предложеннный здесь и са-
самый простой способ деления знаковых чисел друг на друга заключает-
заключается в делении друг на друга их модулей с последующим преобразова-
преобразованием полученного частного в отрицательное число с тем же модулем,
если знак делимого отличался от знака делителя, и сохранением част-
частного как положительного знакового числа, если знаки делимого и де-
делителя совпадали. Именно так работают приводимые в настоящей гла-
главе подпрограммы деления двухбайтных и многобайтных чисел.
При этом нужно обратить внимание на следующую особенность
деления знаковых чисел. Она связана с тем, что диапазон представ-
представления п-разрядных знаковых чисел — от -2"~1 до 2"~'-1. В частности,
для двухбайтовых чисел это -32768,0 и +32767ю. Обратите внимание
на то, что максимально допустимое положительное знаковое число
вдвое меньше максимально допустимого беззнакового числа той же
разрядности. Например, максимально допустимое двухбайтовое без-
беззнаковое число — это 6553510, а знаковое — соответственно 3276710.
Это является возможным источником переполнения частного,
когда в результате беззнакового деления старший бит полученного
положительного частного оказывается равным 1. Подобное перепол-
переполнение нужно выявлять и блокировать дальнейшие вычисления, ибо
они содержат ошибку. С этой целью рассмотренные подпрограммы
осуществляют подобную проверку на переполнение, и при обнару-
обнаружении переполнения устанавливают в 1 флаг переноса.
В конце первой главы мы рассмотрели различные варианты час-
частных случаев умножения и деления числа на степени двойки, позво-
219
ляющие заметно сократить время умножения и деления в этих слу-
случаях. Аналогичные действия можно осуществлять и со знаковыми
числами.
В частности, умножение числа со знаком на степень двойки иден-
идентично умножению беззнакового числа на ту же степень двойки и ре-
реализуется сдвигом всех бит числа влево, при этом освобождающиеся
младшие биты зануляются. При умножении беззнаковых чисел, как
и при умножении чисел со знаком может происходить переполне-
переполнение. В данном случае (случае чисел со знаком) переполнение возни-
возникает тогда, когда сдвиг числа изменяет значение старшего (знаково-
(знакового) бита числа.
Деление числа со знаком на степень двойки несколько отличает-
отличается от деления на ту же степень 2 беззнакового числа. Отличие заклю-
заключается в том, что при сдвиге вправо в старший (знаковый) бит числа
задвигается не 0 (как это было у беззнаковых чисел), а значение это-
этого бита перед сдвигом. Это позволяет сохранить знак числа. Следует
отметить особенное свойство подобного арифметического сдвига
вправо. При сдвиге знакового числа вправо с сохранением знака про-
происходит округление результата в сторону минус бесконечности, т. е.
положительные числа по-прежнему округляются в сторону нуля (для
положительных чисел округление к нулю и к минус бесконечности —
это одно и то же), а отрицательные в сторону противоположную от
нуля — в сторону минус бесконечности.
Этот факт может потребовать дополнительной коррекции резуль-
результата сдвига путем добавления единицы к результату.
ЗАКЛЮЧЕНИЕ К ТРЕТЬЕМУ ТОМУ
Итак, мы познакомились с тем, как в микроконтроллерах могут быть
организованы подпрограммы целочисленной знаковой и беззнаковой ариф-
арифметики. Конечно, вы можете задать вопрос: «Почему вы, авторы, говорите о
микроконтроллерах вообще, в то время как в качестве конкретных приме-
примеров приведены подпрограммы, написанные на ассемблере для семейства х51 ?
Ведь ассемблер—низкоуровневый, машиннозависимый язык, т. е. програм-
программы, написанные для одного семейства МК, не могут быть использованы без
изменений для другого типа микроконтроллеров. И вообще, зачем писать
эти программы на ассемблере?».
Ответы на эти вопросы довольно просты. Во-первых, выбор языка
ассемблера объясняется тем, что он позволяет наиболее детально, почти
на аппаратном уровне раскрыть механизм решения задачи и реализовать
его эффективно, с минимальными затратами времени на выполнение
программ и объема занимаемой ими оперативной и программной памя-
памяти. Это актуально для многих микроконтроллерных систем, особенно ра-
работающих в режиме реального времени. Не секрет, что многим разра-
разработчикам не хватает ресурсов и в первую очередь быстродействия при
работе с самыми производительными микроконтроллерами, которые
выпускаются на сегодняшний день. А приведенный в книге материал де-
демонстрирует, что ассемблерные арифметические подпрограммы весьма
просты и допускают оптимизацию по времени выполнения, недоступ-
недоступную для таковой для подпрограмм на языке высокого уровня.
Во-вторых, трудоемкость написания арифметических программ на
языке ассемблера значительно превышает трудоемкость разработки ана-
аналогичных программ на языках высокого уровня, и потому наличие ассем-
ассемблерных программ-аналогов во многих случаях является неоценимым
подспорьем для разработчика. Поэтому мы на конкретных примерах по-
показываем, как подобные программы нужно реализовывать именно на ас-
ассемблере, а не на высокоуровневом языке.
В-третьих, ассемблер в силу его близости к структуре микроконтроллера
позволяет инженеру-схемотехнику наиболее просто, быстро и безболезненно
перейти от схемотехники к программированию и накоплению нового опыта.
Это чрезвычайно актуально по причине того, что сегодня еще существует до-
довольно большое количество инженеров-электронщиков, не владеющих мик-
микроконтроллерной техникой, но желающих разобраться в этой предметной об-
области и научиться применять в своих разработках микроконтроллеры.
И, наконец, в-четвертых, хорошо известно, что правильно написанные
и хорошо документированные тексты ассемблерных программ для одного
из микропроцессоров очень легко переводятся на ассемблер любого друго-
другого процессора или контроллера. Особенно, когда переводящий ясно пони-
понимает подлежащий переводу алгоритм. Именно поэтому нами уделено так
много внимания подробному описанию алгоритмов и вычислительных схем,
приведены различные варианты реализации некоторых особенно сложных
алгоритмов и даны тестовые примеры, которыми можно воспользоваться
для тестирования переведенных на другой ассемблер программ.
221
Разработчику часто приходится тратить время и усилия на создание
программ, которые где-то кем-то уже созданы, но не описаны в доступной
форме в литературе. Создавая эту книгу, мы хотели восполнить этот про-
пробел. В книге систематизированы алгоритмы и методы элементарной число-
числовой и символьной обработки информации для решения широкого спектра
типичных задач прикладного программирования микроконтроллерных си-
систем, а также представлен большой комплекс программ, практически реа-
реализующий эти решения. Большинство приведенных программ оригиналь-
оригинальны и отражают многолетний опыт авторов по разработке и программиро-
программированию различных микроконтроллерных систем. Хотя представленные про-
программы разработаны, отлажены и оформлены специально для настоящей
книги, они в полной мере соответствуют реальным сложным программам и
методологии преодоления сложности последних.
Приведенные в книге программы имеют двойное назначение. Во-пер-
Во-первых, они выступают как образцы, который читатель может вставить в свой
программный комплекс, что называется, "с листа". В этом качестве програм-
программы могут, естественно, использоваться только для х51-совместимых мик-
микроконтроллеров. К счастью, выбор последних сегодня широк, как никогда.
В связи с этим авторы надеются, что подобным образом используют пред-
предложенный материал очень многие разработчики, и это поможет им сэконо-
сэкономить массу сил, нервной энергии и времени, потратив сэкономленное на
лучшую проработку оригинальной части своих проектов. Кстати, ясно, что
ручной набор программ со страниц книги может привести к появлению
ошибок, поэтому всем, кто воспользуется этими программами в своих про-
проектах, рекомендуем взять исходные тексты на сайте www.pyrometer.ru.
Во-вторых, как уже неоднократно говорилось, программы можно ис-
использовать как аналоги, детально объясняющие конкретные реализации
приведенных алгоритмов решения задач. В этом качестве программы, при-
приведенные в книге, применимы для любых микроконтроллеров, как суще-
существующих, так и находящихся в процессе разработки в компаниях-произ-
компаниях-производителях. Ведь написать программу, имея алгоритм и понятную програм-
программу-аналог, гораздо проще, нежели делать эту работу, что называется, с нуля.
Пожалуй, главная проблема практического программирования — исполь-
использование "чужого" программного обеспечения (здесь и далее мы говорим толь-
только о легальном его использовании). Хотя профаммы пишутся для машин, в
первую очередь они должны быть понятны людям: "чужое" может стать "сво-
"своим" только в том случае, если оно понятно. Чужие программы, оторванные от
машинной среды и представленные в виде "черного ящика" (любимая модель
учебных заведений), вызывают естественное недоверие, сдерживающее их при-
применение в своих проектах. Это недоверие устраняется либо в ходе длительного
тестирования программы, либо после полного уяснения алгоритма работы и
деталей ее внутренней организации. Максимальная читаемость текста програм-
программы, ее компактность и обозримость, доступность для быстрого понимания "чу-
"чужим умом" являются необходимыми условиями использования и "живучес-
"живучести" чужих программ. Эти требования становятся еще актуальнее в том случае,
когда речь идет о модернизации "чужой" программы. Без понимания структу-
структуры профаммы любые изменения в ней "на авось" чреваты ошибками.
222
Обычно возможность понимания программы обеспечивается ее ком-
компактным построением и многоуровневым описанием. Самый высокий уро-
уровень описания — это описание программы в виде "черного ящика" с рас-
расшифровкой структуры его входов/выходов и выполняемой функции. Такое
описание, имеющееся у всех приведенных в книге программ, помогает вос-
восприятию программы как целостного элемента и необходимо для начально-
начального знакомства с ней. Этим описанием можно ограничиться в случае полно-
полного доверия к программе. Именно им вы будете пользоваться для восстанов-
восстановления в памяти выполняемых программой функций и ее особенностей в
процессе разработки программного комплекса с ее использованием или его
отладки.
Самый низкий и самый детальный уровень описания программы—опи-
программы—описание на языке программирования, т. е. последовательность написанных друг
за другом ассемблерных команд. Такое описание необходимо для реализа-
реализации программы, но восприятие программы на этом уровне требует огром-
огромных интеллектуальных усилий вследствие необходимости оперативного за-
запоминания и мысленной обработки большой последовательности операто-
операторов и команд- По этой причине восприятие на детальном уровне обычно
необходимо лишь при разработке и модернизации программы.
Решающее значение для понимания программы имеет промежуточный
уровень описания, соответствующий уровню алгоритма. Нередко этот уро-
уровень представляют в виде схемы алгоритма. Эффективность применения
таких схем весьма высока, однако отрыв их от высокоуровневого и низко-
низкоуровневого описания программы (что случается, увы, довольно часто) рез-
резко снижает возможность детального понимания программы с их использо-
использованием. В связи с этим более эффективным приемом является объединение
всех трех уровней описания внутри текста программы, как это сделано в
настоящей книге. При этом промежуточный уровень образуется за счет
тщательно продуманных комментариев на родном языке, подразделяющих
текст программы на функционально законченные части и комментирую-
комментирующие их функции. При таком подходе программа может читаться на любом
уровне, в зависимости от цели чтения. Этот известный прием использован
во всех программах, входящих в настоящую книгу.
Компактность и обозримость программ достигается за счет их модуль-
модульного построения в виде структуры относительно простых и функциональ-
функционально законченных подпрограмм. При этом сложные подпрограммы форми-
формируются из менее сложных модулей-подпрограмм и в свою очередь образуют
готовые модули-подпрограммы для построения более сложных программ.
Такая иерархическая многоуровневая организация комплекса программ яв-
является на практике основным способом борьбы со сложностью решаемых
проблем. При этом каждая программа имеет свое собственное имя, по кото-
которому происходит обращение к ней. Следует обратить внимание на то, что
имена подпрограмм выбраны не случайным образом — в них авторы по-
постарались внести информацию о функции и действиях, производимых вы-
вызываемой подпрограммой. Иными словами, смысловая нагрузка этих имен
имеет большое значение для достижения обозримости и понимания про-
программ, особенно при их иерархическом построении.
Содержание
Предисловие 3
Глава 1. Беззнаковые целые числа 5
Глава 2. Преобразование беззнаковых целых чисел 95
Глава 3. Многобайтные беззнаковые целые числа 123
Глава 4. Знаковые целые числа 157
Заключение к третьему тому 219
Фрунзе Александр, Фрунзе Алексей
Микроконтроллеры?
Это же просто!
3 том
Ответственный редактор ЮЛ. Герасимова
Дизайн обложки И.В. Ермолаева
Верстка И.К. Чикина
ОООяИДСкимси»
ИД № 02736 от 04.09.2000 г.
127015, ул. Бутырская, 41/47
тел./факс: @95) 777-1215
cditor@dian.ru. salcs@dian.ru
www.dian.ru
Подписано в печать
Формат 60хев/|6. Бумага газетная.
Печать офсетная. Усл. псч. л. 14.
Тираж 4000 экз. Заказ № 1783
Отпечатано с готовых диапозитивов