Liczby zmiennoprzecinkowe

 

  w przypadku liczb stałoprzecinkowych kodowanie liczb bardzo małych jest dość kłopotliwe — wymaga na ogół używania znacznej liczby bitów (np. 128) do przedstawienia liczby z wystarczającą dokładnością; analogiczne problemy występują przy kodowaniu liczb bardzo dużych;

 

  liczby zmiennoprzecinkowe, nazywane też zmiennopozycyjnymi, kodowane są w postaci pary liczb określanych jako mantysa i wykładnik;

 


 

  wartość liczby zmiennoprzecinkowej (różnej od zera) określa wyrażenie:


 

        podane wyrażenie w realizacjach komputerowych ma zwykle nieco inną postać;

 

  pole wykładnika można interpretować jako liczbę pozycji, o którą trzeba przesunąć w lewo lub w prawo umowną kropkę rodzielającą część całkowitą i ułamkową mantysy;

 

  zazwyczaj wprowadza się warunek normalizacji mantysy (dla liczb  0)

 

Formaty  liczb  zmiennoprzecinkowych

 

  koprocesor arytmetyczny wykonuje działania na liczbach zmiennoprzecinkowych 80-bitowych w formacie pośrednim (nazywanym też chwilowym, ang. temporary real, extended precision);

 

 

 

 

  liczba 0 kodowana jest jako tzw. wartość wyjątkowa (zob. dalszy opis): pole mantysy i pole wykładnika zawiera same zera;

 

  oprócz formatu 80-bitowego pośredniego koprocesor akceptuje także inne formaty zmiennoprzecinkowe, całkowite i BCD; obliczenia wykonywane są najczęściej na liczbach zmiennoprzecinkowych w formacie 64-bitowym, które określane są jako liczby zmiennoprzecinkowe długie (ang. double precision); stosowany jest także format 32-bitowy — liczby zapisane w tym formacie określane są jako liczby zmiennoprzecinkowe krótkie (ang. single precision);

 

 

  warunek normalizacji mantysy wymaga, by jej wartość (dla liczb  0) zawierała się w przedziale (2, 1> lub <1, 2); oznacza to, że bit cześci całkowitej mantysy (dla liczb  0) będzie zawsze zawierał 1 — zatem można pominąć bit części całkowitej mantysy, zwiększając o 1 liczbę bitów cześci ułamkowej; takie kodowanie stosowane jest w formatach 64- i 32-bitowych — mówimy wówczas, że część całkowita mantysy występuje w postaci niejawnej;

 

  w celu uniknięcia konieczności wprowadzenia znaku wykładnika stosuje się przesunięcie wartości wykładnika o:

 

                16383 = 3FFFH dla formatu 80-bitowego, czyli


 

                1023 = 3FFH dla formatu 64-bitowego, czyli


 

                127 = 7FH dla formatu 32-bitowego, czyli


 

  ponadto koprocesor akceptuje 3 formaty liczb całkowitych (16-bitowy, 32-bitowy i 64-bitowy) oraz 80-bitowy format BCD;

 

  obliczenia wewnątrz koprocesora prowadzone są zawsze w formacie zmiennoprzecinkowym 80-bitowym;

 

  w języku C wartości typu double kodowane są jako liczby zmiennoprzecinkowe 64-bitowe, a wartości float jako liczby zmiennoprzecinkowe 32-bitowe.

 

Przykład kodowania liczby zmiennoprzecinkowej

 

Kodowanie liczby 12.25 w formacie 32-bitowym

 

Liczbę 12.25 przedstawiamy w postaci iloczynu . Wykładnik potęgi k musi być tak dobrany, by spełniony był warunek normalizacji mantysy , czyli


Łatwo zauważyć, że warunek normalizacji jest spełniony, gdy k = 3. Zatem

 

 

Ponieważ część całkowita mantysy nie jest kodowana, więc w polu mantysy zostanie wpisana liczba , zaś w polu wykładnika (po przesunięciu o 127) liczba  . Ostatecznie otrzymamy

 


 

 

Oprogramowanie koprocesora arytmetycznego

 

Specyfika obliczeń za pomocą koprocesora:

  złożone obliczenia numeryczne trwają czasami wiele godzin czy nawet dni;

  wystąpienie nadmiaru lub niedomiaru nie powinno powodować załamania programu (praktyka wskazuje, że w złożonych obliczeniach wyniki pośrednie z nadmiarem czy niedomiarem często mają niewielki wpływ na wynik końcowy).

 

Wartości specjalne:

  spośród dopuszczalnych wartości liczb wyłaczono niektóre i nadano im znaczenie specjalne; wartości specjalne mogą być argumentami obliczeń tak jak zwykłe liczby; jeśli jeden z argumentów jest wartością specjalną, to wynik jest też wartością specjalną – w wielu przypadkach obserwuje się propagację wartości specjalnych.

 

Dokładność obliczeń:

  obliczenia wykonywane na komputerach różnych typów powinny dawać jednakowe rezultaty;

  przyjęto standardowe formaty liczb zmiennoprzecinkowych określone normą IEEE 754;

  podstawowym formatem liczb jest format 64-bitowy; pokazano, że uzyskiwanie dokładnych wyników 64-bitowych wymaga wykonywania niektórych obliczeń na liczbach 80-bitowych.

 

Norma IEEE 754 została opracowana z myślą aby ułatwić przenoszenie programów z jednego procesora do drugiego — określa ona specyficzne metody i procedury służące temu, aby arytmetyka zmiennoprzecinkowa dawała jednolite i przewidywalne wyniki, niezależnie od platformy sprzętowej. Norma ta jest stosowana praktycznie we wszystkich we wszystkich współczesnych procesorach i koprocesorach arytmetycznych.

 

Zakresy  liczb  zmiennoprzecinkowych  (znormalizowanych)

 

format 32-bitowy:

 

najmniejsza ujemna

 

największa ujemna

1

1...10

11...111

 

1

0...01

00...000

-3.371038

 

-1.1710-38

 

 

najmniejsza dodatnia

 

największa dodatnia

0

0...01

00...000

 

0

1...110

11...111

1.1710-38

 

3.371038

 

 

format 64-bitowy:

 

najmniejsza ujemna

 

największa ujemna

1

1...10

11...111

 

1

0...01

00...000

-1.6710308

 

-2.2310-308

 

 

najmniejsza dodatnia

 

największa dodatnia

0

0...01

00...000

 

0

1...110

11...111

2.2310-308

 

1.6710308

 

 

format 80-bitowy:

 

najmniejsza ujemna

 

największa ujemna

1

1...10

11...111

 

1

0...01

10...000

-1.2104932

 

-3.3710-4932

 

 

najmniejsza dodatnia

 

największa dodatnia

0

0...01

10...000

 

0

1...110

11...111

3.3710-4932

 

1.2104932

 

 

liczba 0

 

 

 

 

1

0...0

00...000

 

0

0...0

00...000

0

 

+0

 

 

W obliczeniach występują czasami wyrażenia postaci:  , itp. Zbadajmy, czy przy wartościach x bliskich zera wartość podanych wyrażeń może przekroczyć największą dopuszczalną wartość, co spowoduje powstanie nadmiaru. W tym celu utwórzmy iloczyn najmniejszej i największej liczby dodatniej znormalizowanej dla formatu 32-bitowego:

 


skąd wynika


 

Zatem obliczenia wyrażeń  w formacie 32-bitowym nie doprowadzą do powstania nadmiaru. Łatwo sprawdzić, że własność ta dotyczy także formatu 64-bitowego i 80-bitowego.

 

 

Zasady  wykonywania  obliczeń  przez  koprocesor

 

  liczby, na których wykonywane są obliczenia, składowane są w 8 rejestrach 80-bitowych tworzących stos;

 

  z każdym rejestrem związane jest 2-bitowe pole stanu rejestru (nazywane także polem znaczeń); wszystkie pola stanu tworzą 16-bitowy rejestr zwany rejestrem stanu stosu koprocesora; interpretacja pola stanu jest następująca: 0 – rejestr zawiera liczbę różną od zera, 1 – rejestr zawiera zero, 2 – rejestr zawiera błędny rezultat, 3 – rejestr jest pusty.

 

rejestry stosu

 

 

pola rejestru stanu

 

R0

 

 

 

R1

 

 

 

R2

 

 

 

R3

 

 

 

R4

 

 

 

R5

 

 

 

R6

 

 

 

R7

 

 

(rejestry 80-bitowe)

 

 

pola 2-bitowe

 

  ze stosem rejestrów związany jest 3-bitowy wskaźnik stosu ST (ang. stack top); zawartość wskaźnika stosu wskazuje numer rejestru stanowiącego wierzchołek stosu;

 

  rozkazy koprocesora adresują rejestry stosu nie bezpośrednio, ale względem wierzchołka stosu; w kodzie asemblerowym rejestr znajdujący się na wierzchołku stosu oznaczany jest  ST(0)  lub  ST , a dalsze ST(1), ST(2),..., ST(7); ze względu na specyficzny sposób adresowania koprocesor arytmetyczny zaliczany jest do procesorów o architekturze stosowej;

 

  mechanizmy stosu rejestrów koprocesora są analogiczne do mechanizmów stosu w procesorze (m.in. stos rośnie w dół);

 

  rejestr numeryczny koprocesora jest wyznaczany poprzez sumowanie (modulo 8) numeru podanego w instrukcji i zawartości ST; początkowa zawartość ST wynosi 0;

 

  operacja zapisu na stosie (np. instrukcja FLD) powoduje

                               ST ST 1,

        po czym liczba wpisywana jest do rejestru będącego nowym wierzchołkiem stosu; w szczególności: jeśli ST = 0, to po zapisie ST = 7;

 

  operacja odczytu ze stosu (np. instrukcja FSTP) powoduje odczytanie liczby z rejestru będącego wierzchołkiem stosu, po  czym

                               ST ST + 1

        w szczególności: jeśli  ST = 7, to po odczycie ST = 0;

 

ST(3)

trzeci od wierzchołka

R0

 

 

ST(4)

czwarty od wierzchołka

R1

 

 

ST(5)

itd.

R2

 

 

ST(6)

 

R3

 

 

ST(7)

 

R4

 

ST

ST(0)

wierzchołek stosu

R5

 

5

ST(1)

pierwszy od wierzchołka

R6

 

 

ST(2)

drugi od wierzchołka

R7

 

 

 

 

Przykład: obliczanie wartości wyrażenia

                                               result = (aa bb)  cc

 

dane                 SEGMENT

aa                    dd                    7.0

bb                    dd                    6.0

cc                    dd                    3.0

result    dd                    ?

dane     ENDS

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

                        mov                  ax,SEG dane

                        mov                  ds,ax

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

                        finit

                        fld                    cc

                        fld                    bb

                        fld                    aa

 

; aktualna sytuacja

 

 

; na stosie

aa

ST

; koprocesora

bb

ST(1)

;

cc

ST(2)

 

 

 

 

                               fmul

 

; aktualna sytuacja

 

 

; na stosie

 

 

; koprocesora

aa bb

ST

;

cc

ST(1)

 

 

 

 

                        fxch                  ST(1)

                        fdiv

 

; aktualna sytuacja

 

 

; na stosie

 

 

; koprocesora

 

 

;

 (aa bb) / cc

ST

 

 

 

 

                        fst                    result    ; po tej instrukcji stos nie zmienia się

 

Rejestr  stanu  koprocesora

 

  rejestr stanu koprocesora zawiera pola, które odzwierciedlają sytuację wewnątrz koprocesora:

 

15

14

13   12   11

10

9

8

7

6

5

4

3

2

1

0

B

C3

ST

C2

C1

C0

ES

SF

PE

UE

OE

ZE

DE

IE

 

B                                            zajętość (= 1 wykonuje rozkaz lub sygnalizuje przerwanie, = 0 bezczynny, oczekuje na rozkaz);

C3, . . . , C0       bity rodzaju wyniku,

ST                                          aktualny wierzchołek stosu,

ES                                         niemaskowany wyjątek,

SF                                          błąd stosu – niedozwolona operacja jest spowodowana przez błąd stosu (SF = 1):

                                                               przeładowanie (C1 = 1) lub

                                                               rozładowanie (C1 = 0);

                                               w przypadku, gdy znacznik niedozwolonej operacji jest maskowany i pojawi się błąd stosu (przeładowanie lub rozładowanie), to wskaźnik ST zostaje tak skorygowany jak gdyby nic nie zdarzyło, ale wygeneruje NaN (zob. dalszy opis) jako wynik tej operacji; bit ten nie występuje w 8087 i 287;

 

Znaczniki wyjątków:

PE          – niedokładny wynik,

UE          – niedomiar,

OE         – nadmiar,

ZE          – dzielenie przez zero,

DE          – operand nienormalizowalny,

IE            – niedozwolona operacja.

 

Znaczniki wyjątków są ładowane przez koprocesor i mogą być (łącznie) wyzerowane przez program; po wpisaniu 1 znacznik pozostaje niezmieniony, mimo że kolejne instrukcje nie generują wyjątku (znaczniki są "lepkie").

 

Rejestr  sterujący  koprocesora

 

  rejestr sterujący koprocesora zawiera pola, które modyfikują działanie koprocesora; zawartość tego rejestru zmienia się wyłącznie wskutek jawnego załadowania go przez program.

 

 

15     13

12

11  10

9     8

7     6

5

4

3

2

1

0

 

IC

RC

PC

 

PM

UM

CM

ZM

DM

IM

 

IC            w 387 i nowszych musi być = 1, w 8087 i 287 bit IC określał rodzaj nieskończoności (projekcyjna lub afiniczna);

 

