Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

RFpro.ru: Ассемблер? Это просто! Учимся программировать


Хостинг портала RFpro.ru:
Московский хостер
Профессиональный ХОСТИНГ на базе Linux x64 и Windows x64

РАССЫЛКИ ПОРТАЛА RFPRO.RU

Чемпионы рейтинга экспертов в этой рассылке

Boriss
Статус: Академик
Рейтинг: 2475
∙ повысить рейтинг »
_Ayl_
Статус: Профессионал
Рейтинг: 1920
∙ повысить рейтинг »
vladisslav
Статус: 6-й класс
Рейтинг: 1227
∙ повысить рейтинг »

/ КОМПЬЮТЕРЫ И ПО / Программирование / Assembler (Ассемблер)

Номер выпуска:1367
Дата выхода:26.06.2010, 17:00
Администратор рассылки:Лысков Игорь Витальевич, Старший модератор
Подписчиков / экспертов:233 / 63
Вопросов / ответов:1 / 1
IRC-канал по теме:#assembler

Вопрос № 179126: Здравствуйте уважаемые эксперты! Помогите пожалуйста решить такую задачку по ассемблеру: Вычислить sin(1/7) с 1000 верными знаками после запятой. Для вычисления использовать разложение синуса в ряд. Вывести результат в файл. Проше напис...



Вопрос № 179126:

Здравствуйте уважаемые эксперты! Помогите пожалуйста решить такую задачку по ассемблеру:

Вычислить sin(1/7) с 1000 верными знаками после запятой. Для вычисления использовать разложение синуса в ряд. Вывести результат в файл.

Проше написать с комментариями) Программа Tasm 5.0. Заранее спасибо)

Отправлен: 16.06.2010, 16:30
Вопрос задал: Петров Юрий Иванович, Посетитель
Всего ответов: 1
Страница вопроса »


Отвечает amnick, Профессионал :
Здравствуйте, Петров Юрий Иванович.

Задача может быть решена с использованием целочисленной "длинной" арифметики. Значение синуса находится в диапазоне -1.0..+1.0, в данном случае — 0 < sin(1/7) < 1. Для того, чтобы свести вычисления к операциям с целыми числами, вычисления выполняются с масштабным множителем 101004. Если проводить вычисления с множителем 101000 (нам требуется 1000 значащих цифр), то последняя цифра может быть неверной, поэтому добавляется еще один "разряд" в используемой в расчетах системе счисления.

Числа хранятся в массивах 16-битных слов, используется система счисления с основанием 10000, т.е. в каждом разряде хранится не одна, а 4 десятичных цифры. Если использовать 32-битные регистры, то основание системы счисления можно увеличить. (Для повышения эффективности вычислений и экономии памяти основание системы счисления следует выбирать возможно большим. Однако, если оно не кратно 10, то усл ожняется ввод и вывод чисел в обычном строковом представлении.)

Подробно можно почитать в статье "Длинная арифметика" (В.Гольдштейн).

Код:
; Вычислить sin(1/7) с 1000 верными знаками после запятой.
; Для вычисления использовать разложение синуса в ряд.
; Вывести результат в файл.
; Запись с разбиением на строки по 60 символов.

locals @@
model tiny

BASE EQU 10000

.data

msgInfo db 13,10,'Calculating sin(1/7) with 1000 significant digits',13,10,'$'
msgExit db 13,10,'Done. Press any key to exit...$'
msgCreateError db 13,10,"Can't create file!$"
msgWriteError db 13,10,'Write error.$'
szFileName db 'sin_1_7.txt',0 ; предопределенное имя файла

i2 dw 2 ; очередной множитель факториала в знаменателе

; первое слово длинного числа содержит кол-во "цифр"
; последующие слова - собственно "цифры", начиная с младшей
; 253 = 1 (слово длины) + 251 (первоначально 1004 нуля) + 1 (старшая 1) -- итого 10^1004
; Вычисления проводим с 1004 разрядами (251 "цифра" в системе счисления с основанием 10000)
; (если вычислять с 1000 цифрами, то последняя может быть неверной)

huge_sum dw 253 dup (0) ; сумма
huge_a dw 253 dup (0) ; очередной член последовательности

sResult db '0.'
sNumber db 1010 dup (0)

.code
.386
org 100h
start:
mov ah,9 ; информационное сообщение
mov dx,offset msgInfo
int 21h

; сумма = 0
mov [huge_sum],1 ; кол-во "цифр"

; вычисляем первый член с коэффициентом 10^1004 - это позволяет выполнять
; все операции с целыми числами
mov [huge_a],252 ; кол-во "цифр"
mov [huge_a+2*252],1 ; huge_a = 10^1004 (1 и 1004(=251*4) нулей)

mov ebx,7 ; EBX - "короткий" делитель
mov di,offset huge_a ; DI - указатель на длинное число
call huge_div ; huge_a = 1/7 (умноженное на 10^1004)

mov si,di ; SI - указатель на второе слагаемое
mov di,offset huge_sum ; DI - указатель на первое слагаемое
call huge_add ; huge_sum += huge_a

; huge_a содержит первый член последовательности с коэффициентом 10^1004
; Каждый следующий член последовательности вычисляем по рекурсивной формуле:
; a[i+1] = a[i] * (-1)^(i+1) / ( 7^2 * 2*i *(2*i+1) )
; За один проход цикла вычисляем 2 члена:
; s = s - a[i+1] + a[i+2]
; Таким образом, мы ограничиваемся "коротким& quot; делителем
; Вместо i храним в переменной i2 значение i*2 - следующий множитель факториала

@@calc:
; член последовательности со знаком '-'
movzx eax,[i2] ; i*2
lea ebx,[eax+1] ; i*2 + 1
mul ebx ; EDX:EAX = 2*i*(2*i+1)
inc bx
mov [i2],bx ; для следующего члена
imul ebx,eax,49 ; EBX = 7^2 * 2*i*(2*i+1)

mov di,offset huge_a ; DI - указатель на длинное число
call huge_div ; huge_a /= 7^2 * 2*i*(2*i+1)
call huge_null
je @@calc_end ; заканчиваем вычисления, если очередной член = 0

mov si,di ; SI - указатель на вычитаемое
mov di,offset huge_sum ; DI - указатель на уменьшаемое
call huge_sub ; huge_sum -= huge_a

; член последовательности со знаком '+'
movzx eax,[i2] ; i*2
lea ebx,[eax+1] ; i*2 + 1
mul ebx ; EDX:EAX = 2*i*(2*i+1)
inc bx
mov [i2],bx ; для следующего члена
imul ebx,eax,49 ; EBX = 7^2 * 2*i*(2*i+1)

mov di,offset huge_a ; DI - указа тель на длинное число
call huge_div ; huge_a /= 7^2 * 2*i*(2*i+1)
call huge_null
je @@calc_end ; заканчиваем вычисления, если очередной член = 0

mov si,di ; SI - указатель на второе слагаемое
mov di,offset huge_sum ; DI - указатель на первое слагаемое
call huge_add ; huge_sum += huge_a
jmp @@calc

@@calc_end:

; округляем
mov ax,[huge_sum+2] ; младшее слово
cmp ax,BASE/2
jb @@1
; в данном случае достаточно инкрементировать предыдущую "цифру" (переноса не будет)
; в общем случае должен быть цикл пока есть перенос в следующий разряд
inc [huge_sum+4]
mov [huge_sum+2],0
@@1:

; Преобразуем число в строку
mov si,offset huge_sum
mov di,offset sNumber
call huge2str
; так можно вычислить длину строки
; mov cx,offset sResult
; sub di,cx
; inc di ; длина строкового представления (включая '0.')

; но нам надо 1000 цифр после запятой:
mov di,1002 ; 100 0 + 2 для '0.'

; выводим число на экран
mov ah,40h
mov bx,1
mov dx,offset sResult
mov cx,di ; длина числа
int 21h

; создаем файл и выводим в него число
mov ah,3Ch ; создаем файл
mov dx,offset szFileName ; DS:DX - адрес имени файла
xor cx,cx ; атрибуты
int 21h
jnc @@create_ok

; AX содержит код ошибки, для простоты его игнорируем
; В серьезной программе надо выводить осмысленное сообщение
; в соответствии с кодом.

mov ah,9 ; сообщение об ошибке создания
mov dx,offset msgCreateError
int 21h
jmp short @@msg_key

@@create_ok:
mov bx,ax ; описатель (handle) файла

; запись без разбиения на строки
; mov ah,40h ; функция записи в файл
; mov dx,offset sResult ; адрес буфера
; mov cx,di ; кол-во байт для записи
; int 21h

; запись с разбиением на строки по 60 символов
mov si,offset sResult ; адрес буфера
@@next60:
mov cx,60 ; кол-во б айт для записи
cmp di,cx ; осталось еще много?
ja @@w1 ; да
mov cx,di ; нет, пишем только остаток
@@w1: mov ah,40h ; функция записи в файл
mov dx,si ; указатель на очередной блок
int 21h
jc @@write_error ; CF=1 - ошибка записи
cmp ax,cx ; записалось все?
je @@w2
@@write_error:
mov ah,9
mov dx,offset msgWriteError
int 21h
jmp short @@close
@@w2:
add si,cx ; продвигаем указатель на следующую часть для записи
; записываем конец строки
mov dx,offset msgInfo ; отсюда берем только 13,10 (CR/LF)
mov cl,2 ; CX=2 (у нас CH=0)
mov ah,40h ; функция записи в файл
int 21h

