Ćwiczenie D4:
P
ODSTAWOWE
OPERACJE
MATEMATYCZNE
NA
LICZBACH
POJEDYNCZEJ
I
WIELOKROTNEJ
PRECYZJI
PRZY
ZASTOSOWANIU
PROCESORÓW
SYGNAŁOWYCH
TMS320
opracował:
mgr inż. Tadeusz Białoń
Cele ćwiczenia:
–
zapoznanie się z kodem uzupełnienia do dwóch
–
zapoznanie się z metodami realizacji operacji dodawania
i mnożenia stałoprzecinkowych liczb wielokrotnej i pojedyn-
czej precyzji
–
zapoznanie się ze skalowaniem typu Q
Wstęp
Kodu uzupełnienia do dwóch
Procesory należące do rodziny C2xxx wykorzystują stałoprzecinkową arytmetykę binarną w kodzie
uzupełnienia do dwóch (ang. two’s complement). Kod ten, wykorzystywany przez większość obecnie
produkowanych mikroprocesorów stałoprzecinkowych, swoją popularność zawdzięcza przede
wszystkim możliwości wyrażania nie tylko liczb dodatnich lecz również liczb ujemnych. Kolejną
bardzo ważną jego zaletą jest możliwość realizacji dodawania i odejmowania za pomocą tego
samego algorytmu, co jest największą przewagą tego kodu nad kodem znak – moduł (sign and
magnitude), wymagającym osobnych algorytmów do przeprowadzenia tych działań.
Aby liczbę zamienić na liczbę do niej przeciwną należy znaleźć jej dopełnienie logiczne a następnie
dodać do niego 1.
Przykład 1:
Należy zamienić liczbę 16-bitową: 0000 0000 0000 0110b, zapisaną w binarnym kodzie
uzupełnienia do dwóch (w kodzie dziesiętnym 6) na liczbę ujemną o tym samym module.
Najpierw należy wykonać operację negacji:
NOT( 0000 0000 0000 0110 b ) = 1111 1111 1111 1001b
następnie dodać 1:
1 <= wiersz przeniesienia
1111 1111 1111 1001b
+ 0000 0000 0000 0001b
-----------------------
1111 1111 1111 1010b
Przy okazji zależy zauważyć ważną cechę kodu uzupełnienia do dwóch. O ile przed pierwszą cyfrą
znaczącą liczby dodatniej można dopisywać zera nie zmieniając jej wartości, np.:
0000 0000 0000 0110b = 110b (6 w kodzie dziesiętnym)
to przed liczbą ujemną można dopisywać jedynki:
1111 1111 1111 1010b = 1010b (-6 w kodzie dziesiętnym)
Zamiana z liczby ujemnej na dodatnią przebiega dokładnie tak samo jak z dodatniej na ujemną.
Jedynym wyjątkiem jest najmniejsza liczba ujemna, czyli w 16-bitowym kodzie uzupełnienia do
dwóch 1000 0000 0000 0000b = -32 768. Liczba ta nie ma liczby do siebie przeciwnej w
kodzie 16-bitowym, gdyż największa liczba dodatnia to 0111 1111 1111 1111b = 32 767.
Przykład 2:
Należy zamienić liczbę 16-bitową 1111 0001 0101 0000b w binarnym kodzie uzupełnienia do
dwóch (w kodzie dziesiętnym -3 760) na liczbę dodatnią o tym samym module.
Najpierw, jak poprzednio, należy wykonać operację negacji:
NOT( 1111 0001 0101 0000b ) = 0000 1110 1010 1111b
następnie dodać 1:
1 111 <= wiersz przeniesienia
0000 1110 1010 1111b
+ 0000 0000 0000 0001b
-----------------------
0000 1110 1011 0000b
Powyższe przykłady ukazują jeszcze jedną, bardzo ważną zaletę kodu uzupełnienia do dwóch: każdy
bit wielobitowej liczby jest traktowany jednakowo podczas operacji arytmetycznych. W przypadku
liczby zapisanej w kodzie znak – moduł, najpierw trzeba od tej liczby oddzielić najbardziej znaczący
(pierwszy z lewej strony) bit a dalsze operacje wykonywać na pozostałych bitach tej liczby,
odpowiednio dobierając algorytm w zależności od tego czy najbardziej znaczący bit był jedynką czy
zerem (czy liczba jest dodatnia czy ujemna).
Powyżej pokazano, że przed liczbą ujemną można w nieskończoność dopisywać jedynki bez zmiany
wartości tej liczby. Właściwość ta bywa w pewnych przypadkach źródłem problemów, na przykład
gdy wynik dodawania dwóch liczb, z których co naj-mniej jedna jest liczbą ujemną, jest liczbą
dodatnią.
Przykład 3:
Należy w 16-bitowym kodzie uzupełnienia do dwóch wykonać działanie 6 + (-5).
Najpierw należy zmienić liczby dziesiętne na odpowiadające im liczby w kodzie uzupełnienia do
dwóch:
6 = 0000 0000 0000 0110b
-5 = 1111 1111 1111 1011b
następnie wykonać dodawanie:
1 1111 1111 1111 11 <= wiersz przeniesienia
0000 0000 0000 0110b
+ 1111 1111 1111 1011b
-----------------------
1 0000 0000 0000 0001b = 65 537
Jak widać, otrzymany wynik jest ewidentnie niepoprawny.
Zauważmy jednak, że zgodnie z zasadą dopisywania jedynek i zer powyższe działanie może wyglądać
również tak (w kodzie 32-bitowym):
1 1111 1111 1111 1111 1111 1111 1111 11 <= wiersz
0000 0000 0000 0000 0000 0000 0000 0110b przeniesienia
+ 1111 1111 1111 1111 1111 1111 1111 1011b
-------------------------------------------
1 0000 0000 0000 0000 0000 0000 0000 0001b => 42 949 67 297
Wynika stąd, że teoretycznie powinno się w takich przypadkach dopisać nieskończenie wiele jedynek
by uzyskać wynik poprawny (zaznaczona na szaro jedynka w ogóle się wtedy nie pojawi). W praktyce
zaznaczona na szaro jedynka zostanie obcięta przez akumulator mikroprocesora, gdyż znajduje się
ona poza zakresem jego pojemności – w przykładzie 3 na miejscu dziewiątym gdy mikroprocesor
operuje w arytmetyce 8-bitowej. W przypadku takim, rdzeń procesora 2xxx nie zgłosi że nastąpiło
przepełnienie akumulatora. Przepełnienie natomiast zostanie zgłoszone gdy do liczby 0111 1111
1111 1111 1111 1111 1111 1111b dodamy 0000 0000 0000 0000 0000 0000
0000 0001b, lub do liczby 1000 0000 0000 0000 0000 0000 0000 0000b dodamy
1111 1111 1111 1111 1111 1111 1111 1111b (należy zauważyć, że akumulator
procesorów serii C24xx i C5x jest 32-bitowy)
Skalowanie typu Q
Wszystkie liczby w poprzednich przykładach były liczbami całkowitymi. Zwykle jednak zachodzi
konieczność wykonywania obliczeń również na ułamkach. Wtedy, aby liczenie na ułamkach było
możliwe, należy zastosować odpowiednie skalowanie. Najczęściej stosowane są skalowania typu Q,
czyli na przykład Q15, Q6, Q10 itd. Występująca w nazwie skalowania liczba określa położenie
przecinka oddzielającego część całkowitą liczby od części ułamkowej.
Wszystkie liczby całkowite występujące w przykładach 1, 2 i 3 są jednocześnie liczbami w skalowaniu
Q0 – przecinek znajduje sie na pozycji zerowej czyli przy pierwszej cyfrze binarnej od prawej.
Przykład 4:
Należy zapisać liczby 13 i -27 w 16-bitowym kodzie uzupełnienia do dwóch i skalowaniu Q9.
Liczba 16-bitowa w skalowaniu Q9 ma postać (gdzie x oznacza 0 lub 1):
xxxx xxx,x xxxx xxxxb
Obie liczby mają część ułamkową równą zeru więc obie liczby po przecinku będą miały tylko bity
nieaktywne czyli zera w przypadku liczby dodatniej i jedynki w przypadku liczby ujemnej (w
przypadku liczby -27 najpierw zakodujemy liczbę przeciwną czyli 27, a potem zmienimy znak liczby
tak samo jak w przykładzie 1). Otrzymujemy więc:
13 = 1101b = 0001 101,0 0000 0000b = 0001 1010 0000 0000b
Q9
27 = 11011b = 0011 011,0 0000 0000b = 0011 0110 0000 0000b
Q9
Na koniec zmieniamy znak liczby 27 na przeciwny:
-27 = 1100 1011 1111 1111b
Q9
Aby zakodować dowolną liczbę dziesiętną należy wymnożyć tą liczbę przez 2 podniesione do potęgi
równej numerowi pozycji przecinka, następnie zaokrąglić wynik pomijając część ułamkową a
otrzymaną w ten sposób liczbę zakodować binarnie w kodzie uzupełnienia do dwóch.
Przykład 5:
Należy zapisać liczby 3,1415, 0,0076 i 1,8463 w 16-bitowym kodzie uzupełnienia do dwóch i
skalowaniu Q14.
Najpierw mnożymy kodowane liczby przez wagę wynikającą z rodzaju skalowania i zaokrąglamy:
3.1415 * 2
14
= 3,1415 * 16384 = 51470,336 = 51570
0.0076 * 2
14
= 0,0076 * 16384 = 124,51840 = 124
1.8463 * 2
14
= 1,8463 * 16384 = 30249,779 = 30250
Na koniec kodujemy otrzymane liczby w 16-bitowym kodzie uzupełnienia do dwóch i dopisujemy
przecinek na pozycji czternastej:
51570 => 0 1100 1001 0111 0010b
Q14
<- kodowanie niemożliwe!
124 => 0000 0000 0111 1100b
Q14
30250 => 0111 0110 0010 1010b
Q14
Okazuje się, że liczby 3,1415 nie da sie zakodować! Liczba dodatnia w kodzie uzupełnienia do dwóch
musi się zaczynać cyfrą 0 a tutaj zero to pojawia się na pozycji siedemnastej, czyli wykracza poza
założony zakres.
Przedział liczb możliwych do zakodowania w kodzie uzupełnienia do dwóch zależy od ilości bitów
liczby oraz od ustalonego położenia przecinka. Dany jest on wzorem:
〈
−2
b
−q−1
; 2
b
−q−1
−2
−q
〉
gdzie: b – liczba bitów liczby stałoprzecinkowej, q – pozycja przecinka.
Przykład 6:
Należy określić przedziały liczb kodowanych za pomocą kodów uzupełnienia do dwóch: 16-bitowego
Q14, 32-bitowego Q31 i 32-bitowego Q24.
Korzystając z powyższego wzoru otrzymujemy:
16bit Q14 => b=16 q=14 => <-2;2-2
-14
>
32bit Q31 => b=32 q=31 => <-1;1-2
-31
>
32bit Q24 => b=32 q=24 => <-2
7
;2
7
-2
-24
>
Aby odczytać wartość dziesiętną liczby binarnej w kodzie uzupełnienia do dwóch i dowolnym
skalowaniu Q należy kolejno: jeżeli jest to liczba ujemna należy zamienić ją na liczbę przeciwną,
następnie zapisać ją w postaci dziesiętnej, po czym podzielić otrzymaną wartość przez dwa
podniesione do potęgi równej pozycji przecinka, na koniec uwzględnić ewentualną zmianę znaku.
Po porównaniu przykładów 5 i 6 staje się jasne dlaczego liczby 3,1415 nie da się zakodować w 16-
bitowym kodzie uzupełnienia do dwóch i skalowaniu Q14.
Przykład 7:
Należy odczytać wartości liczb w 16-bitowym kodzie uzupełnienia do dwóch: 0010 1101 0111
0010b
Q6
, 1111 1100 0100 0110b
Q3
, 1100 0000 0000 0000b
Q15
.
Najpierw należy zamienić znaki liczby drugiej i trzeciej na przeciwne, gdyż są one liczbami ujemnymi
– zaczynają się od jedynki:
- ( 1111 1100 0100 0110b
Q3
) = 0000 0011 1011 1010b
Q3
- ( 1100 0000 0000 0000b
Q15
) = 0100 0000 0000 0000b
Q15
Następnie zapisujemy wszystkie liczby w postaci dziesiętnej i dzielimy przez wagi wynikające ze
sposobu skalowania:
0010 1101 0111 0010b = 11634 => 11634 / 2
6
= 181,78125
0000 0011 1011 1010b = 954 => 954 / 2
3
= 119,95
0100 0000 0000 0000b = 16384 => 16384 / 2
15
= 0,5
Na koniec uwzględniamy wcześniejsze zmiany znaków i otrzymujemy:
0010 1101 0111 0010b
Q6
= 181,78125
1111 1100 0100 0110b
Q3
= -119,95
1100 0000 0000 0000b
Q15
= -0,5
Przebieg ćwiczenia
Ćwiczenie polega na uruchamianiu i testowaniu działania gotowych procedur arytmetycznych
realizujących mnożenie i dodawanie liczb w kodzie uzupełnienia do dwóch, pojedynczej i
dwukrotnej precyzji. Procedury te, napisane w asemblerze, oraz całe środowisko programistyczne
zostaną przygotowane przez prowadzącego ćwiczenie. W trakcie ćwiczenia studenci przy pomocy
tych procedur wykonują przykładowe obliczenia zadane przez prowadzącego ćwiczenie.
Sprawozdanie z ćwiczenia
Sprawozdanie powinno zawierać przykłady obliczeniowe przerobione w trakcie ćwiczenia
opatrzone odpowiednimi komentarzami dotyczącymi sposobu obliczeń oraz działania badanych
procedur arytmetycznych. Przykłady te powinny być opracowane w sposób podobny jak przykłady
zamieszczone w tej instrukcji.