   #Start Prev Next Contents

   Jak pisac programy w jezyku asembler?

Czesc 5 - Koprocesor, czyli jak liczyc na ulamkach. Odwrotna Notacja Polska

   Jak zapewne wiekszosc wie, koprocesor (FPU = Floating Point Unit, NPX
   = Numerical Processor eXtension) sluzy do wykonywania dzialan
   matematycznych. Podstawowy procesor tez oczywiscie wykonuje dzialania
   matematyczne (dodawanie, mnozenie, ...) ale tylko na liczbach
   calkowitych. Z czasem jednak przyszla potrzeba wykonywania obliczen na
   liczbach niecalkowitych, czyli ulamkach (liczbach
   zmiennoprzecinkowych). Dlatego firmy produkujace procesory zaczely je
   wyposazac wlasnie w uklady wspomagajace prace na ulamkach. Do
   procesorow 8086, 80286, 80386 byly dolaczane jako osobne uklady
   koprocesory: 8087, 80287, 80387 (80187 nie wprowadzil zadnych
   istotnych nowosci. Byla to przerobka 8087, a moze nawet byl to po
   prostu ten sam uklad). Procesory 486SX mialy jeszcze oddzielny
   koprocesor (80387) a od 486DX (w kazdym razie u Intela) koprocesor byl
   juz na jednym chipie z procesorem. I tak jest do dzis.

   Ale dosc wstepu. Pora na szczegoly.
     _________________________________________________________________

Typy danych

   Zanim zaczniemy cokolwiek robic, trzeba wiedziec, na czym ten caly
   koprocesor operuje.
   Oprocz liczb calkowitych, FPU operuje na liczbach ulamkowych roznej
   precyzji:
     * Pojedyncza precyzja. Liczby takie zajmuja po 32 bity (4 bajty) i
       ich wartosc maksymalna wynosi ok. 10^39 (10^39). Znane sa
       programistom jezyka C jako "float".
     * Podwojna precyzja. 64 bity (8 bajtow), max = ok. 10^409 (10^409).
       W jezyku C sa znane jako "double"
     * Rozszerzona precyzja. 80 bitow (10 bajtow), max = ok. 10^4930
       (10^4930). W jezyku C sa to "long double"

   Jak widac, ilosci bitow sa oczywiscie skonczone. Wiec nie kazda liczbe
   rzeczywista da sie dokladnie zapisac w postaci binarnej. Na przyklad,
   jedna dziesiata (0.1) zapisana dwojkowo jest ulamkiem nieskonczonym
   okresowym! Poza tym, to, czego nas uczyli na matematyce, na przyklad
   oczywista rownosc: a+(b-a)=b nie musi byc prawda w swiecie ulamkow w
   procesorze ze wzgledu na brak precyzji!

   Poza ulamkami, FPU umie operowac na BCD (binary coded decimal). W
   takich liczbach 1 bajt dzieli sie na 2 czesci po 4 bity, z ktorych
   kazda moze miec wartosc 0-9. Caly bajt reprezentuje wiec liczby od 0
   do 99, w ktorych cyfra jednosci jest w mlodszych 4 bitach a cyfra
   dziesiatek - w starszych 4 bitach.

   Szczegolami zapisu liczb ulamkowych nie bedziemy sie tutaj zajmowac.
   Polecam, tak jak poprzednio: strony Intela i strony AMD, gdzie
   znajduje sie tez kompletny opis wszystkich instrukcji procesora i
   koprocesora.
     _________________________________________________________________