PC          dokładność – pole to umożliwia pewne zmniejszenie dokładności obliczeń poprzez zaokrąglenie wszystkich liczb zanim zostaną one załadowane do rejestrów numerycznych:

                00 – zaokrąglenie do precyzji 80-bitowej,

                10 – zaokrąglenie do precyzji 64-bitowej,

                11 – zaokrąglenie do precyzji 32-bitowej);

                standardowo PC = 00.

 

PM         maska niedokładnego wyniku,

UM         maska niedomiaru,

OM         maska nadmiaru,

ZM          maska dzielenia przez zero,

DM         maska operandu nienormalizowalnego,

IM           maska niedozwolonej operacji.

 

Jeśli bit maskowania zawiera:

0              to wystąpienie zdarzenia spowoduje przerwanie;

1              to zdarzenie jest maskowane, a jako wynik tworzone są wartości wyjątkowe.

 

RC          rodzaj zaokrąglenia:

 

                zaokrąglenie w kierunku liczby najbliższej (RC = 00); jako zaokrąglenie przyjmowana jest wartość reprezentowalna najbliższa nieskończenie dokładnemu wynikowi; jeżeli zaokrąglana liczba jest równoodległa od reprezentowalnych wartości, to zaokrąglenie następuje do liczby parzystej;

 

 

 

                zaokrąglenie w kierunku zera (ang. truncating) (RC = 11); zaokrąglenie polega na obcięciu (zignorowaniu) dodatkowych bitów; 

 

 

        zaokrąglenie w górę (w kierunku ) (RC = 10); ten rodzaj zaokrąglenia oraz zaokrąglenie w dół używane są w metodzie obliczeń jako arytmetyka przedziałów (ang. interval arithmetic); metoda ta polega na dwukrotnym wykonywaniu każdego obliczenia, za jednym razem z zaokrągleniem w górę, za drugim razem w dół — w rezultacie otrzymujemy górną i dolną granicę dokładnej odpowiedzi;

 

 

 

                zaokrąglenie w dół (w kierunku ) (RC = 01)

 

 

 

Formaty  instrukcji  koprocesora

 

  wszystkie kody operacji zaczynają się od 11011, co oznacza, że procesor nie powinien wykonywać instrukcji;

 

  na poziomie asemblera mnemoniki koprocesora zaczynają się od litery F; stosowane są te same tryby adresowania co w procesorze; w polu operandu mogą występować obiekty o długości 32, 64 lub 80 bitów, np.

 

            xvar                  dd                    24.7                 ; liczba 32-bitowa (single)

            yvar                  dq                    24.7                 ; liczba 64-bitowa (double)

            zvar                  dt                     24.7                 ; liczba 80-bitowa (extended)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; dodawanie wartości zmiennych xvar i yvar do liczby

; znajdującej się na wierzchołku stosu

                                   fadd                  xvar                 

                                   fadd                  yvar

 

  jeśli instrukcja odwołuje się do rejestru stosu, to operand ma postać ST(i), co oznacza i-ty rejestr licząc od wierzchołka stosu, np.:

                                   fadd                  ST(0), ST(3)      ; dodawanie zawartości

                                                                                              ; rejestru ST(3) do ST(0)

 

  niektóre instrukcje nie mają jawnego operandu, np. "fabs" zastępuje liczbę na wierzchołku stosu przez jej wartość bezwzględną;

 

Instrukcje przesyłania danych można podzielić na poniższe grupy:

    ładowanie zawartości lokacji pamięci na wierzchołek stosu koprocesora,

    przesyłanie zawartości wierzchołka stosu koprocesora do lokacji pamięci,

    przesyłanie zawartości między wierzchołkiem i innymi elementami stosu;

    ładowanie wartości stałych na wierzchołek stosu.

 

FLD                       ładowanie na wierzchołek stosu koprocesora liczby zmiennoprzecinkowej pobranej z lokacji pamięci lub ze stosu koprocesora, np.:

                                                                       FLD                  z         

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ST

z

ST(0)

ST

x

ST(0)

 

 

x

ST(1)

 

y

ST(1)

 

 

y

ST(2)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

zawartość stosu koprocesora przed wykonaniem instrukcji

 

zawartość stosu koprocesora po wykonaniu instrukcji

 

                               liczba może być też pobrana ze stosu koprocesora, np.:

                                               FLD                  ST(3)

 

FILD                      ładowanie na wierzchołek stosu liczby całkowitej z lokacji pamięci; przed załadowaniem na stos format jest zmieniany na 80-bitowy;

 

FBLD     jw.: ładowanie liczby w kodzie BCD.

 

FST                       przesłanie zawartości wierzchołka stosu do lokacji pamięci lub do innego rejestru stosu koprocesora, np.:

                                   FST                  ST(7)    ; przesyłanie ST(7) ST(0)

 

FSTP                    przesłanie zawartości wierzchołka stosu do lokacji pamięci lub do innego rejestru koprocesora, a następnie usunięcie liczby (P POP) z wierzchołka stosu;

 

FIST                       przesłanie zawartości wierzchołka stosu do lokacji pamięci połączone z przekształceniem  na format całkowity; zaokrąglenie następuje wg zawartości pola RC;

 

FISTP                   jw., ale po przesłaniu następuje usunięcie liczby z wierzchołka stosu;

 

FBST                    przesłanie zawartości wierzchołka stosu do lokacji pamięci połączone z przekształceniem  na format BCD;

 

FBSTP                   jw., ale po przesłaniu następuje usunięcie liczby z wierzchołka stosu;

 

Uwaga 1: załadowanie wartości na wierzchołek stosu powoduje, że wartości wcześniej zapisane dostępne są poprzez indeksy większe o 1, np. wartość ST(3) będzie dostępna jako ST(4); z tych powodów poniższa sekwencja instrukcji jest błędna:

                                   FST                  ST(7)

                                   FLD                  xvar                  ; błąd! — ST(7) staje się ST(8),

                                                                                  ; a takiego rejestru nie ma

 

Uwaga 2: w praktyce programowania instrukcje FST, FIST, FBST używane są znacznie rzadziej niż odpowiadające im instrukcje FSTPFISTP, FBSTP, dlatego też instrukcje FST, FIST, FBST nie są zdefiniowane dla wszystkich typów operandów, np. FST nie ładuje liczby do pamięci w formacie 80-bitowym (ale może to zrobić FSTP).

 

Instrukcja

                                   FSTP               ST(0)

powoduje usunięcie liczby znajdującej się na wierzchołku stosu.

 

FXCH                    zamienia wskazany rejestr koprocesora z wierzchołkiem stosu.

 

 

Ładowanie liczb na wierzchołek stosu:

FLDZ                     – ładowanie 0,

FLD1                     – ładowanie 1,

FLDPI                   – ładowanie ,

FLDL2E                               – ładowanie log 2 e

FLDL2T                – ładowanie log 2 10,

FLDLG2                               – ładowanie log 10 2,

FLDLN2                               – ładowanie log 2,

 

Instrukcje  arytmetyczne

 

  obejmują 4 działania arytmetyczne wykonywane na operandach oznaczonych jako <source>, <destination>; rezultat wpisywany jest do <destination>; podstawowy format jest następujący:

 

                <destination  <destination>   +, , , /    <source>

                                                                              pierwszy operand                                               drugi operand

 

  dla odejmowania i dzielenia stosowany jest także format "przestawiony":

 

                <destination  <source, /    <destination>

 

 

  we wszystkich przypadkach jednym z operandów musi być wierzchołek stosu;

 

  przykładowo instrukcja

                                   FDIV     ST(5), ST

      wykonuje obliczenie


 

W dalszym ciągu rozpatrzymy różne formaty operandów instrukcji arytmetycznych na przykładzie instrukcji odejmowania FSUB; podane dalej przykłady odnoszą się także do instrukcji: FADD, FMUL, FDIV, FSUBR, FDIVR.

 

 

1.                            FSUB

                               instrukcja FSUB (bez operandów) zdejmuje oba operandy ze stosu i wynik wpisuje na wierzchołek; taka instrukcja stanowi skrócony zapis instrukcji

                                               FSUBP ST(1), ST(0)

                        czyli oblicza


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ST

x

 

 

 

 

y

 

ST

y x

 

z

 

 

z

 

 

 

 

 

przed  FSUB

 

po  FSUB

 

 

 

2.                            FSUB   mem

3.                            FISUB  mem

                               operand mem wskazuje lokację pamięci; instrukcja (2) odejmuje od zawartości wierzchołka stosu liczbę zmiennoprzecinkową znajdującą się w lokacji pamięci mem, czyli instrukcja jest interpretowana jako

                                               FSUB   ST(0), mem

                        instrukcja (3) działa podobnie z tą różnicą, że odejmuje liczbę całkowitą; po wykonaniu ww. instrukcji wskaźnik stosu koprocesora pozostaje niezmieniony; operand mem nie może wskazywać lokacji 80-bitowej.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ST

x

 

ST

x mem

 

y

 

 

y

 

 

 

 

 

 

 

 

 

 

przed  FSUB mem

 

po  FSUB mem

 

 

 

4.                            FSUB   ST, ST(i)

                               w tym przypadku wskaźnik stosu koprocesora pozostaje niezmieniony;

 

 

 

 

 

 

 

 

 

 

 

ST

x

 

ST

x y

 

 

 

 

 

 

 

 

 

 

ST(i)

y

 

ST(i)

y

 

 

 

 

 

przed

 FSUB   ST, ST(ia)

 

po

FSUB   ST, ST(i)

 

 

 

 

5.                            FSUB   ST(i), ST

                               w tym przypadku wskaźnik stosu koprocesora pozostaje niezmieniony;

 

 

 

 

 

 

 

 

 

 

 

ST

x

 

ST

x

 

 

 

 

 

 

 

 

 

 

ST(i)

y

 

ST(i)

y x

 

 

 

 

 

przed

 FSUB   ST(i), ST

 

po

 FSUB   ST(i), ST

 

 

6.                            FSUBP  ST(i), ST

 

 

 

 

 

 

ST

x

 

 

 

 

y

 

ST

y

 

 

 

 

 

 

 

 

 

 

ST(i)

z

 

ST(i1)

z x

 

 

 

 

 

przed

 FSUBP   ST(i), ST

 

po

 FSUBP   ST(i), ST

 

 

 

 

Inne instrukcje  koprocesora

 

  pierwiastkowanie FSQRT

  porównywanie liczb FCOM (zob. dalszy opis)

  obliczanie wartości funkcji przestępnych:

    trygonometrycznych

tangens FPTAN

arcus tangens FPATAN

sinus FSIN (FSINCOS)

cosinus FCOS

    wykładniczych

2x1   F2XM1

    logarytmicznych

y log2 x   FYL2X

y log2 (x+1)   FYL2XP1

 

  oprócz wymienionych, lista instrukcji koprocesora zawiera kilkanaście innych instrukcji, które są rzadziej używane.

 

 

Przykłady

 

 

 

FSINCOS

 

 

ST(0)

0

 

cos        1

ST(0)

ST(1)

x

 

sin         0

ST(1)

 

 

 

x

ST(2)

 

 

 

 

 

 

 

 

 

FPTAN

tg = p/q

 

 

ST(0)

/31.047

 

q              1

ST(0)

ST(1)

x

 

p   1.73

ST(1)

 

 

 

x

ST(2)

 

 

 

 

 

W celu obliczenia tg bezpośrednio po instrukcji FPTAN  należy wykonać instrukcję FDIV (bez operandów).

 

 

 

 

FPATAN

arctg  q/p

 

 

ST(0)

p              3

 

0.5235/6

ST(0)

ST(1)

q   1.73

 

x

ST(1)

ST(2)

x

 

 

 

 

 

 

 

 

 

 

 

 

FYL2X

q log2 p

 

 

ST(0)

p           8

 

51

ST(0)

ST(1)

q         17

 

x

ST(1)

ST(2)

x

 

 

 

 

 

 

 

 

 

 

 

 

FYL2XP1

q log2 (p+1)

 

 

ST(0)

p         63

 

102

ST(0)

ST(1)

q         17

 

x

ST(1)

ST(2)

x

 

 

 

 

 

 

 

 

 

 

 

 

F2XM1

2p 1      (1 p 1)

 

 

ST(0)

p       0.5

 

0.4142

ST(0)

ST(1)

x

 

x

ST(1)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

FSCALE

q 2p       

 

 

ST(0)

q       0.31

 

2.48

ST(0)

ST(1)

p            3

 

3

ST(1)

ST(2)

x

 

x

ST(2)

 

 

 

 

 

 

 

 

 

Obsługa błędów w trakcie obliczeń

 

Metody obsługi błędów:

1.     przerwanie wykonywania programu i przekazanie sterowania do programu obsługi błędów;

2.     utworzenie (zamiast wyniku) wartości specjalnej (ang. error value) i kontynuowanie obliczeń, przy czym wartość specjalna jest argumentem w dalszych obliczeniach.

 

Metoda (2) jest rzadko używana w programach nienumerycznych ze względu na konieczność kodowania wartości specjalnych, co ogranicza standardowy zakres liczb (szczególnie dotyczy to liczb 8- i 16-bitowych). Występują też trudności w operacjach porównania, w których jednym z argumentów jest wartość specjalna; kłopotliwa jest też diagnostyka takich programów.

 

W typowych obliczeniach numerycznych sytuacja jest inna:

  liczby zmiennoprzecinkowe zajmują 32, 64 lub 80 bitów i wydzielenie wartości specjalnych w niewielkim stopniu ogranicza zakres liczb;

  wartości specjalne można traktować nie jako wartości związane z błędami, ale jako liczby, które nie mogą być przedstawione w zwykły sposób; dlatego zamiast terminu "wartości specjalne" używa się też "wartości wyjątkowe" (ang. exception value);

  porównywanie liczb zmiennoprzecinkowych występuje znacznie rzadziej niż porównywanie wartości nienumerycznych;

  praktyka obliczeń numerycznych wskazuje, że może być celowe, mimo błędu, dalsze kontynuowanie obliczeń — w niektórych przypadkach można nawet uzyskać poprawny wynik.

 

W koprocesorze arytmetycznym przyjęto, że wszystkie liczby, których pole wykładnika zawiera same zera lub same jedynki traktowane są jako wartości wyjątkowe (wartości specjalne).

 

Błędy obliczeń wykrywane przez koprocesor podzielono na 6 klas: pierwsze 5 klas zawiera ściśle określone, "łagodne" błędy; ostatnia klasa zawiera wszystkie pozostałe.  Wystąpienie błędu należącego do określonej klasy sygnalizowane jest poprzez ustawienie bitu w rejestrze stanu koprocesora; w ślad za tym generowane jest przerwanie.

 

Rejestr stanu koprocesora

15

5

4

3

2

1

0

 

PE

UE

OE

ZE

DE

IE

 

Przerwania spowodowane błędami mogą selektywnie maskowane poprzez ustawienie odpowiednich bitów w rejestrze sterującym koprocesora:

 

Rejestr sterujący koprocesora

15

5

4

3

2

1

0

 

PM

UM

OM

ZM

DM

IM

 

Jeśli określona klasa błędów jest zamaskowana, to wynikiem błędnej operacji jest wartość wyjątkowa i obliczenia są kontynuowane.

 

PE                         – PM      – niedokładny wynik

UE                         – UM      – niedomiar

OE                         – OM     – nadmiar

ZE                          – ZM      – dzielenie przez zero

DE                         – DM      – operand nienormalizowalny

IE                           – IM                      – niedozwolona operacja

 

Znaczniki wyjątków są ładowane przez koprocesor i mogą być (łącznie) wyzerowane przez program; po wpisaniu 1 znacznik pozostaje niezmieniony, mimo że kolejne instrukcje nie generują wyjątku (znaczniki są "lepkie").

 

Niedokładny wynik powstaje gdy wynik operacji nie może być dokładnie przedstawiony w żądanym formacie, np. liczba

                               1/3 = (binarnie) 0.010101...

 

  zazwyczaj użytkownicy koprocesora traktują taką niedokładność jako naturalną i oczekują automatycznego zaokrąglenia; jeśli bit PE jest zamaskowany (tj. PM = 1), to przerwanie nie wystąpi a wynik zostanie zaokrąglony w sposób określony przez zawartość pola RC w rejestrze sterującym.

 

Niedomiar (ang. numeric underflow) występuje w sytuacji, gdy wynik jest różny od zera, ale jego wartość jest bezwzględna jest mniejsza od najmniejszej liczby dodatniej, którą da się przedstawić w danym formacie, np.:

    1.0 / (największa liczba zmiennoprzecinkowa 80-bitowa),

    konwersja najmniejszej liczby 80-bitowej do liczby 64-bitowej;

 

  wskutek przyjęcia formatów, w których część całkowita mantysy występuje w postaci ukrytej, nie jest możliwe kodowanie bardzo małych liczb poprzez rezygnację z warunku normalizacji.

 

  w celu rozszerzenia zakresu reprezentacji liczb bliskich zera ustalono, że takie liczby powinny być traktowane jako wartości wyjątkowe — wartości te kodowane są poprzez liczby, w których pole wykładnika zawiera same zera; w odniesieniu do nich przyjęto następujące założenia:

    wykładnik zawierający same zera będzie traktowany tak jak gdyby zawierał liczbę 00...001;

    w rozpatrywanym przypadku najbardziej znaczący (niejawny) bit mantysy zawiera 0.

 

  przykładowe wartości dla formatu 32-bitowego:

.

zn

wykładnik

mantysa

 

 

8 bitów

23 bity

 

 

 

 

 

0

0000 0001

100....00000

(1.1)2 2-126

0

0000 0001

000....00000

(1.0)2 2-126

0

0000 0000

100....00000

(0.1)2 2-126

0

0000 0000

010....00000

(0.1)2 2-127

 

  ze względu na przesunięcie wykładnika (o 127 dla formatu 32-bitowego) wykładnikowi 0000 0001 odpowiada czynnik 2-126 ; także wykładnikowi 0000 0000 odpowiada czynnik 2-126 (w tym przypadku najstarszy bit mantysy o wadze całkowitej jest z założenia równy 0);

 

      tak więc poprzez zmniejszenie dokładności mantysy można rozszerzyć wykładnik w kierunku bardziej ujemnych wartości (np. do 149 dla formatu 32-bitowego); oznacza to jednak rezygnację z warunku normalizacji;

 

      liczby zmiennoprzecinkowe z wykładnikiem 00...000 i mantysą 0 nazywane są liczbami nienormalizowalnymi (ang. denormals); używane jest także okreslenie "liczby zdenormalizowane"; omawiana koncepcja reprezentacji b. małych liczb nosi nazwę niedomiaru stopniowanego;

 

  podane tu założenia dotyczące wykładnika 00...000 dotyczą formatu 32-, 64- i 80-bitowego (mimo, że w formacie 80-bitowym najbardziej znaczący bit mantysy występuje w postaci jawnej).

 

 

Wyjątek operand nienormalizowalny powstaje gdy jeden z operandów wykonywanej instrukcji jest nienormalizowalny. Analiza tego wyjątku wymaga rozpatrzenia 4 przypadków:

1.     generacja wyniku 0 jeśli rezultat jest zbyt mały aby go przedstawić w postaci liczby nienormalizowalnej (np. mnożenie dwóch liczb nienormalizowalnych);

2.     generacja wyniku nienormalizowalnego (np. dodawanie dwóch liczb nienormalizowalnych);

3.     generacja normalnego wyniku jeśli brak precyzji operandu nienormalizowalnego wpływa na wynik mniej niż zaokrąglenie (np. dodawanie liczby nienormalizowalnej do dużej zwykłej liczby);

4.     generacja normalnego wyniku, jeśli wynik jest zbyt duży aby dał się przedstawić jako liczba nienormalizowalna, nawet jeśli rezultat cechuje mała dokładność (np. mnożenie liczby nienormalizowalnej przez dużą liczbę znormalizowaną); w tym przypadku koprocesory 8087 i 80287 generują wartość wyjątkową określaną jako liczba nieznormalizowana (ang. unnormal), natomiast w 387 i w nowszych generowany jest normalny wynik (dawniej uważano takie wyniki za niepewne).

 

Liczba nieznormalizowana występuje tylko w formacie 80-bitowym, ma zwykły wykładnik, ale najbardziej znaczący bit mantysy zawiera 0; liczby tego typu nie są generowane przez procesor 387 (ewentualne wystąpienie takiej liczby jako operandu powoduje wygenerowanie wyjątku "niedozwolona operacja").

 

Wyjątek "dzielenie przez zero" powstaje gdy wystepuje dzielenie liczby różnej od zera przez zero. W przypadku zamaskowania tego wyjątku (ZM = 1) wynikiem operacji jest wartość wyjątkowa "nieskończoność": pole wykładnika zawiera same jedynki, pole mantysy zawiera same zera. W szczególności dla pewnej liczby   zachodzi (  nie jest nieskończonością)

 

                                                  

 

 

                                                  

 

 

W przypadku gdy jednym z operandów jest "nieskończoność" obowiązują poniższe reguły

 

                                                                     

 

 

                                    

 

 

                                                                  

 

 

                                        

 

  wszystkie mnożenia i dzielenia zawierające jako operandy nieskończoność i zero oraz / prowadzą do wyjątku "niedozwolona operacja;

 

  dodawanie i odejmowanie liczby skończonej do nieskończoności daje wynik nieskończoność; ponadto

 

                                     

 

 

 

Wyjątek "nadmiar" powstaje, gdy wartość bezwględna wyniku jest zbyt duża, np. w wyniku dodawania dwóch największych liczb 80-bitowych. Jeśli bit OE jest zamaskowany (tj. OM = 1), to rezultat ma postać "nieskończoność" z takim samym znakiem jak nadmiarowy "prawdziwy" rezultat.

 

Przypadek szczególny: jeśli ustawiony jest tryb zaokrąglania w kierunku zera, to wynikiem operacji, w której wystąpił nadmiar jest największa liczba dodatnia (zamiast +) albo najmniejsza liczba ujemna (zamiast ).

 

Wyjątek "niedozwolona operacja" powstaje, gdy niemożliwe jest żadne inne działanie, np. próba obliczenia pierwiastka z liczby ujemnej, próba użycia pustego rejestru stosu. Jeśli bit IE jest zamaskowany (tj. IM = 1), to wynikiem operacji jest nieliczba  – NaN (ang. Not a Number); przykładowo, jeśli bit IE jest zamaskowany, to instrukcja fsqrt dla operandu 2 daje wynik:

 


 

  dowolna wartość zmiennoprzecinkowa z polem wykładnika zawierającym same jedynki i mantysą różną od zera jest traktowana jako nieliczba (NaN); zatem istnieje wiele różnych NaN-ów;

 

  w odróżnieniu od innych wyjątków, omawiany wyjątek zazwyczaj nie jest maskowany – dalsze postępowanie zależy wówczas programu obsługi wyjątków;

 

  jeśli jednym z operandów jest NaN, to wynik ma postać  NaN; zatem program jest dalej wykonywany bez tworzenia wyjątków, generując kolejne NaN'y, co powoduje ich epidemiczne rozprzestrzenianie się;

 

  NaN-y mogą być też używane do reprezentowania specjalnych liczb, których arytmetyka definiowana jest przez oprogramowanie; w takim przypadku każde wystąpienie takiej liczby powinno generować wyjątek – program obsługi wyjątków może obliczyć wynik i skierować go z powrotem do programu; ten rodzaj NaN-ów nosi nazwę sygnalizacyjnych lub aktywnych; inne NaN-y określane jako milczące lub pasywne (nie sygnalizują wyjątków);

 

  typ NaN identyfikowany jest przez pierwszy bit mantysy po kropce:

                               = 0          sygnalizacyjny (aktywny),

                               = 1          milczący (pasywny).

 

 

NaN-y jako operandy, gdy bit niedozwolona operacja nie jest zamaskowany:

 

NaN

287

387 i wyższy

pasywna

generuje przerwanie

nie generuje przerw.

aktywna

generuje przerwanie

generuje przerwanie

 

Uwaga:                  NaN aktywny musi mieć co najmniej jedną jedynkę w polu ułamkowym mantysy (w przeciwnym razie traktowany będzie jako +).

 

Porównywanie liczb zmiennoprzecinkowych

 

FCOM                   porównanie

FUCOM                               porównanie "unordered" (387 i wyższy)

FICOM                  porównanie z liczbą całkowitą

FCOMP                               j.w. połączone z POP

FUCOMP                             porównanie "unordered" połączone z POP (387 i wyższy)

FICOMP                              porównanie z liczbą całkowitą połączone z POP

FCOMPP             porównanie z liczbą całkowitą połączone z 2xPOP

FUCOMPP                          porównanie "unordered" (387 i  wyższy) połączone z 2xPOP

FTST                     porównanie z zerem

FXAM                    badanie rodzaju liczby

 

 

  instrukcje FUCOM, FUCOMP, FUCOMPP wprowadzono w 387 w celu obsługi operacji dot. NaN: instrukcje te działają jak zwykłe odpowiedniki, ale nie generują wyjątku "niedozwolona operacja" w sytuacji, gdy jednym z operandów jest pasywny (milczący) NaN.

 

 

Instrukcja

                                   FCOM              x

porównuje ST(0) z operandem x i ustawia bity C3 i C0 w rejestrze stanu koprocesora:

 

C3

C0

warunek

0

0

ST(0) > x

0

1

ST(0) < x

1

0

ST (0) = x

1

1

niezdef.

               

Instrukcja  FTST porównuje ST(0) z zerem i ustawia poniższe bity

 

C3

C0

warunek

0

0

ST(0) > 0

0

1

ST(0) < 0

1

0

ST (0) = 0

1

1

niezdef.

               

 

Instrukcja FXAM jest podobna do FTST, ale dostarcza więcej informacji o liczbie znajdującej się na wierzchołku stosu:

 

C1

 

0

1

liczba dodatnia

liczba ujemna

 

 

C3 C2 C0

 

0    0    0

liczba nieznormalizowana

0    0    1

NaN

0    1    0

zwykła liczba

0    1    1

nieskończoność

1    0    0

zero

1    0    1

wierzchołek stosu pusty

1    1    0

liczba nienormalizowalna

 

 

  bity w rejestrze stanu określające wynik porównania zostały umieszczone na pozycjach odpowiadających znaczników w rejestrze procesora – pozwala to na wykorzystanie zwykłych instrukcji skoków warunkowych (dla liczb bez znaku)

 

SF

ZF

 

AF

 

PF

 

CF

młodsze bity rej. znaczników F

 

 

 

 

 

 

 

 

 

 

B

C3

ST

C2

C1

C0

starsze bity rejestru stanu koproc.

 

Przykład:

 

            FCOM                          ; porównanie ST(0)  i  ST(1)

            FSTSW            AX                    ; zapamiętanie rejestru stanu koproc. w AX

            SAHF                           ; przepisanie AH do rejestru znaczników

            JZ                    ROWNE

            JA                    WIEKSZE

           

               