sub di,60 ; DI -= 60 - сколько осталось записать
jg @@next60 ; знаковое сравнение! больше 0? если да, то следующая строка
@@close:
mov ah,3Eh ; закрываем файл
int 21h

@@msg_key:
mov dx,offset msgExit ; предложение нажать клавишу для выхода
@@last_msg:
mov ah,9
int 21h

xor ah ,ah ; ждем нажатия любой клавиши (чтобы увидеть результат,
int 16h ; прежде чем консоль в Windows закроется)
@@exit:
int 20h

;--------------------------------------------------
; функция проверяет длинное число на равенство нулю
; Вход:
; DI - указатель на длинное число
; Выход:
; ZF=1 - длинное число равно нулю
;--------------------------------------------------
huge_null PROC
cmp word ptr [di],1
jne @@ret
cmp word ptr [di+2],0
@@ret: ret
huge_null ENDP

;-------------------------------------------------
; функция складывает два длинных числа и помещает
; результат на место первого слагаемого:
; a += b
; Вход:
; DI - указатель на первое слагаемое
; SI - указатель на второе слагаемое
;-------------------------------------------------
huge_add PROC
push di
lodsw ; длина второго слагаемого
mov cx,[di] ; длина первого слагаемого
cmp cx,ax ; if( a[0] < b[0] ) a[0] = b[0];
jae @@1
mov cx,ax
mov [di],cx
@@1:
; **в нашем случае** складывать нужно до размера большего числа
inc di
inc di
xor dx,dx ; перенос (сколько у нас "в уме", сначала 0)
mov bx,BASE
@@next:
lodsw
add ax,[di]
add ax,dx ; a[i] += b[i] + перенос
xor dx,dx ; предполагаем, что переноса нет
cmp ax,bx ; сумма цифр > основания системы счисления?
jb @@2
sub ax,bx ; да, есть перенос
inc dx ; перенос в следующий разряд
@@2:
stosw
loop @@next

; если после сложения остался еще перенос, то нужно добавить еще одну цифру
pop bx
test dx,dx
jz @@ret
mov [di],dx
inc word ptr [bx]
@@ret: ret
huge_add ENDP

;-------------------------------------------------
; функция вычитает два длинных числа и помещает
; результат на место уменьшаемого:
; a -= b
; Вход:
; DI - указатель на уменьшаемое
; SI - указатель на вычитаемое
;-------------------------------------------------
huge_sub PROC
push di
mov cx,[di] ; длина уменьшаемого
inc di
inc di
xor d x,dx ; перенос (сколько у нас "в уме", сначала 0)
mov bx,BASE
@@next:
inc si
inc si
mov ax,[di]
sub ax,[si]
sub ax,dx ; AX = a[i] - b[i] - r
; если результат отрицательный, то был заем из следующего разряда
mov dx,0 ; предполагаем, что заема не было
jge @@1
inc dx ; происходит заем из следующего разряда
add ax,bx
@@1:
stosw
loop @@next

; Разность может содержать меньше цифр, поэтому нужно при
; необходимости уменьшить количество цифр
; while( a[0] > 1 && a[a[0]] == 0 ) --a[0];

pop si
@@null:
mov bx,[si]
cmp bx,1
jbe @@ret

shl bx,1
cmp word ptr [si+bx],0
jne @@ret
dec word ptr [si]
jmp short @@null
@@ret: ret
huge_sub ENDP

;-------------------------------------------------
; функция делит длинное число на короткое число
; и возвращает остаток от деления
; Вход:
; EBX = "короткий" делитель
; DI = указатель на длинное число
; Выход:
; DI - не изменяется
; (E)DX = остаток
;-------------------------------------------------
huge_div PROC
xor edx,edx ; остаток, изначально 0
mov ebp,BASE ; основание системы счисления
mov cx,[di]
add di,cx
add di,cx
std ; строковые команды будут уменьшать указатели
@@next:
mov eax,edx
mul ebp ; EDX:EAX = остаток * BASE
movzx esi,word ptr [di]
add eax,esi ; EAX = остаток * BASE + a[i] (приписывание очередной цифры)
adc edx,0
div ebx ; EAX = EDX:EAX / делитель, EDX = остаток
stosw
loop @@next
cld

; Частное может содержать меньше цифр, поэтому нужно при необходимости
; уменьшить количество цифр
; while( a[0] > 1 && a[a[0]] == 0 ) --a[0];

; здесь DI = указатель на длинное число
@@null:
mov bx,[di]
cmp bx,1
jbe @@ret
shl bx,1
cmp word ptr [di+bx],0
jne @@ret
dec word ptr [di]
jmp short @@null
@@ret: ret
huge_div ENDP