Rejestry koprocesora

   Po zapoznaniu sie z typami (a przede wszystkim z rozmiarami) liczb
   ulamkowych, powstaje pytanie: gdzie koprocesor przechowuje takie
   ilosci danych?
   FPU ma specjalnie do tego celu przeznaczonych 8 rejestrow, po 80 bitow
   kazdy. W operacjach wewnetrznych (bez pobierania lub zapisywania
   danych do pamieci) FPU zawsze uzywa rozszerzonej precyzji.

   Rejestry danych nazwano st(0), st(1), ... , st(7) (NASM: st0 ... st7).
   Nie dzialaja jednak one tak, jak zwykle rejestry, lecz jak ... stos!
   To znaczy, ze dowolnie dostepna jest tylko wartosc ostatnio polozona
   na stosie czyli wierzcholek stosu, w tym przypadku: st(0). Znaczy to,
   ze do pamieci (FPU operuje tylko na wlasnych rejestrach lub pamieci -
   nie moze uzywac rejestrow ogolnego przeznaczenia na przyklad EAX itp.)
   moze byc zapisana tylko wartosc z st(0), a kazda wartosc pobierana z
   pamieci idzie do st(0) a stare st(0) przesuwa sie do st(1) itd. kazdy
   rejestr przesuwa sie o 1 dalej. Jezeli brakuje na to miejsca, to FPU
   moze wygenerowac przerwanie (wyjatek) a rejestry danych beda
   prawdopodobnie zawierac smieci.

   Operowanie na rejestrach FPU bedzie wymagalo nieco wiecej uwagi niz na
   zwyklych, ale i do tego mozna sie przyzwyczaic.
     _________________________________________________________________

Instrukcje koprocesora

   Zacznijmy od omowienia kilku podstawowych instrukcji. Przez [mem] bede
   nazywal dane bedace w pamieci (32-, 64- lub 80-bitowe, "int" oznacza
   liczbe calkowita), "st" jest czestym skrotem od "st(0)". Jezeli
   komenda konczy sie na P to oznacza, ze zdejmuje dane raz ze stosu, PP
   oznacza, ze zdejmuje 2 razy: st(0) i st(1).
    1. Instrukcje przemieszczenia danych:
          + FLD/FILD [mem] - zaladuj liczbe rzeczywista/calkowita z
            pamieci. Dla liczby rzeczywistej jest to 32, 64 lub 80 bitow.
            Dla calkowitej - 16, 32 lub 64 bity.
          + FST [mem32/64/80] - do pamieci idzie liczba ze st(0).
          + FSTP [mem32/64/80] - zapisz st(0) w pamieci i zdejmij je ze
            stosu. Znaczy to tyle, ze st(1) o ile istnieje, staje sie
            st(0) itd. kazdy rejestr cofa sie o 1.
          + FIST [mem16/32] - ewentualnie obcieta do calkowitej liczbe z
            st(0) zapisz do pamieci.
          + FISTP [mem16/32/64] - jak wyzej, tylko ze zdjeciem ze stosu.
          + FXCH st(i) - zamien st(0) z st(i).
    2. Instrukcje ladowania stalych
          + FLDZ - zaladuj zero. st(0) = 0.0
          + FLD1 - zaladuj 1. st(0) = 1.0
          + FLDPI - zaladuj pi.
          + FLDL2T - zaladuj log2(10)
          + FLDL2E - zaladuj log2(e)
          + FLDLG2 - zaladuj log(2)=log10(2)
          + FLDLN2 - zaladuj ln(2)
    3. Dzialania matematyczne:
          + dodawanie: FADD, skladnia identyczna jak w odejmowaniu
            prostym
          + odejmowanie:
               o FSUB [mem32/64]       st := st-[mem]
               o FSUB st(0),st(i)       st := st-st(i)
               o FSUB st(i),st(0)       st(i) := st(i)-st(0)
               o FSUBP st(i), st(0)       st(i) := st(i)-st(0) i zdejmij
               o FSUBP (bez argumentow) = FSUBP st(1),st(0)
               o FISUB [mem16/32int]       st := st-[mem]
          + odejmowanie odwrotne:
               o FSUBR [mem32/64]       st := [mem]-st(0)
               o FSUBR st(0),st(i)       st := st(i)-st(0)
               o FSUBR st(i),st(0)       st(i) := st(0)-st(i)
               o FSUBRP st(i),st(0)       st(i) := st(0)-st(i) i zdejmij
               o FSUBRP (bez argumentow) = FSUBRP st(1),st(0)
               o FISUBR [mem16/32int]       st := [mem]-st
          + mnozenie: FMUL, skladnia identyczna jak w odejmowaniu
            prostym.
          + dzielenie: FDIV, skladnia identyczna jak w odejmowaniu
            prostym.
          + dzielenie odwrotne: FDIVR, skladnia identyczna jak w
            odejmowaniu odwrotnym.
          + wartosc bezwzgledna: FABS (bez argumentow) zastepuje st(0)
            jego wartoscia bezwzgledna.
          + zmiana znaku: FCHS:       st(0) := -st(0).
          + pierwiastek kwadratowy: FSQRT:      st(0) := SQRT[ st(0) ]
          + reszty z dzielenia: FPREM, FPREM1       st(0) := st(0) mod
            st(1).
          + zaokraglanie do liczby calkowitej: FRNDINT:      st(0) :=
            (int)st(0).
    4. Komendy porownania:
       - FCOM/FCOMP/FCOMPP, FUCOM/FUCOMP/FUCOMPP, FICOM/FICOMP,
       FCOMI/FCOMIP, FUCOMI/FUCOMIP, FTST, FXAM.
       Tutaj trzeba troche omowic sytuacje. FPU oprocz rejestrow danych
       zawiera takze rejestr kontrolny (16 bitow) i rejestr stanu (16
       bitow).
       W rejestrze stanu sa 4 bity nazwane C0, C1, C2 i C3. To one
       wskazuja wynik ostatniego porownania, a uklad ich jest taki sam,
       jak flag procesora, co pozwala na ich szybkie przeniesienie do
       flag procesora. Aby odczytac wynik porownania, nalezy zrobic takie
       cos:
        fcom
        fstsw   ax      ; tylko od 386. Inaczej:
                        ; fstsw word ptr [zmienna] / mov ax,[zmienna]
        sahf            ; AH -> flagi
       i uzywac normalnych komend JE, JB itp.
          + FCOM st(n)/[mem] -       porownaj st(0) z st(n) (lub zmienna
            w pamieci) bez zdejmowania st(0) ze stosu FPU
          + FCOMP st(n)/[mem] -       porownaj st(0) z st(n) (lub zmienna
            w pamieci) i zdejmij st(0)
          + FCOMPP -       porownaj st(0) z st(1) i zdejmij oba ze stosu
          + FICOM [mem] -       porownaj st(0) ze zmienna calkowita 16-
            lub 32-bitowa w pamieci
          + FICOMP [mem] -       porownaj st(0) ze zmienna calkowita 16-
            lub 32-bitowa w pamieci, zdejmij st(0)
          + FCOMI st(0), st(n) -       porownaj st(0) z st(n) i ustaw
            flagi procesora, nie tylko FPU
          + FCOMIP st(0), st(n) -       porownaj st(0) z st(n) i ustaw
            flagi procesora, nie tylko FPU, zdejmij st(0)
       Komendy konczace sie na I lub IP zapisuja swoj wynik bezposrednio
       do flag procesora. Mozna tych flag od razu uzywac (JZ, JA, ...).
       Te komendy sa dostepne tylko od 386.
       FTST porownuje st(0) z zerem.
       FXAM bada, co jest w st(0) - prawidlowa liczba, blad (NaN = Not a
       Number), czy 0.
    5. Instrukcje trygonometryczne:
          + FSIN - st(0) := sinus [st(0)]
          + FCOS - st(0) := kosinus [st(0)]
          + FSINCOS - st(0) := kosinus [st(0)], st(1) := sinus [st(0)]
          + FPTAN - partial tangent = tangens st(0) := tg [st(0)]
          + FPATAN - arcus tangens st(0) := arctg [st(0)]
    6. Logarytmiczne, wykladnicze:
          + FYL2X       st(1) := st(1)*log2[st(0)] i zdejmij
          + FYL2XPI       st(1) := st(1)*log2[ st(0) + 1.0 ] i zdejmij
          + F2XM1       st(0) := 2^[st(0)] - 1
    7. Instrukcje kontrolne:
          + FINIT/FNINIT - inicjalizacja FPU. Litera N po F oznacza, aby
            nie brac po uwage potencjalnych niezalatwionych wyjatkow.
          + FLDCW, FSTCW/FNSTCW - Load/Store control word - zapisuje 16
            kontrolnych bitow do pamieci, gdzie mozna je zmieniac na
            przyklad aby zmienic sposob zaokraglania liczb.
          + FSTSW/FNSTSW - zapisz do pamieci (lub rejestru AX) slowo
            statusu, czyli stan FPU
          + FCLEX/FNCLEX - wyczysc wyjatki
          + FLDENV, FSTENV/FNSTENV - wczytaj/zapisz srodowisko (rejestry
            stanu, kontrolny i kilka innych, bez rejestrow danych).
            Wymaga 14 albo 28 bajtow pamieci, w zaleznosci od trybu pracy
            procesora (rzeczywisty - DOS lub chroniony - Windows/Linux).
          + FRSTOR, FSAVE/FNSAVE - jak wyzej, tylko ze z rejestrami
            danych. Wymaga 94 lub 108 bajtow w pamieci, zaleznie od trybu
            procesora.
          + FINCSTP, FDECSTP - zwieksz/zmniejsz wskaznik stosu - przesun
            st(0) na st(7), st(1) na st(0) itd. oraz w druga strone,
            odpowiednio.
          + FFREE - zwolnij podany rejestr danych
          + FNOP - no operation. Nic nie robi, ale zabiera czas.
          + WAIT/FWAIT - czekaj, az FPU skonczy prace. Uzywane do
            synchronizacji z CPU.
     _________________________________________________________________

Przyklady

   Dosc juz teorii, pora na przyklady. Programiki te wymyslilem piszac
   ten kurs.

   Przyklad 1 (bedzie to program typu .exe, bo dodamy moja biblioteke do
   wyswietlania wynikow):
   (przeskocz program wyswietlajacy czestotliwosc zegara)
; TASM:
; z wyswietlaniem:
;   tasm naszplik.asm
;   tlink naszplik.obj bibl\lib\bibldos.lib
; bez wyswietlania:
;   tasm naszplik.asm
;   tlink naszplik.obj

.model small
.stack 400h                     ; stos dla programu .exe
.data

dzielna         DQ 1234DDh      ; 4013 91a6 e800 0000 0000
dzielnik        DQ 10000h
iloraz          DT ?

.code

; jesli nie chcesz wyswietlania, usun te linijke nizej:
 include incl\std_bibl.inc

start:
        mov     ax, @data
        mov     ds, ax
        mov     es, ax          ; konieczne w programie typu .exe !
                                ; Zycie przestaje byc
                                ; wygodne. DOS juz nam nie ustawi
                                ; DS=ES=CS. A nasze dane sa w
                                ; segmencie kodu, stad ustawiamy DS=CS
                                ; W programie typu .com to na pewno
                                ; nie zaszkodzi.

        finit                   ; zawsze o tym pamietaj !

        fild    qword ptr [dzielna]     ; ladujemy dzielna. st(0) = 1234DD
        fild    qword ptr [dzielnik]    ; ladujemy dzielnik. st(0) = 10000h,
                                        ; st(1) = 1234DD
        fdivp                           ; dzielimy. st(1) := st(1)/st(0) i
                                        ; zdejmij. st(0) ~= 18.2
        fstp    tbyte ptr [iloraz]      ; zapisujemy st(0) do pamieci i
                                        ; zdejmujemy ze stosu

; jesli nie chcesz wyswietlania, usun te 3 linijki nizej:
        mov     di, offset iloraz ; DI=adres zmiennej zawierajacej wynik
        piszd80                   ; wyswietl wynik
        nwln                      ; przejdz do nowej linii

        mov     ax, 4c00h
        int     21h

end start
     _________________________________________________________________

   Teraz wersja dla NASMa. O tym, jak NASMem zrobic program typu .exe
   napisane jest w jego dokumentacji. Wymaga to przede wszystkim
   stworzenia wlasnego segmentu stosu i nakierowanie na niego rejestrow
   SS:SP. Trzeba miec tez odpowiedni linker, na przyklad VAL. Mozna
   jednak uzyc jednego z plikow dolaczonych do mojej biblioteki i tak tez
   zrobimy.
   (przeskocz ten program w wersji NASMa)
; NASM:
; z wyswietlaniem:
;   nasm -f obj -o fpu1.obj fpu1.asm
;   val fpu1.obj,fpu1.exe,,bibl\lib\bibldos.lib,
; bez wyswietlania:
;   nasm -f obj -o fpu1.obj fpu1.asm
;   val fpu1.obj,fpu1.exe,,,

; czesciowa zgodnosc z TASMem:
%include "bibl\incl\dosbios\nasm\do_nasma.inc"

; jesli nie chcesz wyswietlania, usun te linijke nizej:
%include "bibl\incl\dosbios\nasm\std_bibl.inc"

.model small
.stack 400h
.code

..start:
        mov     ax, cs
        mov     ds, ax
        mov     es, ax          ; konieczne w programie typu .exe !
                                ; Zycie przestaje byc
                                ; wygodne. DOS juz nam nie ustawi DS=ES=CS.
                                ; A nasze dane sa w
                                ; segmencie kodu, stad ustawiamy DS=CS.
                                ; W programie typu .com to na pewno nie
                                ; zaszkodzi.

        finit                   ; zawsze o tym pamietaj !

        fild    dword [dzielna] ; ladujemy dzielna. st(0) = 1234DD
        fild    dword [dzielnik]; ladujemy dzielnik. st(0) = 10000h,
                                ; st(1) = 1234DD
        fdivp   st1, st0        ; dzielimy. st(1) := st(1)/st(0) i
                                ; zdejmij. st(0) ~= 18.2
                                ; FASM: fdivp st(1)

        fstp    tword [iloraz]  ; zapisujemy st(0) do pamieci i zdejmujemy
                                ; ze stosu

; jesli nie chcesz wyswietlania, usun te 3 linijki nizej:
        mov     di, iloraz
        piszd80                 ; wyswietl wynik
        nwln                    ; przejdz do nowego wiersza

        mov     ax, 4c00h
        int     21h

align 8                         ; NASM w tym miejscu dorobi kilka NOPow
                                ; (instrukcji nic nie robiacych, ale
                                ; zabierajacych czas), aby adres dzielil sie
                                ; przez 8 (patrz dalej).

dzielna         dd 1234ddh      ; 4013 91a6 e800 0000 0000
dzielnik        dd 10000h

iloraz          dt 0.0
     _________________________________________________________________

   Wersja dla FASMa:
   (przeskocz ten sam program w wersji FASMa)
; FASM:
; z wyswietlaniem (stary format biblioteki - OMF):
;   fasm fpu1.asm fpu1.obj
;   alink fpu1.obj  bibl\lib\bibldos.lib -c- -entry _start -oEXE -m-
; z wyswietlaniem (nowy format biblioteki - COFF):
;   fasm fpu1.asm fpu1.obj
;   ld -s -o fpu1.exe fpu1.obj bibl\lib\bibldos.a
; bez wyswietlania:
;   fasm fpu1.asm fpu1.exe

; jesli chcesz wyswietlanie (stary format biblioteki - OMF):
format coff
public _start
public start
include "bibl\incl\dosbios\fasm\std_bibl.inc"
use16

; jesli chcesz wyswietlanie (nowy format biblioteki - COFF):
;format coff
;public _start
;public start
;include "bibl\incl\dosbios\fasm32\std_bibl.inc"

; jesli nie chcesz wyswietlania:
;format MZ
;entry kod:_start
;stack 400h
;segment kod

start:
_start:
        ; wylaczyc trzy ponizsze linie w przypadku FASMa z nowym formatem
        ; biblioteki (32-bitowy COFF nie pozwala na manipulacje segmentami)
        mov     ax, cs
        mov     ds, ax
        mov     es, ax          ; konieczne w programie typu .exe !
                                ; Zycie przestaje byc
                                ; wygodne. DOS juz nam nie ustawi DS=ES=CS.
                                ; A nasze dane sa w
                                ; segmencie kodu, stad ustawiamy DS=CS.
                                ; W programie typu .com to na pewno nie
                                ; zaszkodzi.

        finit                   ; zawsze o tym pamietaj !

        fild    dword [dzielna] ; ladujemy dzielna. st(0) = 1234DD
        fild    dword [dzielnik]; ladujemy dzielnik. st(0) = 10000h,
                                ; st(1) = 1234DD
        fdivp                   ; dzielimy. st(1) := st(1)/st(0) i
                                ; zdejmij. st(0) ~= 18.2

        fstp    tword [iloraz]  ; zapisujemy st(0) do pamieci i zdejmujemy
                                ; ze stosu

; jesli nie chcesz wyswietlania, usun te 3 linijki nizej:
        mov     edi, iloraz
        piszd80                 ; wyswietl wynik
        nwln                    ; przejdz do nowego wiersza

        mov     ax, 4c00h
        int     21h

dzielna         dd 1234ddh      ; 4013 91a6 e800 0000 0000
dzielnik        dd 10000h

