Вопрос № 179126: Здравствуйте уважаемые эксперты! Помогите пожалуйста решить такую задачку по ассемблеру: Вычислить sin(1/7) с 1000 верными знаками после запятой. Для вычисления использовать разложение синуса в ряд. Вывести результат в файл. Проше напис...
Вопрос № 179126:
Здравствуйте уважаемые эксперты! Помогите пожалуйста решить такую задачку по ассемблеру:
Вычислить sin(1/7) с 1000 верными знаками после запятой. Для вычисления использовать разложение синуса в ряд. Вывести результат в файл.
Проше написать с комментариями) Программа Tasm 5.0. Заранее спасибо)
Отвечает 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
* Стоимость одного СМС-сообщения от 7.15 руб. и зависит от оператора сотовой связи.
(полный список тарифов)
** При ошибочном вводе номера ответа или текста #thank услуга считается оказанной, денежные средства не возвращаются.
*** Сумма выплаты эксперту-автору ответа расчитывается из суммы перечислений на портал от биллинговой компании.