0387 - Programowanie - Rzemiosło Czy Sztuka ?

''Programować rzeczywiście może każdy, ale... Po pierwsze, nie każdy musi - można korzystać z komputerów nie umiejąc programować. Po drugie, pewne nieskomplikowane zadania można wykonać programując byle jak. Jeśli chcemy pisać programy rozwiązujące duże, poważne zadania nie wystarczy po prostu programować. Trzeba programować dobrze.''

Bez wdawania się w zawiłe rozważania, czy jest to sztuka, czy tylko rzemiosło, możemy zauważyć, że dobremu programiście potrzebne są zarówno praktyka jak i pewien zasób wiedzy fachowej. Żeby mieć jedno i drugie potrzebny jest pewien nakład pracy. Tym artykułem rozpoczynamy cykl adresowany przede wszystkim do tych, którzy chcą taki nakład ponieść. Niestety praktykę musicie sobie zapewnić sami - jedyna metoda, którą ja znam, to pisanie i uruchamianie programów. Równocześnie chcę Was przestrzec, że "Bajtek" nie jest i nie może być podręcznikiem programowania. Dlatego nie oczekujcie systematycznego, podzielonego na kolejne lekcje wykładu. Będzie to raczej przegląd podstawowych wiadomości o programowaniu i o metodologii programowania, w miarę możności uzupełniany pożytecznymi ciekawostkami

Nie można jednak zapominać o potrzebach tych, którzy dopiero zaczynają. Dlatego będziemy pisać również o rzeczach zupełnie podstawowych, co z kolei nie jest zapowiedzią kursu BASIC-a. Umiejętność programowania i znajomość konkretnego języka programowania to dwie różne rzeczy. My będziemy się zajmować tą pierwszą - językom "Bajtek" poświęcił już sporo miejsca.

- * - * - * -

Dziś zaczniemy nie od programowania, lecz od uporządkowania wiadomości. które przy programowaniu mogą się przydać. Bardzo często używany w informatyce jest szesnastkowy system zapisu liczb, nazywany również hexadecymalnym (ang. hexadecimal). Jest to system pozycyjny, działający dokładnie tak samo jak systemy: dziesiętny, ósemkowy, czy dwójkowy, opisane w numerze 12/86 "Bajtka". Podstawą systemu jest 16, do zapisu liczb potrzebujemy szesnastu znaczków, czyli cyfr systemu szesnastkowego (analogicznie do dziesięciu znaczków: 0,1,2, ... 9, czyli dziesięciu cyfr systemu dziesiętnego). Poradzimy sobie biorąc cyfry dziesiętne, a jako brakujące sześć wykorzystamy litery A, B, C, D, E, F. Odpowiadają one kolejnym dziesiętnym wartościom: 10, 11, 12, 13, 14, 15. Tam gdzie liczby szesnastkowe mogą się mylić, np. z dziesiętnymi, zaznaczamy je pisząc na końcu literę H, np. 197H.

Kilka przykładów liczb szesnastkowych i ich wartości dziesiętnych:

 1AH = 160 * A+161 * 1 = 1 * 10 + 16 * 1 = 26 
 234H = 160 * 4 + 161 * 3 + 162 * 2 = 1 * 4 + 16 *3 + 256 * 2 = 564 
 FFFH = 160 * F + 161 * F + 162 * F = 1 * 15 + 16 * 15 + 256 * 15 = 4095

Z powyższych przykładów widać od razu jak przeliczyć liczbę szesnastkową na dziesiętną. Jak zrobić to w drugą stronę napiszę za miesiąc, bo może spróbujecie wymyślić to sami.

System szesnastkowy zyskał popularność dzięki ogromnej łatwości przeliczania między nim a systemem binarnym. Dwójkowe reprezentacje wartości cyfr szesnastkowych są następujące: *)

0 = 0000    4 = 0100    8 = 1000    C = 1100
1 = 0001    5 = 0101    9 = 1001    D = 1101
2 = 0010    6 = 0110    A = 1010    E = 1110
3 = 0011    7 = 0111    B = 1011    F = 1111

Widać, że każdej cyfrze odpowiada czterocyfrowa liczba dwójkowa, więc konwersję wykonujemy mechanicznie zastępując kolejne cyfry odpowiednią czwórką bitów, np. E1A9 ma wartość binarną 111000011010. Oczywiście konwersja działa w obie strony. Liczbę dwójkową dzielimy na czwórki bitów, zaczynając od prawej strony, z lewej można dopisać tyle wiodących zer, ile brakuje do pełnej czwórki. Przykład:

1101011101 - (Czyli 001101011101) = 35D