iloraz          dt 0.0

   Ten przyklad do zmiennej "iloraz" wstawia czestotliwosc zegara
   komputerowego (ok. 18,2 Hz). Nalezy zwrocic uwage na zaznaczanie
   rozmiarow zmiennych (dword/qword/tbyte ptr).

   Dyrektywa ALIGN ustawia kolejna zmienna/etykiete tak, ze jej adres
   dzieli sie przez 8 (qword = 8 bajtow). Dzieki temu, operacje na
   pamieci sa szybsze (na przyklad dla zmiennej 8-bajtowej zamiast 3 razy
   pobierac po 4 bajty, bo akurat tak sie zdarzylo, ze miala jakis
   nieparzysty adres, pobiera sie 2x4 bajty). Rzecz jasna, skoro zmienna
   "dzielna" (i "dzielnik") ma 4 bajty, to adresy zmiennych "dzielnik" i
   "iloraz" tez beda podzielne przez 4.
   Ciag cyfr po sredniku to ulamkowa reprezentacja dzielnej.
   Skomplikowane, prawda? Dlatego nie chcialem tego omawiac.
     _________________________________________________________________

   Przyklad 2: czy sinus liczby pi rzeczywiscie jest rowny 0 (w
   komputerze)?
   (przeskocz program z sinusem)
.model tiny
.code
;.386                           ; odkomentowac, jezeli .387 sprawia problemy
.387

org 100h
start:
        finit                   ; zawsze o tym pamietaj !

        fldpi                   ; wczytujemy PI
        fsin                    ; obliczamy sin(PI)
        ftst                    ; porownujemy st(0) z zerem.
        fstsw ax                ; zapisujemy rejestr stanu bezposrednio w AX.
                                ; Dlatego bylo .387

        sahf                    ; AH idzie do flag
        mov ah,9                ; AH=9, flagi niezmienione
        je jest_zero            ; st(0) = 0? Jesli tak, to wypisz, ze jest
        mov dx,offset nie_zero  ; zmienic DX na EDX, jezeli sprawia problemy
        jmp short pisz
jest_zero:
        mov dx,offset zero      ; DX/EDX jak wyzej
pisz:
        int 21h                 ; wypisz jedna z wiadomosci.

        mov ax,4c00h
        int 21h

nie_zero        db      "Sin(PI) != 0.$"
zero            db      "Sin(PI) = 0$"

end start
     _________________________________________________________________

   Wersja dla NASMa i FASMa:
   (przeskocz wersje NASM/FASM programu z sinusem)
org 100h

start:
        finit                   ; zawsze o tym pamietaj !

        fldpi                   ; wczytujemy PI
        fsin                    ; obliczamy sin(PI)
        ftst                    ; porownujemy st(0) z zerem.
        fstsw ax                ; zapisujemy rejestr stanu bezposrednio w AX.
                                ; Dlatego bylo .387

        sahf                    ; AH idzie do flag
        mov ah,9                ; AH=9, flagi niezmienione
        je jest_zero            ; st(0) = 0? Jesli tak, to wypisz, ze jest
        mov dx,nie_zero
        jmp short pisz
jest_zero:
        mov dx,zero
pisz:
        int 21h                 ; wypisz jedna z wiadomosci.

        mov ax,4c00h
        int 21h

nie_zero        db      "Sin(PI) != 0$"
zero            db      "Sin(PI) = 0$"
     _________________________________________________________________

   Przyklad 3: czy pierwiastek z 256 rzeczywiscie jest rowny 16, czy 200
   jest kwadratem liczby calkowitej (komentarze do .386/.387 jak wyzej)?
   (przeskocz ten przyklad)
.model tiny
.code
.386
.387

org 100h                        ; program typu .com

start:
        finit                   ; zawsze o tym pamietaj !

        mov ax,cs
        mov ds,ax               ; konieczne w programie typu .exe !
                                ; Zycie przestaje byc
                                ; wygodne. DOS juz nam nie ustawi DS=ES=CS.
                                ; A nasze dane sa w
                                ; segmencie kodu, stad ustawiamy DS=CS.

        fild word ptr [dwa_pie_sze]     ; st(0) = 256
        fsqrt                           ; st(0) = sqrt(256)
        fild word ptr [szesnascie]      ; st(0) = 16, st(1) = sqrt(256)
        fcompp                          ; porownaj st(0) i st(1), zdejmij oba
                                        ; st: [pusty]
        fstsw ax
        sahf

        mov ah,9
        je tak256
        mov dx,offset nie_256
        jmp short pisz_256
tak256:
        mov dx,offset tak_256
pisz_256:
        int 21h                         ; wypisz stosowna wiadomosc

                        ; do zapisu stanu stosu, czyli rejestrow danych FPU
                        ; mozna uzywac takiego schematu zapisu,
                        ; ktory jest krotszy:
                        ; st:  (0),  (1),  (2),  ... , (7)

        fild word ptr [dwiescie]        ; st: 200
        fsqrt                           ; st: sqrt(200)
        fld st(0)                       ; do st(0) wczytaj st(0).
                                        ; st: sqrt(200), sqrt(200)
        frndint                         ; zaokraglij do liczby calkowitej.
                                        ; st:  (int)sqrt(200), sqrt(200)
        fcompp                          ; porownaj i zdejmij oba.
        fstsw ax
        sahf

        mov ah,9
        je tak200
        mov dx,offset nie_200
        jmp short pisz_200
tak200:
        mov dx,offset tak_200
pisz_200:
        int 21h                         ; wypisz stosowna wiadomosc


        mov ax,4c00h
        int 21h

dwa_pie_sze     dw      256
dwiescie        dw      200
szesnascie      dw      16

nie_256 db      "SQRT(256) != 16$"
tak_256 db      "SQRT(256) = 16$"
nie_200 db      "Liczba 200 nie jest kwadratem liczby calkowitej$"
tak_200 db      "Liczba 200 jest kwadratem liczby calkowitej$"
end start
     _________________________________________________________________

   Teraz dla NASMa i FASMa:
   (przeskocz ten sam przyklad w wersji NASM/FASM)
org 100h                        ; program typu .com

start:
        finit                   ; zawsze o tym pamietaj !

        mov ax,cs
        mov ds,ax               ; konieczne w programie typu .exe !
                                ; Zycie przestaje byc
                                ; wygodne. DOS juz nam nie ustawi DS=ES=CS.
                                ; A nasze dane sa w
                                ; segmencie kodu, stad ustawiamy DS=CS.
                                ; W programie typu .com to na pewno nie
                                ; zaszkodzi.

        fild word  [dwa_pie_sze]        ; st(0) = 256
        fsqrt                           ; st(0) = sqrt(256)
        fild word [szesnascie]  ; st(0) = 16, st(1) = sqrt(256)
        fcompp                  ; porownaj st(0) i st(1) i zdejmij oba
                                        ; st: [pusty]
        fstsw ax
        sahf

        mov ah,9
        je tak256
        mov dx, nie_256
        jmp short pisz_256
tak256:
        mov dx,tak_256
pisz_256:
        int 21h                         ; wypisz stosowna wiadomosc

                        ; do zapisu stanu stosu, czyli rejestrow danych FPU
                        ; mozna uzywac takiego schematu zapisu,
                        ; ktory jest krotszy:
                        ; st:  (0),  (1),  (2),  ... , (7)

        fild word [dwiescie]            ; st: 200
        fsqrt                           ; st: sqrt(200)
        fld st0                         ; do st(0) wczytaj st(0).
                                        ; st: sqrt(200), sqrt(200)
        frndint                         ; zaokraglij do liczby calkowitej.
                                        ; st:  (int)sqrt(200), sqrt(200)
        fcompp                          ; porownaj i zdejmij oba.
        fstsw ax
        sahf

        mov ah,9
        je tak200
        mov dx,nie_200
        jmp short pisz_200
tak200:
        mov dx,tak_200
pisz_200:
        int 21h                         ; wypisz stosowna wiadomosc


        mov ax,4c00h
        int 21h

dwa_pie_sze     dw      256
dwiescie        dw      200
szesnascie      dw      16

nie_256 db      "SQRT(256) != 16$"
tak_256 db      "SQRT(256) = 16$"
nie_200 db      "Liczba 200 nie jest kwadratem liczby calkowitej$"
tak_200 db      "Liczba 200 jest kwadratem liczby calkowitej$"

   Dwa ostatnie programiki zbilem w jeden i przetestowalem. Wyszlo, ze
   sinus PI jest rozny od zera, reszta byla prawidlowa.

   Oczywiscie, w tych przykladach nie uzylem wszystkich instrukcji
   koprocesora (nawet sposrod tych, ktore wymienilem). Mam jednak
   nadzieje, ze te proste programy rozjasnia nieco sposob poslugiwania
   sie koprocesorem.
     _________________________________________________________________

