Главная страница
Библиотека (скачать книги)
Скачать софт
Введение в программирование
Стандарты для C++
Уроки по C#
Уроки по Python
HTML
Веб-дизайн
Ассемблер в среде Windows
ActiveX
Javascript
Общее о Линукс
Линукс - подробно
Линукс - новое
Delphi
Паскаль для начинающих
Турбопаскаль
Новости
Партнеры
Наши предложения
Архив новостей





Как видите, число задействованных регистров то же самое, но процедура получилась значительно короче. Недостаток этого способа, кроме большей длительности выполнения — здесь мы напрямую не получаем остатка. Его все же можно получить, если прибавить к тому отрицательному числу, что осталось в регистрах countxx, значение делителя (как бы вернуться на одну итерацию назад), либо если оборвать цикл за одну итерацию до того, как выполнится условие переноса в минус (каждый раз сравнивая значение делимого с делителем — как только первое станет меньше второго, цикл можно обрывать). Другой недостаток— здесь приходится выделять специальные регистры под результат, а делимое при этом все равно окажется испорченным.

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

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

/деление на 64
clr count_data /счетчик до б
div64L:
Isг dataH /сдвинули старший вправо ror dataL /сдвинули младший с переносом inc
count_data cpi count_data, б brne div64L
He правда ли, гораздо изящнее и понятнее, чем деление "в лоб"?

Операции с дробными числами
Усвоив такой прием, попробуем на радостях решить задачку, которая на первый взгляд требует, по крайней мере, знания высшей алгебры — умножить некое число на дробный коэффициент (вещественное число с "плавающей запятой"). Теоретически для этого требуется представить исходные числа в виде "мантисса-порядок", сложить порядки и перемножить мантиссы. Нам же неохота возиться с этим представлением, т. к. мы не проектируем универсальный компьютер, и в подавляющем большинстве реальных задач все конечные результаты у нас представляют собой целые числа (запятая в электронных схемах с индикацией результатов устанавливается на нужном месте принудительно). На самом деле эта задача решается очень просто, если ее свести к последовательному умножению и делению целых чисел, представив реальное число в виде целой дроби с оговоренной точностью. Например, число 0,48576 можно представить, как 48576/100000. И если нам требуется на такой коэффициент умножить, к примеру, результат какого-то измерения, равный 976, то можно действовать, не выходя за рамки диапазона целых чисел: сначала умножить 976 на 48 576 (получится заведомо целое число 47 410 176), а потом поделить результат на L05, чисто механически перенеся запятую на пять разрядов. Получится 474,10176 или, если отбросить дробную часть, 474.
Большая точность нам и не требуется, т. к. исходное число 976 имело три десятичных разряда.

С числами в десятичном виде хорошо работать "руками", просто отсчитывая разряды. Нам же делить на сто тысяч в 8-разрядном МК крайне неудобно — представляете, насколько громоздкая процедура получится? Наше ноу-хау будет состоять в том, что мы для того, чтобы "вогнать" дробное число в целый диапазон, будем использовать не десятичную дробь, а двоичную — деление тогда сведется к вышеописанной механической процедуре сдвига, аналогичной переносу запятой в десятичном виде.
Итак,\гобы умножить 976 на коэффициент 0,48576, следует сначала последний "вручную" умножить, например, на 216 = 65 536, и таким образом получить числитель соответствующей двоичной дроби (у которой знаменатель равен 65 536)— он будет равен 31834,76736, или, с округлением до целого, 31 835. Такой точности хватит, если исходные числа не выходят, как у нас, за пределы трех-четырех десятичных разрядов. Теперь мы в контроллере должны умножить исходную величину 976 на константу 31 835 и полученное число 31 070 960 (оно оказывается 4-байтовым— $01DA1AF0) сдвинуть на 16 разрядов вправо (листинг 7.7).

/в ddHH:ddH:ddM:ddL число $01DA1AF0, ;его надо сдвинуть на 16 разрядов
clr cnt 1 divl6L: ;деление на 65536
Isг ddHH /сдвинули старший
гor ddH /сдвинули 3-й
гor ddM /сдвинули 2-й
ror ddL /сдвинули младший
inc cnt
cpi cnt,16
brne divl6L ;сдвинули-поделили на 2 в "l6

В результате, как. вы можете легко проверить, старшие байты обнулятся, а в. ddM:ddL окажется число 474— тот же самый результат. Но и это еще не все — такая процедура приведена скорее для иллюстрации общего принципа. Ее можно еще больше упростить, если обратить внимание на то, что сдвиг на восемь разрядов есть просто перенос значения одного байта в соседний (в старший, если сдвиг влево, и в младший — если вправо). Итого получится, что для сдвига на 16 разрядов вправо нам нужно всего-навсего отбросить два младших байта и взять из исходного числа два старших ddHH:ddH— это и будет результат. Проверьте— $01DA и есть 474. Никаких других действий вообще не требуется!

Если степень знаменателя дроби, как в данном случае, кратна 8, то действительно не нужно никакого деления, даже в виде сдвига, но чаще всего это не так. Однако и тут приведенный принцип может помочь: например, при делении на 215 (что может потребоваться, если, например, в нашем примере константа больше единицы) вместо пятнадцатикратного сдвига вправо результат можно сдвинуть на один разряд влево (фактически умножив число на два), а потом уже выделить из него старшие два байта. Итого процедура будет состоять из четырех операций и займет четыре такта. А в виде циклического сдвига на 15, как ранее, нам необходимо в каждом цикле выполнить четыре операции сдвига и одну увеличения счетчика, итого 15x5 = 75 простых одно-тактных операций, и еще 15 сравнений, из которых 14 займут два такта — всего 104 такта. А решение "в лоб" — на основе целочисленного деления — еще в несколько раз превышало бы и эту величину. Существенная разница, правда? Вот такая специальная арифметика в МК. Применение таких процедур на практике мы рассмотрим в главе 10.

Генератор случайных чисел
Настоящая генерация случайных чисел в МК требуется не так уж часто, потому что, например, достаточно зафиксировать значение счетного регистра таймера (при счете с переполнением) в произвольный момент времени, определяемый действиями пользователя (например, по нажатию им кнопки). Случайность здесь определяется тем, что таймер считает намного быстрее, чем человек успевает подготовиться к нажатию, и при всем желании попасть в определенное число невозможно.
Но если отказаться от участия человека с его непредсказуемостью и медленностью реакции, то проблема генерации случайных чисел становится довольно сложной. Ведь компьютер— система полностью детерминированная, в которой любое состояние однозначно выводится из предыдущих.

Заметки на полях
Над созданием хорошего компьютерного генератора случайных чисел бились лучшие математические умы, и эта проблема до сих пор однозначно не решена. Насколько она актуальна, можно судить по такому примеру: в конце 2007 г. израильские ученые из университета Хайфы обнаружили дефект в коде генератора случайных чисел (функция CryptGenRandom) в операционной системе Windows, из-за которого злоумышленник при желании может не только просчитать, какие шифровальные ключи будут создаваться системой, но и выяснить, какие из них генерировались ею в прошлом. Обратите внимание — дефект этот существовал со времен Windows 2000, т. е., по крайней мере, восемь лет до того, как его нашли.



     
 

Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru