§ Пояснение
По правде говоря, эта программа представляет из себя более технический метод, рассказывающий как программировать на Ассемблере под ДОС, чем сам по себе метод вычисления факториала. Вначале задается параметр BX = любому числу от 1..65535 (хотя последние брать не следует из-за огромного результата, получающегося при возведении в факториал оного). После программа вычисляет последовательность: BX * (BX - 1) * (BX - 2) * ... * 1 и результат оказывается записанным в 65536-байтном буфере.§ Код
1 mov bx, 6542 ; исходные данные 2 3 ; ----- установление указателей ---- 4 mov ax, cs 5 add ah, 0x10 6 mov es, ax 7 mov ds, ax ; рабочие сегменты 8 9 ; ----- инициализация массива ---- 10 xor ax, ax ; AX = 0x0000 11 xor cx, cx ; CX = 0x0000 12 xor di, di ; DI = 0x0000 13 dec cx ; CX = 0xFFFF 14 rep stosb ; записать 65535 значений нуля 15 inc di ; DI = 0, т.к. данее DI = 0xFFFF 16 mov byte [di], 0x01 ; записать 1 как первый множитель 17 18 ; ----- факториал ----- 19U: xor bp, bp ; BP - это перенос при умножении 20 mov cx, 32764 21 xor si, si 22 xor di, di ; новая итерация 23 24 ; ----- умножение ----- 25R: lodsw 26 mul bx ; умножить AX на BX 27 add ax, bp ; с учетом предыдущего переноса 28 adc dx, 0 ; корректировка нового переноса 29 mov bp, dx ; установление значения переноса 30 stosw ; запись нового значения 31 loop R ; повторить с 32764 разрядами 32 33 dec bx ; следующее умножение 34 jne U 35 36 ; CALL ... печать 65536-байтного числа ... 37 38 int 0x20Для выделения рабочего места в программе используется повышение сегмента на 4096 машинных параграфов, что составляет 65536 байт. Программа COM не может занимать в ДОС'е больше места, чем около одной машинной страницы, примерно около 65000 байт. Остальное выделяется под стек. ДОС так устроен, что программы "накладываются" снизу вверх, по все возрастающим адресам, потому над программой по сути, должно находится неиспользуемое место. Это место используется как свободное, для вычисления факториала, в данном случае.
Потому нужно установить указатели в то место, откуда будет начинаться буфер. Это делается так:
1 mov ax, cs ; взять текущий сегмент кода 2 add ah, 0x10 ; добавить страницу вверх 3 mov es, ax ; ES = новая страница 4 mov ds, ax ; DS = ES = AXПотом происходит предварительная очистка массива, в котором будет совершаться последовательное умножение на 1, 2, 3.... BX-2, BX-1, BX. Далее программа очищает буфер и устанавливает его значение как 1.
По сути, этот буфер не более, чем сверхбольшое число, записанное в позиционной системе счисления (как обычная, 10-я система, но только с гораздо большей разрядностью, т.е. с 65536-й разрядностью. Возьмем любое число, и тогда оно будет записываться примерно так:
16383 16382 .... .... .... 2 1 0 ------------------------------------------------------ 0 0 ............. 1544 32553 65535Верхняя строчка - это номер позиции. Самая младшая позиция - это 0, самая старшая - это 16383, хотя предел совершенно не ограничен. Я сейчас не буду выкладывать основы позицонной системы счисления, просто скажу, что если перевести это число в 10-ю систему, получится число:
65535*655360 + 32553*655361 + 1544*655362 + .... + 0 = 6633562963967Огроменное! У него в десятичной системе счисления задействовано 13 разрядов вместо 3. Выгода от этого есть большая, потому что если уж в 65536-й системе счисления получается число с 16384 знаками, то что получится в 10-й! Да, это будет число так число :-) а точнее, количество знаков у него будет примерно 78913. Такого числа и в природе нет, ведь даже количество атомов во Вселенной от силы еле достигнет 10100.
Программа работает так просто, что проще не придумаешь. Вначале берется, с помощью инструкции LODSW, нулевой разряд (автоматически устанавливая на 0 + 1 = 1, т.е. на 1-й), выбранное число умножается на BX. Если полученное число получилось больше 65535, то всё не вместившееся уходит в верхний разряд, в DX. Умножим, например, 0x8852 * 0xA826 = 0x598A:0C2C Нижняя часть этого числа уйдет в AX = 0x0C2C, а верхняя - в DX = 0x598A. Как раз DX будет значением переноса, как при умножении в столбик, и оно является переносом в старший разряд.
Но при умножении в столбик не следует забывать, что может существовать предыдущий перенос. В этой программе он представлен как BP. Так что складывая AX + BP, мы можем получить еще перенос, который, как известно, в процессоре записывается в CF (Carry Flag, флаг переноса). Если он есть, то инструкция ADC DX, 0 добавляет к DX единицу, если нет - то оставляет как есть. После этих всех операции DX переписывается в BP, и становится новым переносом для следующего разряда.
1 add ax, bp ; сложение текущего разряда с учетом переноса 2 adc dx, 0 ; если есть перенос при сложении, добавить 3 ; 1 к переносу в старший разряд 4 mov bp, dx ; теперь перенос становится переносом из 5 ; младшего разрядаПолученное значение текущего разряда, что сохранено в AX, переписывается в этот же рязряд и процесс идет дальше. И так до тех пор, пока не доберется до последнего разряда. Число разрядов было указано в регистре CX. В данном случае, в CX было 32764, значит, именно столько разрядов он и учтет, а остальные "отрежет".
P.S.Так что не надо страдать гигантоманией, и столько хватит для начала ;-)