Kodowanie  operacji  zmiennoprzecinkowych

 

  instrukcje koprocesora kodowane są jako instrukcje ESC:

 

1) gdy instrukcja nie odwołuje się do lokacji pamięci

 

1 1 0 1 1

x1

1 1

x2

r/m

 

np. FADD

 

1 1 0 1 1

1 1 0

1 1

0 0 0

0 0 1

 

 

2) gdy instrukcja odwołuje się do lokacji pamięci

 

1 1 0 1 1     x1

mod   x2   r/m

disp(L)

disp(H)

 

np.  FLD                               var_32bitowa

 

1 1 0 1 1 0 0 1    

0 0 0 0  0 1 1 0

0 0 0 0  1 0 0 0

0 0 0 0  0 0 0 0

 

 

 

  starsze typy translatorów poprzedzają instrukcje koprocesora rozkazem WAIT (nie jest potrzebny dla 287 i nowszych):

 

1 0 0 1  1 0 1 1    

 

  w programach tworzonych przez kompilatory języków wysokiego poziomu kod wynikowy programu zazwyczaj nie zawiera instrukcji koprocesora (bo nie wiadomo czy jest zainstalowany), a operacje zmiennoprzecinkowe kodowane są za pomocą instrukcji INT  34H  INT  3DH (8 instrukcji); instrukcje te, poprzez tablicę wektorów przerwań, prowadzą do pakietu procedur emulujących koprocesor;

  jeśli koprocesor jest zainstalowany, to emulator jest zredukowany do krótkiego programu, który modyfikuje kod instrukcji INT, np.:

 

            INT       34H                  =          34CD

                                                         5C32

                                                           –––––

                                                           D89B             9B (WAIT),        D8 (ESC)

 

        oraz zmniejsza o 2 adres powrotu przechowywany na stosie;

 

  w rezultacie zostaną wykonane instrukcje WAIT i ESC  zapisane w tym samym obszarze pamięci, w której znajdowała instrukcja INT – zatem bieżące i wszystkie następne realizacje tego kodu będą kierowane do koprocesora.

 

 

Obsługa przerwania z koprocesora

 

  w typowych komputerach przerwanie z koprocesora połączone jest z IRQ13, co odpowiada pozycji 75H w tablicy wektorów przerwań; w komputerach XT przerwanie z koprocesora było włączone jako NMI (poz. 2);

 

  zazwyczaj programy korzystające z koprocesora same obsługują przerwania; niektóre programy opracowane przed 1990r. obsługują tylko przerwanie 2 – w takim przypadku przerwanie 75H musi być przejęte przez BIOS i skierowane do programu jako INT 2;

 

  jeśli instrukcja generuje przerwanie, to nie jest wykonywana, a zatem sytuacja na stosie koprocesora nie ulega zmianie;

 

  koprocesor generuje przerwanie dopiero w chwili rozpoczęcia wykonywania kolejnej instrukcji koprocesora;

 

  opis instrukcji, która spowodowała przerwanie jest przechowywany w:

    rejestrze wskaźnika instrukcji (ang. instruction pointer), który zawiera adres (położenie w pamięci) ostatnio wykonywanej instrukcji koprocesora oraz jej 11 bitowy kod (bez pierwszych 5 bitów ESC);

    rejestrze wskaźnika argumentu (ang. data pointer), który zawiera adres argumentu biorącego udział  w wykonywaniu instrukcji;

 

  struktura informacji przechowywanych w w/w rejestrach zależy od trybu pracy procesora – w trybie rzeczywistym struktura jest następująca:

 

bity 15 - 0 położenia instrukcji

 

bity 15 - 0 adresu operandu

b.19 - 16 poł.instr.

 

kod instrukcji (11 bitów)

 

b.19 - 16 adr.oper.

 

 

Technika obsługi przerwania z koprocesora

 

; struktura używana przez instrukcję  fstenv

env87               STRUC

ctrl_reg             dw                    ?

status_reg        dw                    ?

tag_reg             dw                    ?

ins_ptr              dd                    ?

data_ptr                        dd                    ?

env87               ENDS

 

buf                               env87    < >

- - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - -

; odblokowanie przerzutnika pamietającego stan sygnału BUSY

; obecność tego sygnału uniemożliwia wykonywanie dalszych

; instrukcji  koprocesora

            mov                  al,0

            out                   0F0H,al

 

; odblokowanie sterownika przerwań

            mov                  al,20H

            out                   0A0H,al

            out                   20H,al

 

; zapamiętanie stanu koprocesora

            fstenv    buf

 

; bit 7 rejestru sterującego zawiera zawsze 0 (ale nie w 8087)

; bit 7 rejestru stanu zawiera 1, gdy wystąpił wyjątek

            jns                   ....        ; skok do obsługi NMI (np. błąd parzystości)

 

 

 

; identyfikacja przyczyny przerwania

            mov                  bx, OFFSET  buf

            mov                  al, [bx]              ; młodszy bajt rejestru sterującego

            not                   al

            and                  al, [bx]+2          ; młodszy bajt rejestru stanu

; bity 5 - 0 rejestru AL określają przyczynę przerwania

 

            sti

 

; analiza rodzaju błędu i ewentualna próba korekcji

; (w tym odtworzenie stanu koprocesora za pomocą  fldenv)

; jeśli korekcja niemożliwa, to zakończenie wykonywania

; programu (przedtem finit, fldcw)

- - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - -

            iret

 

 

; standardowa obsługa przerwania IRQ15 (poz. 75H)

            push                 ax

            mov                  al,0

            out                   0F0H,al

            mov                  al,20H

            out                   0A0H,al

            out                   20H,al

            pop                  ax

            int                    2

            iret

 

Synchronizacja  procesora  i  koprocesora

 

  w chwili gdy procesor napotka instrukcję koprocesora, to czeka na zakończenie wykonywania poprzedniej instrukcji, zezwala na rozpoczęcie wykonywania nowej i przechodzi do wykonywania kolejnej instrukcji;

 

            FIST     licz_c

            MOV    AX, licz_c         ; błędna wartość w AX  !

 

 

            FIST     licz_c

            FWAIT

            MOV    AX,licz_c          ; wartość w AX poprawna

 

  zamiast FWAIT można zastosować inną instrukcję koprocesora, która rozpocznie się wykonywać po FIST (ale nie dowolną, bo koprocesor może wykonywać instrukcje organizacyjne w trakcie obliczeń).

 