Zauważmy, że zawartość jednego bajtu (8 bitów) można zapisać przy pomocy dwucyfrowej liczby szesnastkowej o wartości od 00 do FF (dziesiętnie jest to od 0 do 255). Ten sposób reprezentacji jest czasami wygodniejszy od dziesiętnego, bo łatwo zobaczyć, które bity mają wartość 0, a które 1. Piszę o tym, gdyż czasami w programach chcemy używać pewnych szczególnych komórek pamięci, których zawartości nie należy traktować jako zwykłych liczb, lecz jako ciągi bitów, z których każdy niesie inną informację. Przykładem może być zawartość zmiennej ST (STATUS), lub rejestrów generatora dźwięku na Commodore C-64. I chociaż najczęściej sztuki z bitami robi się przy programowaniu w assemblerze, to np. na C-64, BASIC i PASCAL pozwalają czytać zawartość różnych rejestrów (instrukcja PEEK) i zmienić ją (POKE). Szczegóły techniczne trzeba znaleźć w odpowiedniej dokumentacji, a autorzy dokumentacji często stosują zamiennie system dziesiętny, dwójkowy i szesnastkowy, więc mimo pozornie teoretycznego wyglądu, nasze rozważania mogą się przydać w praktyce.

Wszystko to piękne, powie ktoś, ale mój BASIC "zna" tylko liczby dziesiętne, jak w takim razie dowiedzieć się czy określony bit ma wartość jeden? Mając do dyspozycji tylko operacje na na liczbach dziesiętnych musimy "oszukać" komputer, czyli potraktować interesujący nas bajt (np. pobrany przy pomocy PEEK) jako liczbę dziesiętną. Jak operując na liczbach dziesiętnych dobrać się do pojedynczych bitów?

Przypomnijmy, że operację logiczne AND i OR, na ogół używane do budowy złożonych warunków logicznych, mogą pracować również jako operacje na liczbach dziesiętnych. Ich działanie jest wtedy następujące: obie liczby zapisane są w postaci dwójkowej, jako ciągi bitów równej długości, bity do operacji pobierane są parami, po jednym z każdego argumentu, wynik dla każdej pary zależy tylko od jej wartości - nie ma związku z resztą argumentu. Dla operacji AND wynik jednej pary jest równy 1 wtedy i tylko wtedy, gdy oba bity są 1 (w pozostałych przypadkach 0). Dla OR wynik jest 0 wtedy i tylko wtedy, gdy oba bity są 0 (w pozostałych przypadkach 1) Znowu przykład:

 AND 100111001 OR 10010001 
     010101111    11000100 
     000101001    11010101

Zauważamy, że jeśli X - dowolna wartość binarna (0 lub 1), to

    AND XXXXXXXX     OR XXXXXXXX 
  maska 00010000  maska 00010000
        000X0000        XXX1XXXX

czyli zastosowanie odpowiedniej maski i operacji AND przenosi do wyniku wartość tylko jednego, wybranego bitu. Pozostałe bity na pewno są równe 0, więc wartość tego wybranego łatwo ustalić, sprawdzając czy cały wynik jest równy 0. Operacja OR pozwala wstawić jedynkę, ustawioną w wybranym bicie maski, do bajtu bez zmiany wartości pozostałych bitów. Oczywiście kilka kolejnych OR i AND pozwoli stworzyć każdą potrzebną kombinację zer i jedynek. Można również używać masek mających kilka jedynek.

Jeszcze tyko musimy zbudować maskę, posługując się liczbami dziesiętnymi. Przypomnijmy sobie system binarny: na kolejnych pozycjach (bitach) stoją kolejne potęgi dwójki, a każda z nich ma znaną wartość dziesiętną. Popatrzmy na bajt:

 Bity:        b7  b6  b5  b4  b3  b2  b1  b0
 Potęgi:      22  22   22  22  22  22  22  22
 Dziesiętne: 128  64  32  16   8   4  2   1

Nie trudno zgadnąć, że maską, która wytnie bit b5 jest po prostu liczba 32, itd. Przykłady:

  • Ustawić b6 na 1 w bajcie o adresie 100 - POKE 100, PEEK(100) OR 64.
  • Sprawdzić, czy bit b3 w zmiennej A% jest równy 0 - IF (A% AND 8)=0 THEN PRINT "b3 = 0"
  • Sprawdzić, czy bajt 5537 ma bity b1 i b5 równe 1 - IF (PEEK(5537) AND 34) = 34 THEN PRINT "b1 = 1 i b5 = 1" (Oczywiście że b5+b1, 34=32+2, maska zawiera dwie jedynki 00100010).

UWAGA: przy wszystkich operacjach bitowych należy posługiwać się liczbami (stałymi i zmiennymi) całkowitymi, czyli typu INTEGER. Zmienne rzeczywiste zapisywane są w maszynie w całkiem inny sposób niż omówiony przez nas zwykły system binarny.

Na zakończenie pytanie: jak sprawdzić, czy wartość zmiennej całkowitej jest parzysta? Jak zrobić to samo nie korzystając z operacji AND? Odpowiedź oczywiście za miesiąc.

*) jeśli interesuje Cię dodawanie w arytmetyce binarnej możesz je prześledzić na podstawie tej tabelki. Kolejna w kolumnie liczba dwójkowa powstaje przez dodanie 1 do poprzedniej.

Andrzej Pilaszek.
Bajtek nr. 3/1987r.