Odwrotna Notacja Polska (Reverse Polish Notation, RPN)

   Ladnie brzmi, prawda? Ale co to takiego?

   Otoz, bardzo dawno temu pewien polski matematyk, Jan Lukasiewicz,
   wymyslil taki sposob zapisywania dzialan, ze nie trzeba w nim uzywac
   nawiasow. Byla to notacja polska. Sposob ten zostal potem dopracowany
   przez Charlesa Hamblina na potrzeby informatyki - w ten sposob
   powstala Odwrotna Notacja Polska.

   W zapisie tym argumenty dzialania zapisuje przed symbolem tego
   dzialania. Dla jasnosci podam teraz kilka przykladow:
   (przeskocz przyklady na ONP)
   Zapis tradycyjny                  ONP
        a+b                     a b +
        a+b+c                   a b + c +  ; ab+ stanowi pierwszy argument
                                           ; drugiego dodawania
        c+b+a                   c b + a +
        (a+b)*c                 a b + c *
        c*(a+b)                 c a b + *
        (a+b)*c+d               a b + c * d +
        (a+b)*c+d*a             a b + c * d a * +
        (a+b)*c+d*(a+c)         a b + c * d a c + * +
        (a+b)*c+(a+c)*d         a b + c * a c + d * +
        (2+5)/7+3/5             2 5 + 7 / 3 5 / +

   Ale po co to komu i dlaczego mowie o tym akurat w tej czesci?
   Powod jest prosty: jak sie dobrze przyjrzec zapisowi dzialania w ONP,
   to mozna zobaczyc, ze mowi on o kolejnosci dzialan, jakie nalezy
   wykonac na koprocesorze. Omowimy to na przykladzie:
   (przeskocz ilustracje relacji miedzy ONP a koprocesorem)
        Zapis tradycyjny (jeden z powyzszych przykladow):
                (a+b)*c+(a+c)*d

        Zapis w ONP:
                a b + c * a c + d * +

        Uproszczony kod programu:

        fld     [a]
        fld     [b]
        faddp                   ; NASM: faddp st1, st0
        fld     [c]
        fmulp                   ; NASM: fmulp st1, st0
        fld     [a]
        fld     [c]
        faddp                   ; NASM: faddp st1, st0
        fld     [d]
        fmulp                   ; NASM: fmulp st1, st0
        faddp                   ; NASM: faddp st1, st0

        Teraz st0 jest rowne wartosci calego wyrazenia.

   Jak widac, ONP znacznie upraszcza przetlumaczenie wyrazenia na kod
   programu. Jednak, kod nie jest optymalny. Mozna byloby na przyklad
   zachowac wartosci zmiennych a i c na stosie i wtedy nie musielibysmy
   ciagle pobierac ich z pamieci. Dlatego w krytycznych sekcjach kodu
   stosowanie zasad ONP nie jest zalecane. Ale w wiekszosci przypadkow
   Odwrotna Notacja Polska sprawuje sie dobrze i uwalnia programistow od
   obowiazku zgadywania kiedy i jakie dzialanie wykonac.

   Pamietajcie tylko, ze stos koprocesora moze pomiescic tylko 8 liczb!

   Nastepnym razem o SIMD.

   Poprzednia czesc kursu (klawisz dostepu 3)
   Kolejna czesc kursu (klawisz dostepu 4)
   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
     _________________________________________________________________

Cwiczenia

    1. Napisz program, ktory sprawdzi (wyswietli stosowna informacje),
       czy liczba PI dzielona przez sama siebie daje dokladne 1.0
    2. Napisz program obliczajacy (nie wyswietlajacy) wartosc 10*PI.
       Potem sprawdz, czy sinus tej liczby jest zerem.
    3. Napisz program mowiacy, ktora z tych liczb jest wieksza: PI czy
       log2(10).
    4. Napisz program sprawdzajacy, czy 10*PI - PI - PI - PI - PI - PI =
       5*PI.
    5. Zamien na ONP:
       a/c/d + b/c/d
       a/(c*d) + b/(c*d)
       (a+b)/c/d
       (a+b)/(c*d)
    6. Zamien z ONP na zapis tradycyjny (daszek ^ oznacza potegowanie):
       ab*cd*e/-
       a5/c7/ed-9/*+
       a3+b/de+6^-
       dc-7b*2^/