Sterowanie koprocesora na poziomie języka C

 

  w języku C operacje zmiennoprzecinkowe, zgodnie ze standardem IEEE 754, wykonuje się na liczbach typu

                float                                                       32-bitowe

                double                                  64-bitowe

                long  double                        80-bitowe

 

  w środowisku systemów DOS i Windows pracą koprocesora arytmetycznego można sterować za pomocą niżej podanych funkcji:

                _clear87                               odczyt i zerowanie rejestru stanu koprocesora;

                _status87            odczyt rejestru stanu koprocesora;

                _control87           odczyt rejestru sterującego koprocesora,

                                                               poprzedzony ewentualną zmianą zawartości

                                                               niektórych bitów;

                _fpreset                                reinicjalizacja modułu zmiennoprzecinkowego;

 

  prototypy podanych funkcji mają postać (plik nagłówkowy float.h):

unsigned int _clear87(void);

unsigned int _status87(void);

unsigned int _control87 (unsigned int new, unsigned int mask);

void _fpreset(void);

 

  funkcja _control87 (new, mask) pozwala na zmianę zawartości wskazanych bitów rejestru sterującego — zmieniane bity wskazuje argument mask, natomiast nowe wartości bitów określa argument new; wartość zwracana przez tę funkcję reprezentuje "nową" zawartość rejestru; jeśli argument mask zawiera 0, to stan rejestru nie ulega zmianie;

 

  argumentem funkcji _control87 może być też wartość standardowa CW_DEFAULT ( = 1330H, maskowanie niedokładnego wyniku i niedomiaru);

 

  funkcja _fpreset używana jest zazwyczaj w połączeniu z funkcjami exec lub spawn — ewentualne zmiany konfiguracji jednostki zmiennoprzecinkowej nie powinny wpływać na realizację procesu potomnego, podobnie należy także brać pod uwagę możliwość zmiany tej konfiguracji przez proces potomny;

 

  w środowisku systemu Unix (Linux) przyjęto także standard IEEE 754, aczkolwiek występują pewne różnice w konfiguracji systemów; zazwyczaj domyślnie wszystkie wyjątki są maskowane (np. dzielenie 3/0 daje wynik +INF);

 

  w systemie Unix firmy Sun dostępne są m.in. funkcje fpgetmask i fpsetmask (plik nagłówkowy ieeefp.h), które umożliwiają odczytanie aktualnego trybu maskowania i ustawienie innego, np. wywołanie

                        fpsetmask (FP_X_DZ);

      powoduje, że ewentualne dzielenia przez zero spowoduje przerwanie i wysłanie sygnału SIG_FPE;

 

  w innych wersjach systemu dostępna jest m.in. funkcja _ _setfpucw (plik nagłówkowy fpu_control.h), która umożliwia zmianę maskowania;

 

  funkcje zdefiniowane w pliku nagłówkowym math.h w przypadku błędu na ogół wyświetlają odpowiedni komunikat na ekranie, np.  obliczenie

 

                        a = 2.0; b = sqrt(a);

                        printf("\na = %f      b = %f", a, b);

 

        powoduje wyświetlenie na komunikatu na ekranie i kontynuowanie wykonywania programu:

                        sqrt :  DOMAIN error

                        a = 2.000000   b = +NAN

 

  za pomocą funkcji _matherr można przejąć obsługę błędu, np.

 

            int        _matherr (struct exception *a)

            {

                        printf("\nFunkcja: %s", a -> name);

                        a -> retval = a -> arg1;

                        return 1;

            }

 

  wówczas można podjąć odpowiednie działania, a nawet określić wartość zwracaną przez funkcję (pole retval w strukturze exception); jeśli zwraca wartość różną od zera, to oznacza, że w całości przejęła obsługę błędu, w przeciwnym razie dalszą obsługę błędu wykona funkcja standardowa.

 

Przykład: maskowanie bitu 'dzielenie przez 0' w programie w języku C

 

                #include <stdio.h>

            #include <float.h>

 

            unsigned int s;

            double v1,v2,wyn;

 

            s =_control87(4,4);   /* maskowanie bitu 'dzielenie przez 0' */

            v1=1;    v2=0;    wyn = v1 / v2;

            printf("\nWynik  = %f", wyn);

 

Wynik na ekranie ma postać:

            Wynik = +INF

 

w przypadku nie zamaskowania sygnalizowany jest błąd:

            Floating point error: Divide by 0.

            Abnormal program termination

 

Identyfikacja obecności koprocesora

 

1) odczyt opisu wyposażenia komputera (INT  11H)

2) wykonanie poniższej sekwencji rozkazów:

 

            jest87               dw                    00FFH

            - - - - - - - - - - - - - - - - - - - -

            chk87:              fninit

                                               mov                  cx,100H

            ptl:                               loop                  ptl                    ; opóźnienie

                                               fnstsw   jest87   ; zapisanie rejestru stanu

 

  w powyższej sekwencji instrukcje  fninit  fnstsw  nie są poprzedzone instrukcją  wait ; jeśli koprocesor jest zainstalowany, to mniej znaczący bajt w słowie jest87 zostanie wyzerowany;

 

 

3)     w programie w języku C w środowisku Borland dostępna jest zmienna globalna typu int (deklarowana w pliku nagłówkowym dos.h)

                                                                              _8087

Zmienna ta przyjmuje wartości:

0 – gdy koprocesor nie zainstalowany

1 – gdy zidentyfikowano koprocesor 8087

2 – gdy zidentyfikowano koprocesor 287

3 – gdy zidentyfikowano koprocesor 387

 

Polecenia DOSu

                                               SET  87 = NO

                                               SET  87 = YES

wymuszają wartość zmiennej _8087 niezależnie od rzeczywistej sytuacji.

 

 

/* Program do testowania błędu rozkazu fdiv w Pentium

   wg Dr. Dobb's Journal, February 1995, str. 10 */

#include <stdio.h>

#include <math.h>

#define  C          824633702449.0

 

double  test      (double x)

{           /* ta funkcja powinna zawsze zwracac wartość 1 */

            return ((1.0 / x) * x);

}

 

void main  ( )

{

            double  delta = 1e-15;

            volatile  double  in1, in2, in3, out1, out2, out3;

 

            in1 = C 1;       out1 = test (in1);

            in2 = C;                        out2 = test (in2);

            in3 = C + 1;      out3 = test (in3);

            printf("\nProgram sygnalizuje błąd > %e", delta ");

            printf("\npodczas wykonania instrukcji fdiv");

 

            /* Poniższy fragment programu oblicza błędy podczas

            dzielenia — jeśli obliczony błąd jest większy od 10^-15,

            to wyświetlany jest takze tekst ... pentium bug... */

 

            if  (fabs (out1 - 1.0) > delta)

            printf ("You have the pentium bug\n");

            printf("%lf produced an error of %e\n\n",in1, fabs(out1 - 1.0));

            if (fabs (out2 - 1.0) > delta)

            printf ("You have the pentium bug\n");

            printf("%lf produced an error of %e\n\n",in2, fabs(out2 - 1.0));

            if (fabs (out3 - 1.0) > delta)

            printf ("You have the pentium bug\n");

            printf("%lf produced an error of %e\n\n",in3, fabs(out3 - 1.0));

}