;-------------------------------------------------
; Преобразование длинного числа в строку
; Каждая "цифр а" длинного числа, кроме старшей,
; представляется 4-мя обычными цифрами.
; Вход:
; SI - адрес длинного числа
; DI - адрес строки
; Выход:
; DI - указывает на последний символ строки
;-------------------------------------------------
huge2str PROC
lodsw ; длина длинного числа в "цифрах"
dec ax
push ax

shl ax,1 ; теперь длина в байтах
add si,ax ; SI - адрес старшей "цифры"
mov bx,10 ; преобразуем в 10-тичную систему

; старшую "цифру" преобразуем в строку без дополнения нулями
std
lodsw
xor cx,cx ; обнуляем счетчик цифр
@@1:
xor dx,dx ; будем делить DX:AX на BX
div bx ; DX = последняя цифра
push dx ; мы получаем цифры в обратном порядке
inc cx ; счетчик цифр
test ax,ax ; повторяем, пока частное в AX != 0
jnz @@1

cld
@@2: ; формируем строку из цифр
pop ax ; извлекаем очередную цифру из стека
add al,'0' ; пе реводим её в символ
stosb ; переносим в строку
loop @@2
dec di

pop cx
jcxz @@ret
std
@@next: ; цикл по "цифрам" длинного числа
push cx
mov cx,4 ; кол-во разрядов
add di,cx ; теперь DI указывает на последнее место в очередной "четверке"
lodsw ; очередная "цифра" длинного числа
@@3:
xor dx,dx ; будем делить DX:AX на BX
div bx ; DX = последняя цифра
add dl,'0'
mov [di],dl ; мы получаем цифры в обратном порядке
dec di
dec cx ; счетчик цифр
test ax,ax ; повторяем, пока частное в AX != 0
jnz @@3

mov al,'0' ; оставшиеся позиции заполняем '0'
rep stosb

add di,4 ; очередная "четверка" заполнена
pop cx
loop @@next
cld
@@ret:
ret
huge2str ENDP
end start

Результат:
Код:
0.1423717297922636671652723207062311707868670997593640610136
194683306299014902000278428962694972948706355450012362928736
988326239663841550579269451498009380398969875424016888475083
760439150437675853170489265322562811417203505843547741982870
512236074739065042448115815144048550516885548516303882223127
095934460341174304098129103301450613983380948436284030188810
514583923452114301299791746267616059451181656967225353885593
949524145813619254723597593533797149813144777431740353912553
358124366308044179080393491559664091515798824223246479767202
524021771806763790603850529366190151236073471947459707542842
920499140362397811449201003376918743683891660563885237898132
926977078066502357981992688298767739290087571684219189064959
994823146391567442367759 499661963574931080346955777550653639
388004838583631823721949859158934232550823262223606970479897
009033800330983355194421678147350384108029533247509739770641
576680863250756502861555013536261986827751952091145568370918
890233114174164498487582823007240091266502

Использовался TASM 3.1. Компилировать в COM-файл.

Успехов!

Ответ отправил: amnick, Профессионал
Ответ отправлен: 22.06.2010, 21:22
Номер ответа: 262238

Оценка ответа: 5
Комментарий к оценке:
Спасибо, прога работает отлично)

Вам помог ответ? Пожалуйста, поблагодарите эксперта за это!
Как сказать этому эксперту "спасибо"?
  • Отправить SMS #thank 262238 на номер 1151 (Россия) | Еще номера »
  • Отправить WebMoney:

  • Оценить выпуск »
    Нам очень важно Ваше мнение об этом выпуске рассылки!

    Задать вопрос экспертам этой рассылки »

    Скажите "спасибо" эксперту, который помог Вам!

    Отправьте СМС-сообщение с тестом #thank НОМЕР_ОТВЕТА
    на короткий номер 1151 (Россия)

    Номер ответа и конкретный текст СМС указан внизу каждого ответа.

    Полный список номеров »

    * Стоимость одного СМС-сообщения от 7.15 руб. и зависит от оператора сотовой связи. (полный список тарифов)
    ** При ошибочном вводе номера ответа или текста #thank услуга считается оказанной, денежные средства не возвращаются.
    *** Сумма выплаты эксперту-автору ответа расчитывается из суммы перечислений на портал от биллинговой компании.


    © 2001-2010, Портал RFpro.ru, Россия
    Авторское право: ООО "Мастер-Эксперт Про"
    Автор: Калашников О.А. | Программирование: Гладенюк А.Г.
    Хостинг: Компания "Московский хостер"
    Версия системы: 2010.6.16 от 26.05.2010

    В избранное