Professional Documents
Culture Documents
Wstęp Do Informatyki Nie Tylko Dla Informatyków - Jerzy Mieścicki
Wstęp Do Informatyki Nie Tylko Dla Informatyków - Jerzy Mieścicki
Wstęp Do Informatyki Nie Tylko Dla Informatyków - Jerzy Mieścicki
Wstęp
do informatyki
nie tylko dla informatyków
Jerzy Mieścicki
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Książka jest przeznaczona dla Czytelników, którzy nie mając specjalnego przygotowania matematyczne-
go i wiadomości wykraczających poza przeciętną szkolną wiedzę, chcą poznać pojęcia i idee stanowiące
fundament współczesnej informatyki.
W początkowych rozdziałach przedstawiono najważniejsze właściwości algorytmów, metody algorytmicz-
ne, różnice między obliczeniami analogowymi i cyfrowymi oraz metody cyfrowego przetwarzania sygna-
łów. W kolejnych rozdziałach Czytelnik jest zaznajamiany z maszyną Turinga, lingwistyką matematyczną
i automatami skończonymi. Autor omawia także budowę i działanie urządzeń cyfrowych, począwszy od
operacji na dwójkowych danych poprzez algebrę Boole’a oraz zasady projektowania cyfrowych podze-
społów, aż do architektury współczesnych komputerów i współdziałania sprzętu z oprogramowaniem.
Książka jest przeznaczona dla amatorów, którzy chcą być świadomymi użytkownikami sprzętu informa-
tycznego, dla studentów informatyki i kierunków pokrewnych, a także dla informatyków chcących lepiej
poznać i zrozumieć korzenie dziedziny, w której się specjalizują.
ISBN 978-83-60233-94-8
© Copyright by Wydawnictwo BTC
Legionowo 2013
Wydanie I
Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli.
Autor oraz wydawnictwo BTC dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie
biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw paten-
towych lub autorskich. Autor oraz wydawnictwo BTC nie ponoszą również żadnej odpowiedzialności za ewentualne szkody
wynikłe z wykorzystania informacji zawartych w książce.
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentów niniejszej publikacji w jakiejkol-
wiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku
filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.
Spis treści
Od autora ....................................................................................................................................................... 7
4 Spis treści
Spis treści 5
6 Spis treści
Od autora
Dziwna jakaś jest ta książka. Gdy się ją pobieżnie kartkuje – rzucają się
w oczy schematy, grafy i symboliczne wyrażenia, które wielu od razu od-
stręczają i – jeśli książka ma być popularnonaukowa – podobno od razu ra-
dykalnie zmniejszają krąg potencjalnych czytelników. Z drugiej strony, gdy
się spojrzy na indeks nazwisk – widzi się (już na samym jego początku)
z takimi praojcami współczesnej informatyki, jak Charles Babbage i George
Boole, sąsiadują Aleksander Wielki, Arystoteles, Napoleon Bonaparte i lord
Byron. A cóż oni z informatyką mają wspólnego? Wypada odpowiedzieć na
takie wątpliwości.
Przede wszystkim: do kogo ta książka jest skierowana? Odpowiedź
brzmi: do ciekawych. Do tych, którzy zadają sobie pytania: Jak to działa?
Dlaczego? Skąd się wziął taki pomysł? Czy nie można by tego zrobić ina-
czej? Jak to można wykorzystać gdzie indziej?
Większości ludzi takie pytania nie dręczą. Jeśli wezmą do ręki książ-
kę z informatyką w tytule, to oczekują od autora, żeby za dużo nie gadał,
tylko konkretnie, w punktach podał, co mają napisać i gdzie nacisnąć, żeby
na ekranie komputera pojawiło się to, co mają ochotę zobaczyć. Ci nie będą
mieli z tej książki większego pożytku. Niech raczej sięgną do innych, właś-
nie tak „konkretnie” pomyślanych podręczników i poradników. Znajdą je
bez trudu. Zapełniają one całe metry półek w każdej księgarni, która ma
dział „informatyka i komputery”.
Książkę kieruję więc do tych, których nie zadowala czysto techniczna
biegłość w posługiwaniu się komputerem, samochodem czy komórkowym
telefonem. Chcą oni zrozumieć, jak to działa, co to znaczy, o co w tym
chodzi… w tym przypadku – w informatyce. Innymi słowy – zachowali w
sobie podstawową ciekawość świata, która jest tak cenna i naturalna u dzie-
ci, a potem u wielu niestety zanika wraz z wiekiem. Warto taką ciekawość
zaspokajać, bo to właśnie spośród tych ciekawych wywodzą się osoby naj-
bardziej twórcze, zdolne do formułowania nowatorskich pomysłów i orygi-
nalnych rozwiązań, które czasami osiągają światowy sukces. Uważam, że
ciekawość i zrozumienie podstaw danej dziedziny wiedzy (a nie tylko opa-
nowanie technicznych sztuczek) są warunkiem kreatywności. Odstępstwa
od tej zasady, jeśli się w ogóle zdarzają – to bardzo rzadko.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
8 Od autora
Od autora 9
Czy ten zamysł się powiódł – ocenią już sami czytelnicy. Kolegów
informatyków (jeśli ta książka wpadnie w ich ręce) proszę natomiast o wy-
rozumiałość i wybaczenie uproszczeń i uników, prowadzących do tego, że
rozumowanie niejednokrotnie zatrzymuje się u progu trudniejszych zagad-
nień.
Jak więc traktować te odstraszające wzory, grafy i schematy w książ-
ce, która ma być z założenia popularnonaukowa? Proponuję, by się nimi nie
przejmować. Jeżeli komuś zabraknie w pewnym momencie woli i koncen-
tracji, by wgłębiać się w szczegóły jakiegoś przykładu czy formalnego wy-
rażenia – niech bez wyrzutów sumienia czyta dalej. Towarzyszący zawsze
przykładowi nieformalny opis też wyjaśni, o co chodziło. Sam tak wielo-
krotnie robiłem i wiem, że bardzo często wkrótce przychodzi zrozumienie,
po co ten przykład był i dlaczego warto zapoznać się z nim dokładniej.
Wtedy zawsze można wrócić i z nową motywacją, wziąwszy kartkę i ołó-
wek, spróbować przebrnąć przez trudny fragment jeszcze raz. To naprawdę
pomaga.
A dlaczego Boole, Babbage, a obok Arystoteles i Napoleon? Dlatego,
że uważam, iż każdemu inteligentowi przydadzą się wiadomości nie tylko
o teoretycznych czy technicznych zagadnieniach danej dziedziny, ale także
o wydarzeniach historycznych i o postaciach, które mniej czy bardziej bez-
pośrednio z tą dziedziną się wiążą. W moim przekonaniu, inteligentem się
jest lub nie jest, niezależnie od tego, czy się pracuje na roli, czy w wyższej
uczelni, czy się ukończyło tylko szkołę podstawową – czy też ma się tytuł
profesora. Dla mnie inteligent – to pewna konstrukcja psychiczna, która łą-
czy w sobie wspomnianą już ciekawość świata z naturalną postawą szacun-
ku nie tylko dla osób i poglądów, ale i dla tradycji.
Informatyka ma również swoją tradycję. Należą do niej wydarzenia
i postaci często bardzo wybitne, lecz prawie zupełnie nieznane, mimo że
wszyscy dziś korzystamy z ich dokonań. Ich losy splatają się z innymi fak-
tami i postaciami, lepiej znanymi szerszej publiczności z polityki, historii
i z innych dziedzin nauki lub techniki. Dlatego w tej książce zamieściłem
nieco wiadomości o wydarzeniach historycznych i o ciekawych, a często
dramatycznych losach osób dla informatyki ważnych, o których powinniśmy
pamiętać i którym należy się nasz szacunek za to, czego dla nas dokonali.
Uważam, że to również jest część podstawowej wiedzy o informatyce.
Na koniec pozostaje miły autorski obowiązek podziękowania oso-
bom, które miały swój udział w powstaniu tej książki.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
10 Od autora
O czym opowiemy
Wróćmy do rysunku 1.1. Naszkicowany na nim „ciąg technologiczny”,
wiodący od pomysłu aż do wykonania programu w komputerze, jest do-
brze znany, zapewne również bardzo wielu czytelnikom niniejszej książki.
Przećwiczono go na całym świecie miliony razy: nie ma dziś praktycznie
komputerowego programu, który by powstał inaczej niż we właśnie opisany
sposób. Mogłoby się więc wydawać, że chcąc rozwiązać pewien obliczenio-
wy problem – wystarczy posuwać się po dobrze znanej, oświetlonej i ozna-
kowanej drodze, prowadzącej bezpiecznie do celu.
Na szczęście tak nie jest. Gdyby tak było – informatyka byłaby zwy-
czajnie nudna, a zawód informatyka byłby jedynie powtarzalnym, niecie-
kawym wyrobnictwem. W rzeczywistości – na tej pozornie prostej drodze
kryją się liczne pułapki i wilcze doły, a wiele jej odnóg prowadzi w kolcza-
ste maliny.
Nie chodzi przy tym o trywialne błędy, które mogą wyniknąć z nie-
dostatecznej znajomości języka programowania, ani o ludzkie pomyłki,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
O czym opowiemy 17
których translator nie potrafi wykryć (choćby np. omyłkowe użycie znaku
„plus” zamiast „minus”). Oczywiście, takie kłopoty też się zdarzają, i to
często. Jednak najbardziej intrygujące problemy zaczynają się już znacznie
wcześniej: na etapie formułowania zadań obliczeniowych i prób konstru-
owania dla nich algorytmów.
Pojawiają się tu pytania w gruncie rzeczy ciekawsze niż odpowiedzi
na nie.
Przede wszystkim musimy wiedzieć, że nie dla każdego zadania, któ-
re sobie wymyślimy, istnieje algorytm jego rozwiązania. Co więcej, o nie-
których zadaniach wiemy na pewno (i udało się to formalnie udowodnić), że
algorytm dla nich nie może istnieć, gdyż prowadziłoby to do jakiejś nieroz-
wiązywalnej sprzeczności. Czy można podać przykład? Oczywiście: nie ist-
nieje na przykład algorytm projektowania dowolnych algorytmów. Inaczej
mówiąc, nie ma przepisu, który wskazałby nam, co – punkt po punkcie
– trzeba zrobić, aby otrzymać algorytm dla właśnie przez nas wymyślonego
dowolnego problemu.
No dobrze, ale w takim razie skąd algorytmy się biorą? To też tak
zwane dobre pytanie. Podobnie jak wiersze, powieści, piosenki czy symfo-
nie – są one rezultatem twórczych, zupełnie niealgorytmicznych procesów,
zachodzących w ludzkim mózgu. Poświęcimy temu nieco uwagi na począt-
ku rozdziału 4.
Wcześniej jednak pokażemy na możliwie prostych przykładach, jakie
właściwości mają znane algorytmy. Rozpoczniemy od zadań, które są „ob-
liczeniowo łatwe” (rozdział 2), ale wkrótce przekonamy się, że są również
„obliczeniowo trudne” (rozdział 3). Dla tych ostatnich algorytmy wprawdzie
istnieją, lecz trudno je zaakceptować, ponieważ nawet dla – wydawałoby się
– prostych przypadków musielibyśmy niekiedy czekać całe tysiące lat, za-
nim zobaczylibyśmy wynik ich działania. Dowiemy się też (i przekonamy
się na przykładach), że przypadłość ta dotyczy niestety wielu problemów
bardzo w praktyce ważnych i powszechnych.
W rozdziale 4 pokażemy kilka typowych sposobów i metod, które
projektanci algorytmów stosują w swojej pracy i za pomocą których usiłują
radzić sobie m.in. również z owymi „trudnymi” problemami. W rozdziale
5 powiemy też o algorytmach probabilistycznych i ewolucyjnych, których
skuteczność i uniwersalność prowokuje do intrygujących refleksji, dosyć od
samej informatyki odległych.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
O czym opowiemy 19
O czym opowiemy 21
2. O algorytmach i złożoności
obliczeniowej na kilku łatwych
przykładach
Ale to nie jest dobry przepis. Po pierwsze, jeśli Jaś wydał nie 5, 2 i 1 zł,
tylko 6, 2 i 3 zł, to tę instrukcję postępowania trzeba by właściwie napisać
od nowa: odwołuje się ona bowiem do trzech konkretnych, zbyt konkretnych
wartości liczb. Warto byłoby ją skonstruować tak, by można było ją zastoso-
wać również wtedy, kiedy wartości zakupów Jasia będą inne.
Po drugie, co by było, gdyby Jaś kupił nie trzy rzeczy, a cztery, pięć
albo pięćdziesiąt? A co, jeśli nie kupił nic zgoła? Dobrze byłoby, gdyby
algorytm radził sobie z dowolną liczbą zakupów.
Wreszcie, czy powinniśmy się ograniczać tylko do małych liczb cał-
kowitych? Przecież nawet mały Jaś wie, że są rzeczy, które kosztują 7,99 zł
albo 0,23 zł. Wkrótce się dowie, że są liczby ujemne, zero i ułamki, i będzie
się uczył dodawać także takie wartości. Może przygotować się na to już
teraz?
Trzeba takie pytania sobie zadać, bo skoro już zabieramy się do for-
mułowania przepisu na rozwiązanie zadania obliczeniowego, to warto się
postarać, aby był on możliwie uniwersalny, to znaczy by pasował do możli-
wie dużej liczby sytuacji.
Zróbmy więc tak, jak postąpiłby informatyk. Najpierw wypiszmy
całą listę wydatków, umieszczając na niej wszystkie liczby, oddzielone na
przykład średnikami. Na końcu postawmy jakiś umówiony znak (na przy-
kład #), który na pewno nie jest liczbą i oznacza właśnie tyle, że to koniec
listy. Niech ta lista nazywa się L. W przypadku zakupów Jasia lista L wy-
glądałaby następująco:
L = 5; 2; 1; #
ale generalnie – może mieć ona dowolną długość. Gdyby Jaś kupił sto ty-
sięcy rzeczy – zawierałaby sto tysięcy liczb i symbol # na końcu. Gdyby
nic nie kupił, lista L byłaby pusta, to znaczy nie zawierałaby żadnej liczby,
a jedynie symbol końca listy.
W ten sposób zestawiliśmy występujące w zadaniu dane w pew-
ną strukturę danych (ang. data structure). W tym przypadku jest to lista
liczb, ale są też inne struktury: tablice, grafy, drzewa... Wybór odpowiedniej
struktury danych ma duże znaczenie dla przebiegu obliczenia.
Dalej wprowadźmy sobie pomocniczą liczbę naturalną i, która bę-
dzie wskazywała numer aktualnie przetwarzanej pozycji z listy. Nazwijmy
ją indeksem. Dla porządku ustalmy też, że wynik będzie się nazywał suma.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Obie te liczby będą się zmieniać w toku rozwiązywania zadania, będą więc
zmiennymi w naszym przepisie. Przygotujmy sobie na kartce miejsce zaty-
tułowane i oraz drugie, zatytułowane suma. Będziemy w nie wpisywali ak-
tualne wartości tych zmiennych. Kiedy wyliczymy nową wartość zmiennej,
wytrzemy gumką (albo przekreślimy) dotychczasową zawartość i wpiszemy
tam nową.
Umówmy się, że L(i) oznacza i-tą pozycję na liście L. W naszym
przykładzie, kiedy i = 1, to pisząc L(i), mamy na myśli pierwszą pozycję
listy, czyli liczbę L(1) = 5. Kiedy i = 2, to pod L(i) rozumiemy drugą pozy-
cję: liczbę L(2) = 2, Podobnie, L(3) = 1, a L(4) = #. Przy tych oznaczeniach
rozwiązanie może mieć postać następującego ciągu instrukcji:
1. W miejscu i napisz 1, w miejscu suma napisz 0;
2. Weź z listy L pozycję L(i);
3. Czy L(i) to jest #? Jeśli tak – przejdź do instrukcji numer 7, jeśli nie – idź
dalej;
4. Wykonaj dodawanie L(i) + suma, wynik zapisz do suma;
5. Do i dodaj 1, wynik zapisz w miejsce dotychczasowego i;
6. Przejdź z powrotem do czynności 2;
7. Napisz liczbę suma jako ostateczny wynik;
8. Już koniec, zatrzymaj się.
Teraz jest znacznie lepiej. Proszę wziąć kartkę papieru, ołówek, gum-
kę i – dla zabawy i nauki – wykonać ten ciąg instrukcji dla trzyelemen-
towej listy zakupów Jasia. Proszę jednocześnie zapisywać numery kolejno
wykonywanych instrukcji: od początku aż do chwili zatrzymania. W tym
konkretnym przypadku, będą to kolejno instrukcje numer 1, 2, 3, 4, 5, 6, 2,
3, 4, 5, 6, 2, 3, 4, 5, 6, 2, 3, 7, 8. Okazuje się, że ciąg ośmiu instrukcji napi-
sanych rozwija się w ciąg dwudziestu instrukcji wykonanych. Rzeczywiście,
instrukcje numer 2, 3, 4, 5, 6 tworzą pętlę, w której krążymy tyle razy, ile
liczb umieściliśmy na liście L. Instrukcja 3 nakazuje wyjście z tej pętli do-
piero po natrafieniu na symbol końca listy.
Można łatwo policzyć, ilu kroków (wykonań instrukcji) wymaga wy-
konanie całego obliczenia, jeśli liczba pozycji na liście wynosi N. Jeżeli
N = 0 (czyli lista jest pusta) – wykonywanych jest tylko pięć instrukcji,
o numerach 1, 2, 3, 7, 8. Jeśli N = 1 (jedna pozycja na liście), to trzeba
obiec pętlę raz, więc wykonają się instrukcje 1, (2, 3, 4, 5, 6), 2, 3, 7, 8.
Numery kolejnych instrukcji tworzących pętlę umieszczono w nawiasach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
na i-tej pozycji listy L znajduje się znacznik końca listy?”. Jeśli tak, to
kończymy działanie, drukując przedtem wynik, jeśli nie – wykonujemy na-
stępny obieg pętli.
Para znaków := oznacza operację podstawienia, na przykład instruk-
cja i := i + 1; każe wstawić do zmiennej i wartość, jaką da w wyniku doda-
wanie i + 1. Warto wspomnieć, że w niektórych językach programowania
dla uproszczenia zapisu w operacji podstawienia dwukropek się opuszcza1.
Sformułowaliśmy w ten sposób algorytm sumowania listy liczb. Jest
to pojęcie dla informatyki zupełnie podstawowe. Do pełniejszego jego rozu-
mienia będziemy się zbliżać w kolejnych rozdziałach tej książki, jednak już
w tej chwili możemy zauważyć trzy własności, które sprawiają, że pewien
przepis postępowania można nazwać algorytmem.
Tak więc po pierwsze algorytm powinien być skonstruowany nie dla
jednego, konkretnego zestawu danych, lecz dla większego ich zbioru. W na-
szym przypadku udało się nam doprowadzić do tego, że algorytm sumuje
dowolnie długą (ale skończoną) listę dowolnych liczb rzeczywistych.
Po drugie, sam algorytm ma postać skończonego ciągu instrukcji,
z których każda musi być zrozumiała i wykonalna dla tego, kto będzie ten
algorytm realizował. Nic po takim przepisie, jeśli ten, kto będzie go używał,
nie potrafi wykonać operacji dodawania.
Muszą być także dobrze określone reguły następowania poszczegól-
nych instrukcji po sobie. Możemy dla uproszczenia zakładać, że jeśli nie
jest powiedziane inaczej – instrukcje są wykonywane w takim porządku,
jak je napisano: kolejno, wierszami, od góry do dołu. Niekiedy jednak trze-
ba odstąpić od tego porządku. W powyższym przykładzie, po instrukcji 1
następuje (właśnie przez domniemanie) instrukcja 2, a po niej 3, ale sama
instrukcja 3 ma bardziej złożony charakter. Jest to instrukcja skoku warun-
kowego: w zależności od wartości pewnego warunku każe ona nam albo
przeskoczyć do instrukcji 7, albo przejść dalej, to znaczy do instrukcji 4.
Z kolei instrukcja 6 oznacza skok bezwarunkowy: wskazuje jawnie jako
swojego następcę instrukcję 2. W sieci działań takie konstrukcje są szcze-
gólnie wyraźnie widoczne.
W grafie G1 W grafie G2
1 a
2 b
3 c
4 d
5 e
6 f
O(N 3), O(N 4) ... itd., ogólnie – o złożoności O(N k), gdzie k jest pewną sta-
łą. Taką złożoność rzędu O(N k) nazywamy wielomianową (ang. polynomial
complexity), ponieważ domniemana zależność czasu wykonania od N ma
istotnie postać wielomianu k-tego stopnia.
Jednak są również algorytmy o złożoności O(k N) – na przykład O(2 N)
– a więc takiej, że N znajduje się w wykładniku potęgi. Wtedy mówimy
o złożoności wykładniczej (ang. exponential complexity). Co więcej, są tak-
że algorytmy, w przypadku których czas wykonania jeszcze szybciej rośnie
wraz z N: o rzędzie złożoności np. , a nawet – aż strach powiedzieć
– o jeszcze większej liczbie pięter potęgowania. Wykładniczy charakter ma
także złożoność rzędu O(N!), gdzie N! oznacza silnię liczby naturalnej N.
W każdym przypadku – co zastrzegaliśmy kilkakrotnie – rząd złożo-
ności obliczeniowej nie mówi nam, jaki dokładnie czas zajmie wykonanie
danego algorytmu dla danej wartości N, charakteryzującej rozmiar danych.
Nie można zatem powiedzieć wprost, że złożoność jest tym lepsza, im mniej
czasu zajmuje wykonanie danego algorytmu. Ustalając rząd złożoności, abs-
trahujemy przecież od konkretnych wartości współczynników wielomianów,
podstaw potęg czy logarytmów itd. Ba, nawet jeszcze nie wiemy, kto, czy
co będzie w przyszłości ten algorytm wykonywało.
Wyobraźmy sobie na przykład, że do przeszukiwania tej samej listy
uporządkowanej angażujemy dwa różne komputery: szybki komputer K1
i wielokrotnie od niego wolniejszy K2. Niech szybki K1 wykonuje pro-
gram powstały z pierwszego algorytmu, o złożoności O(N), drugi (K2, ten
wolniejszy) – program realizujący algorytm o złożoności O(logN), uznany
przez nas za lepszy, sprytniejszy, wykorzystujący wielokrotny podział listy
na połowy.
Jeśli różnica szybkości między K1 i K2 jest znaczna, to prawdo-
podobnie dla mniejszych wartości N fizyczny czas wykonania algorytmu
o złożoności O(N) przez szybki komputer K1 będzie krótszy niż wykonania
lepszego algorytmu przez wolniejszy K2. Jednak wraz ze wzrostem dłu-
gości listy (czyli N) przewaga lomputera K1 będzie coraz mniej wyraźna.
Wreszcie, kiedy N osiągnie pewną graniczna wartość, powiedzmy, N* – cza-
sy wykonania w obu komputerach zrównają się, a potem już dla wszystkich
dalszych wartości N większych od N* fizyczny czas wykonania algorytmu
o złożoności O(logN) będzie nieuchronnie mniejszy, mimo niekorzystnej
różnicy szybkości między dwoma konkurującymi komputerami.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1
lub komputer, który robi to za nas) wiele godzin na krążeniu w pętli, zanim
w końcu pojawi się owa upragniona jedynka.
Co gorsza, nie ma żadnego dowodu na to, że to się musi w końcu
zdarzyć. Innymi słowy, nie mamy pewności, że opisany program na pewno
kiedyś się zatrzyma, niezależnie od tego, jaką liczbę X zadaliśmy na po-
czątku. Liczb naturalnych jest nieskończenie wiele, więc nie mamy szansy
wykazania tego metodą sprawdzenia wszystkich możliwości.
Fakt, że w tysiącach sprawdzonych do tej pory przypadków program
się zatrzymywał – też nie stanowi żadnego dowodu. Może wśród nieskoń-
czonej mnogości liczb naturalnych jest taka jedna, szczególnie złośliwa,
która podstawiona jako początkowe X spowoduje, że program nigdy nie
osiągnie 1 i będzie krążył w swej pętli w nieskończoność? A może jest cała
klasa takich liczb, ukrytych wśród innych liczb naturalnych, podobnie jak
kryją się tam np. liczby pierwsze? Zapewne – gdyby były – nazywałyby
się liczbami Collatza. A może, przeciwnie, żadnych takich „liczb Collatza”
nie ma, program zatrzymuje się dla każdego X, tylko my nie potrafimy tego
udowodnić?
Opisany program jest szczególnie wymowną (bo prostą) ilustracją
problemu, na który może natknąć się każdy programista. Oto okazuje się,
że można napisać program o przejrzystej konstrukcji, o wyraźnie zaznaczo-
nym początku i końcu, językowo poprawny (słowem – wyglądający całkiem
przyzwoicie), ale o niepewnej własności stopu. Nawet sprawdzenie go na
stu czy tysiącu przykładów nie jest w stanie tej wątpliwości rozwiać.
Co gorsza, można mieć pewność, że każdy programista już gdzieś
na początku kariery niechcący wyprodukuje własny, prywatny „problem
Kowalskiego”: program, który się „zapętli” i będzie działał podejrzanie dłu-
go. Padnie wtedy bardzo praktyczne pytanie: czy ten program ma własność
stopu, czy jej nie ma? Czy ów programista, Kowalski, ma poczekać jeszcze
z pięć minut (a może godzinę, a może rok), aż on sam się zakończy, czy
też ma już teraz przerwać jego działanie przez wyłączenie komputera lub
skorzystanie z systemowego menedżera zadań – i zająć się poszukiwaniem
błędu?
Może kryje się tu pomysł na nowe, bardzo ważne zadanie naukowe?
Skoro pytanie o własność stopu ma tak podstawowe znaczenie, to może
należałoby raz na zawsze, dla pożytku i uciechy wszystkich informatyków
świata, opracować skuteczną, uniwersalną metodę badania, czy dany, wska-
zany program własność stopu ma, czy też nie ma? Oczywiście, myślimy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
sobie, taka metoda nie jest na razie znana, ale może by tak ufundować in-
stytut grupujący najtęższe głowy ze światowej społeczności matematyków
i informatyków, który by się tym zajął? Albo wyznaczyć ogromną nagrodę,
o którą konkurowaliby naukowcy z całego świata?
Gdyby udało się ten problem rozwiązać, rozwiązanie powinno mieć
postać algorytmu orzekania o tym, czy zadany, dowolny program ma wła-
sność stopu. Najwygodniej byłoby, gdyby ten algorytm przybrał następnie
postać programu, tak by mógł być wykonywany przez komputer. Z pew-
nością chętnie korzystałby z niego każdy programista. Dla odróżnienia od
innych nazwijmy ten hipotetyczny program – superprogramem.
Zapewne, na początku działania, superprogram wczytywałby zadany,
podlegający analizie program tak, jak się wczytuje każdy inny tekst, skła-
dający się z liter, cyfr, znaków przestankowych, symboli matematycznych
itd. Następnie... No właśnie, tego nie wiadomo, co następnie miałby robić.
W każdym razie, na końcu superprogram zatrzymywałby się w jeden z dwóch
sposobów. Jeżeli okazałoby się, że badany program ma własność stopu, to
superprogram pisałby komunikat „tak” albo „badany program ma własność
stopu” i sam kończyłby działanie. W przeciwnym przypadku superprogram
pisałby „nie” albo „nie ma własności stopu” i też zatrzymywałby się.
Niestety (a może na szczęście dla tych, którzy byliby gotowi poświę-
cić wiele lat życia na takie poszukiwania) nie ma nadziei na to, że uda się
opracować uniwersalną metodę badania własności stopu dowolnych progra-
mów i superprogram będący implementacją tej metody. Już teraz wiadomo,
że taka metoda nie istnieje i istnieć nie może, co da się wykazać na drodze
poprawnego formalnie rozumowania.
Jest rzeczą samą w sobie ciekawą, jak można wykazać nieistnienie
czegoś. Idea dowodu polega na tym, by pokazać, że gdyby taka metoda ist-
niała, to prowadziłoby to nieuchronnie do niedorzeczności, matematycznego
absurdu, do jakieś samosprzeczności, na przykład takiej, jak zdanie, z które-
go prawdziwości wynika jego fałszywość.
Logika i matematyka od wieków podsuwają pomysły takich para-
doksów. Już w starożytnych Atenach zabawiano się taką zagadką: „Pewien
Kreteńczyk twierdzi, że wszyscy Kreteńczycy kłamią. Czy więc to prawda,
że Kreteńczycy kłamią?”. Problem polega na tym, że jeśli ten Kreteńczyk
mówi prawdę, a sam jest Kreteńczykiem, to znaczy, że on też kłamie, czyli
nie mówi prawdy. Z drugiej strony, jeśli kłamie, mówiąc, że Kreteńczycy
kłamią, to Kreteńczycy naprawdę nie kłamią, a więc on też nie kłamie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Krótko mówiąc, jeśli mówi prawdę, to nie mówi prawdy, a jeśli kłamie, to
nie kłamie.
Oczywiście, rozumowanie takie nie prowadzi do żadnych interesu-
jących wniosków co do prawdomówności lub kłamliwości mieszkańców
Krety. To jedynie przykład wypowiedzi, która jest logicznie źle skonstru-
owana. W ramach klasycznej logiki dwuwartościowej (ściślej – w rachun-
ku zdań) przyjmuje się bowiem, że zdanie logiczne może być tylko albo
prawdziwe, albo fałszywe i trzeciej możliwości nie ma (tertium non datur).
W codziennym życiu raczej rzadko występują zdania, które mają taką przy-
jemną właściwość, ale w klasycznej logice – wyłącznie takie. Tylko one
zasługują na miano zdań logicznych, w ścisłym sensie.
W szczególności z takimi zdaniami mamy do czynienia w przypad-
ku twierdzeń matematycznych. Chcielibyśmy, żeby były one (twierdzenia)
tautologiami, to znaczy zdaniami logicznymi, które są zawsze prawdziwe.
Dowód twierdzenia polega na wykazaniu, że pewne wypowiedziane zdanie
logiczne (właśnie owo twierdzenie) jest nieuchronnie prawdziwe. Ale wy-
nika z tego również, że muszą być spełnione dwa warunki: taka wypowiedź
po pierwsze musi być zdaniem logicznym, a dopiero po drugie – prawdzi-
wym zdaniem logicznym.
Tymczasem można sformułować wypowiedź, która jest zdaniem je-
dynie w potocznym, „gramatycznym” sensie. Ma wprawdzie podmiot, orze-
czenie itd., jednak nie jest zdaniem w rozumieniu klasycznej logiki, skoro
nie można orzec, czy jest prawdziwe, czy fałszywe. Tak właśnie jest w przy-
padku opinii Kreteńczyka o jego współrodakach. Na jej podstawie nie moż-
na udowodnić ani twierdzenia o tym, że Kreteńczycy kłamią, ani o tym, że
nie kłamią, gdyż nie jest ona w ogóle zdaniem w sensie klasycznej logiki.
W przypadku naszego superprogramu, „haczyk”, na którym można
skonstruować (czy może – skoro ma to być haczyk – raczej: zawiesić) po-
dobny paradoks – kryje się w zakładanej uniwersalności superprogramu. Ma
on orzekać o własności stopu wszelkich programów, ale przecież on sam jest
również programem. Powinien więc poradzić sobie z analizą samego siebie,
tzn. tekstu tegoż superprogramu, zadanego mu na wejściu tak, jak się zada-
je każdy inny program. Co więcej, on sam powinien mieć własność stopu,
gdyż inaczej całe przedsięwzięcie nie miałoby sensu.
Nie będziemy tu prowadzili całego rozumowania. W każdym razie,
przygotowując nieco zmodyfikowaną wersję superprogramu, można dopro-
wadzić do sytuacji, w której jeśli orzeknie on, że superprogram ma wła-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
sność stopu, to sam (też będąc superprogramem) się nie zatrzyma, a jeżeli
orzeknie, że tej własności nie ma – to zatrzyma się natychmiast. Innymi
słowy, zdanie mówiące o tym, że hipotetyczny superprogram może istnieć
– jest w konsekwencji równie samosprzeczne, jak tamta ateńska wypowiedź
o mieszkańcach Krety.
Nie uda się nam skonstruować algorytmu orzekania o własności stopu
dowolnego algorytmu. Tak więc nie każdy pomysł na zadanie obliczeniowe
musi koniecznie zakończyć się algorytmicznym rozwiązaniem. Istnieje kla-
sa problemów, które w ogóle nie są algorytmicznie rozstrzygalne lub obli-
czalne. To ważny element naszej wiedzy informatycznej: nie wszystko, co
byśmy chcieli – da się zalgorytmizować, przekształcić w program i wyko-
nać przez komputer, nawet w odległej przyszłości i przy zaangażowaniu
najwybitniejszych fachowców.
Już w pierwszym rozdziale podaliśmy zresztą inny przykład proble-
mu, który także należy do tej klasy. Powiedzieliśmy tam, że – podobnie jak
superprogram – nie może istnieć algorytm tworzenia dowolnych algoryt-
mów. Skądinąd, gdyby istniał – implementowalibyśmy go w postaci uniwer-
salnego programu projektującego, natychmiast kazalibyśmy mu zbudować
algorytm badania własności stopu i sprawa byłaby rozwiązana.
Dla ścisłości należy dodać, że choć problem stopu dla dowolnego
programu jest algorytmicznie nierozstrzygalny, to dla niektórych typów
programów, ograniczonych pewnymi warunkami, można znaleźć algo-
rytm (i stworzyć odpowiednio wyspecjalizowany „superprogram”) bada-
nia własności stopu tej ograniczonej klasy konstrukcji programistycznych.
Podobnie, choć nie istnieje algorytm tworzenia wszelkich algorytmów (ani
program służący do pisania wszelkich programów), istnieją programy zdol-
ne do generowania pewnej ograniczonej klasy programów. To nawet zja-
wisko powszechne w praktyce: w gruncie rzeczy każdy translator języka
programowania sam jest programem, służącym do produkowania innych
programów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
3. Trudne problemy,
które wybuchają
Komiwojażer ma problem
Wspomnieliśmy już, że istnieje cała klasa problemów, zwanych problema-
mi NP, które wciąż stanowią wyzwanie dla specjalistów zajmujących się
zarówno praktycznymi zastosowaniami algorytmów, jak teorią złożoności
obliczeniowej.
Ze względu na charakter książki nie będziemy usiłowali zagłębiać się
w podstawy tej teorii ani formalnie definiować samych problemów NP (ang.
NP problems), problemów NP-trudnych (ang. NP-hard) czy NP-zupełnych
(ang. NP-complete) i związków między nimi. Jest to dość trudna, stosunko-
wo nowa dziedzina matematyki, jednak – jak zobaczymy – przypisuje się
jej wielkie znaczenie dla całej współczesnej nauki. My ograniczymy się do
intuicyjnego wprowadzenia w tę tematykę.
Zacznijmy od zadania, które zostało sformułowane jeszcze w XIX
wieku. Początkowo nosiło ono nazwę problemu listonosza, ale teraz, już
od dawna, jest znane jako problem komiwojażera (ang. traveling salesman
problem).
Komiwojażer, czyli wędrowny sprzedawca, obwozi swój towar po
kraju i zachęca do jego kupowania. Ruszając w podróż, planuje odwiedzenie
kilku czy kilkunastu miejscowości i na koniec – powrót do domu. Chciałby
każdą z miejscowości odwiedzić tylko raz, a jednocześnie zaplanować swo-
ją podróż tak, by przebyta droga była jak najkrótsza. To właśnie znajdowa-
nie najkrótszej drogi jest przedmiotem zadania.
Posłużmy się, jak zwykle, prostym przykładem. Powiedzmy, że ko-
miwojażer mieszka w Warszawie i planuje odwiedzić każde z trzech miast:
Gdańsk, Poznań i Rzeszów, a następnie powrócić do Warszawy. Spróbujmy
wyznaczyć najkrótszą drogę, która spełnia ten warunek.
Danych potrzebnych do rozwiązania problemu dostarczy nam mapa
drogowa. W uproszczonej wersji możemy ją uważać za graf (jak na rysun-
ku 3.1), którego węzłami są interesujące nas cztery miasta (W – Warszawa,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Komiwojażer ma problem 49
ność jest (co ważne i godne zapamiętania) zupełnie dowolna. Każda z tych
trzech możliwości rozgałęzia się znów na dwa możliwe sposoby kontynu-
owania podróży. Jeżeli jako pierwsze miasto wybraliśmy Gdańsk, to z niego
możemy pojechać dalej albo do Poznania, albo do Rzeszowa: Gdańsk mamy
już bowiem zaliczony. Jeżeli pierwszy etap prowadził do Poznania, to dalej
możemy jechać albo do Gdańska, albo do Rzeszowa, i tak dalej. W każdym
z sześciu przypadków, utworzonych w wyniku takich dwóch kolejnych wy-
borów, brakuje już tylko jednego miasta do odwiedzenia. W trzecim kroku
musimy wybrać to jedno („brakujące”) miasto, w czwartym musimy jeszcze
powrócić do Warszawy. Ta „ponowna Warszawa” stanowi liście drzewa, to
znaczy takie węzły, z których nie wyrastają żadne nowe gałęzie.
Każda ścieżka w drzewie decyzji, zaczynająca się od korzenia i koń-
cząca się w jednym z liści, opisuje jeden wariant trasy podróży. Jest ich
więc w sumie sześć, tyle, ile liści drzewa. Każda ścieżka składa się z czte-
rech odcinków. Łatwo policzyć jej długość: poszczególne odcinki gałęzi
w drzewie decyzji odpowiadają przecież odcinkom drogi pomiędzy dwoma
miastami. Wystarczy je podsumować dla każdej z sześciu ścieżek, porównać
– i znaleźć w ten sposób najkrótszy wariant trasy podróży.
Warto zrobić takie obliczenia dla naszego konkretnego przykładu. Gdy
zaczniemy wyznaczać długości poszczególnych tras – szybko zorientujemy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
się, że musimy w rzeczywistości wykonać nie sześć, lecz tylko trzy opera-
cje sumowania czterech liczb. Założyliśmy bowiem, że połączenia między
miastami są dwukierunkowe, a więc każdą drogę możemy przejechać albo
w jednym, albo w drugim kierunku, pokonując tę samą liczbę kilometrów.
W rezultacie trasa „Warszawa – Gdańsk – Poznań – Rzeszów – Warszawa”
jest (co do długości) identyczna z tą samą trasą, przejechaną w odwrotnym
kierunku: „Warszawa – Rzeszów – Poznań – Gdańsk – Warszawa”. To nie-
co skraca obliczenia.
W końcu okazuje się, że najkrótsza droga (właśnie wspomniana
„Warszawa – Gdańsk – Poznań – Rzeszów – Warszawa” lub odwrotnie)
liczy 1473 km, podczas gdy najdłuższa („Warszawa – Gdańsk – Rzeszów
– Poznań – Warszawa”, czy na odwrót) liczy 1841 km. To ponad 350 km
różnicy! Opłaca się więc poświęcić nieco czasu na skonstruowanie drze-
wa decyzyjnego i wykonanie kilku prostych sumowań. Komiwojażer może
oszczędzić sobie w ten sposób ładnych kilka godzin nużącej jazdy i zużyć
o kilkanaście litrów paliwa mniej.
Mamy więc dwie optymistyczne wiadomości. Pierwsza to taka, że
algorytm rozwiązania problemu komiwojażera istnieje. Druga – że jego wy-
konanie może przynieść całkiem wymierne, praktyczne korzyści. Jednak już
wiemy, że rozwiązanie zadania dla jednego, przykładowego zestawu danych
nie wystarcza, by uznać, że mamy już skuteczny algorytm rozwiązania pro-
blemu. Dobrze też byłoby wiedzieć, jaka jest złożoność obliczeniowa tego
algorytmu. Spróbujmy więc uogólnić problem.
W bardziej ogólnym ujęciu problem wygląda następująco. Mamy
dany pełny i nieskierowany graf, który ma N + 1 węzłów: jeden, który jest
miejscem zamieszkania komiwojażera oraz N tych, które ma on odwiedzić.
Krawędzie grafu odpowiadają połączeniom drogowym między miastami.
Ich długość jest znana. W tak zadanym grafie poszukujemy ścieżki, któ-
ra obiegłaby wszystkie węzły, odwiedzając każdy z nich dokładnie jeden
raz. Taka droga w grafie nazywa się ścieżką Hamiltona. Nam chodzi nawet
o coś więcej: o to, by ta ścieżka wracała do swego początkowego węzła,
czyli tworzyła zamknięty cykl (a więc cykl Hamiltona) i dodatkowo – by
była najkrótszym z takich cykli.
W grafie pełnym można znaleźć wiele cykli Hamiltona. Właśnie, jak
wiele? Konstrukcja drzewa decyzyjnego pozwala odpowiedzieć na to pyta-
nie. Jeżeli komiwojażer ma odwiedzić N miast, to z korzenia drzewa wyra-
sta N gałęzi. Każda z tych N pierwszych decyzji rozgałęzia się na (N – 1)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Komiwojażer ma problem 51
N · (N – 1) · (N – 2) · ... · 3 · 2 · 1 = N!
Dlaczego NP? 53
Dlaczego NP?
W teorii złożoności obliczeniowej termin NP (cokolwiek na razie ten skrót
znaczy) odnosi się w zasadzie do problemów decyzyjnych, to znaczy takich,
których oczekiwanym rozwiązaniem jest jedynie odpowiedź „tak” albo
„nie”. Problem komiwojażera, sformułowany tak, jak to zrobiliśmy wyżej,
nie jest – formalnie rzecz biorąc – problemem decyzyjnym, lecz obliczenio-
wym, podobnie jak wiele innych problemów optymalizacji. Zadanie polega-
ło przecież nie na uzyskaniu takiej binarnej odpowiedzi, lecz na znalezieniu
najkrótszej drogi. Jednak dla każdego zadania obliczeniowego można sfor-
mułować wiele równoważnych mu (co do sposobu rozwiązania i złożoności
obliczeniowej) zadań decyzyjnych. W przypadku problemu komiwojażera
takimi równoważnymi zadaniami decyzyjnymi mogłyby być, na przykład,
następujące pytania:
– czy w danym (pełnym) grafie istnieje cykl Hamiltona krótszy niż
1200 km?
– czy trasa „Warszawa – Gdańsk – Rzeszów – Poznań – Warszawa” jest
najkrótszym z możliwych cykli Hamiltona?
itp. Dla dowolnego grafu, o którym nie wiemy, że jest grafem pełnym, do-
brym problemem decyzyjnym jest pytanie, czy w ogóle istnieje w nim choć-
by jeden cykl Hamiltona. Sposób naiwnego rozwiązywania (naiwnego, bo
przez prostoduszne wyliczanie) każdego z takich równoważnych problemów
decyzyjnych jest bardzo zbliżony do opisanego wyżej, a co ważniejsze – zo-
staje zachowana złożoność obliczeniowa, w tym przypadku O(N!), wraz ze
wszystkimi płynącymi stąd kłopotami. Dlatego o zadaniach obliczeniowych
też możemy mówić, że są NP.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Dlaczego NP? 55
2 Warto dodać, że w telefonii GSM, dzięki technice podziału czasu, z jednej czę-
stotliwości może korzystać do ośmiu użytkowników jednocześnie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Każdej stacji BTS należy więc przypisać z góry pewną pulę częstotliwości,
którą ona potem dynamicznie dysponuje, przydzielając je zgłaszającym się
abonentom. Ale jednocześnie należy pamiętać, że żadne dwie stacje o na-
kładających się zasięgach nie powinny nadawać i odbierać na tej samej
częstotliwości, gdyż prowadziłoby to do wzajemnych zakłóceń transmisji.
Częstotliwości należy więc rozpisać pomiędzy stacje bazowe tak, by każda
stacja miała możliwie jak najwięcej częstotliwości do dyspozycji (od tego
zależy, ilu abonentów naraz będzie w stanie obsłużyć), ale jednocześnie tak,
by żadne dwie stacje o nakładających się zasięgach nie miały przypisanej tej
samej częstotliwości.
Wyobraźmy sobie więc sieć telefonii GSM jako wielki graf, którego
węzłami są stacje BTS. Każde dwie stacje, których zasięgi zazębiają się
– połączmy krawędzią. Skoro – jak powiedzieliśmy – stacji bazowych jest
kilka tysięcy, a w każdym punkcie terenu muszą się pokrywać zasięgi kilku
czy kilkunastu z nich, to powstanie niezła plątanina. Teraz każdemu wę-
złowi takiego skomplikowanego grafu powinniśmy przypisać pewną liczbę
częstotliwości, ale tak, by żadne dwa węzły bezpośrednio połączone krawę-
dzią nie otrzymały tej samej częstotliwości.
Czy już widać, że jeśli częstotliwości nazwiemy kolorami, to mamy
tu do czynienia z odmianą problemu kolorowania grafu?
Żeby lepiej zorientować się co do skali problemu, dodajmy, że dla po-
trzeb telefonii GSM zarezerwowano (w okolicach 900 MHz) 124 częstotli-
wości dla łączności w kierunku od mobilnego aparatu do BTS oraz 124 czę-
stotliwości dla łączności w przeciwnym kierunku. W praktyce niekoniecznie
wszystkie są dostępne, gdyż niektóre z nich mogą być np. przeznaczone do
celów wojskowych, dla ratownictwa. Częstotliwości te w wyniku państwo-
wego przetargu dzieli się pomiędzy kilku operatorów, którzy płacą ogrom-
ne sumy za koncesję na ich używanie. Tak więc każdy operator otrzymuje
kilkanaście czy kilkadziesiąt par częstotliwości, którymi musi pokolorować
graf, zawierający kilka tysięcy węzłów (BTS) i monstrualną liczbę krawę-
dzi, symbolizujących zazębianie się ich zakresów. I to musi działać, a nawet
– rzeczywiście działa.
Znaczy to, że choć problem jest NP, to muszą istnieć (i rzeczywiście
istnieją) skuteczne algorytmy przydziału częstotliwości w sieci. Z pewnością
nie rozwiązują one zadania przez naiwne wyliczanie i sprawdzanie wszyst-
kich możliwości, nie gwarantują stuprocentowej optymalności rozwiązania,
lecz dają satysfakcjonujące wyniki w sensownym czasie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
(ang. conjecture), a nie formalne twierdzenie, ponieważ nie było ono wspar-
te matematycznym dowodem.
Praktyka dostarczyła licznych przykładów na słuszność twierdzenia
Guthriego, ale matematycy biedzili się nad jego formalnym udowodnieniem
jeszcze przez ponad sto dwadzieścia lat. Dopiero w 1976 roku dwaj ame-
rykańscy badacze, Wolfgang Haken i Kenneth Appel, przedstawili dowód,
w którym – jako pierwsi – posłużyli się w procedurze dowodowej kompu-
terem.
Po pierwsze, wykazali oni formalnie, że każdy płaski graf można zło-
żyć z pewnej liczby prostszych części, jak gdyby pewnych typowych wzor-
ców ułożeń państw i granic między nimi. Repertuar wszystkich możliwych
wzorców tego typu liczy – co też wykazali – dokładnie 1936 wyszczegól-
nionych przez nich pozycji. Następnie, wykorzystując napisany przez siebie
program komputerowy, sprawdzili, że każdy z tych 1936 układów państw
można pokolorować, używając nie więcej niż czterech barw. Jeśli tak, to
całe twierdzenie o czterech kolorach można uznać za udowodnione.
Sprawdzenie każdego z owych 1936 wzorców polegało na algo-
rytmicznym, systematycznym przeglądaniu wszystkich możliwych ułożeń
czterech kolorów. Oczywiście, taki algorytm eksploduje wykładniczo, jed-
nak w każdym z podstawowych wzorców liczba państw była stosunkowo
niewielka. Dlatego po przeszło 1200 godzinach pracy komputera badaczom
udało się doprowadzić dowód do szczęśliwego zakończenia.
Tradycyjni matematycy długo mieli zastrzeżenia do takiej techniki
przeprowadzenia dowodu. Mówili, że nie jest to matematycznie eleganckie
rozumowanie, tylko ordynarne wyliczenie i sprawdzenie („na siłę”) wszyst-
kich możliwości. Poza tym, gdzie jest dowód, że sam program nie zawie-
ra jakiegoś ukrytego błędu, który prowadzi do niewłaściwych wniosków?
Niemniej, mimo tych wątpliwości, w obecnej chwili twierdzenie o czterech
barwach uważa się za udowodnione.
W przypadku problemu kolorowania map trudno dziś mówić o jego
bezpośrednich praktycznych zastosowaniach. Na szczęście, współczesna
poligrafia czy grafika komputerowa potrafią sobie radzić z większą liczbą
barw. Historyczna zasługa problemu polega jednak na tym, że intelektualne
zmagania z długo niedającym się udowodnić twierdzeniem przyczyniły się
do rozwoju matematyki.
Jednym z rezultatów było sformułowanie i udowodnienie warunku,
który jest konieczny i wystarczający do tego, by mapa zawierająca N państw
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
dała się pokolorować za pomocą dwóch kolorów. Nie będziemy tego wa-
runku podawać, powiemy jedynie, że jego sprawdzenie wymaga obliczenia
o wielomianowej złożoności. Dobrze znanym przykładem takiej sytuacji jest
szachownica. Ponieważ dla szachownicy ów warunek jest spełniony, to daje
się ona pokolorować dwoma kolorami, mimo że liczy aż 64 pola.
W rezultacie okazuje się, że złożoność algorytmu, który potrafiłby
rozstrzygnąć (odpowiedzieć „tak” albo „nie”), czy daną mapę N państw
można pokolorować za pomocą K kolorów – zależy w nieoczekiwanie
dziwny i nieoczywisty sposób od liczby K:
– jeżeli K = 1, to algorytm zatrzymuje się szybko, bo odpowiedź jest na-
tychmiastowa: nie, nie można,
– jeżeli K ≥ 4, to algorytm zatrzymuje się szybko, bo odpowiedź jest na-
tychmiastowa: tak, można,
– jeżeli K = 2, to algorytm ma złożoność wielomianową,
– jeżeli natomiast K = 3, to problem jest NP i ma wykładniczą złożoność
O(3N).
Przykłady tego typu można by długo mnożyć: warto wspomnieć, że
problemów, należących do klasy tzw. problemów NP-zupełnych (ang. NP-
-complete problems), takich jak wyżej wymienione, zidentyfikowano i na-
zwano ponad trzy tysiące.
4. O metodach konstruowania
algorytmów
ficzną, współczesną etykę biznesu i rynku: dobre jest to, co szybko schodzi,
przynosi zysk i napędza produkcję.
Może się nam to podobać lub nie, ale musimy przyznać, że wyni-
kiem tej sytuacji jest niezwykły postęp ilościowy, przejawiający się w ma-
sowej dostępności różnorodnych towarów i usług, również – w dziedzinie
sprzętu i oprogramowania komputerowego. Jednak prawdziwy, jakościowy
postęp dokonuje się nadal w staroświecki sposób. Nie rozpoczyna się od
klikania myszą lub pisania kodu programu od razu na klawiaturze, lecz od
tego, że kilka osób siada przy stole, nad czystą kartką, z ołówkiem w jednej,
a kubkiem kawy w drugiej ręce – i zaczyna, antiquo modo, jak sto lat temu,
szkicować niechlujne rysunki, jakieś bloczki i strzałki, pisać na margine-
sach wzory i uwagi, wyrzuca to wszystko do kosza, zaczyna jeszcze raz...
Oczywiście, kiedyś musi to w końcu przybrać jakąś porządną formę, mieć
jakiś sensowny wygląd na ekranie czy w druku. Warto wtedy posłużyć się
gotowymi wzorcami, by nie wymyślać wszystkich technicznych szczegółów
od nowa. Oczywiście, przyjdzie czas i na finansową stronę przedsięwzięcia,
biznes plan, kredyt, inwestora… Ale najpierw jest pomysł, zamiar, motywa-
cja i… nocne godziny spędzone nad kartką papieru.
Z tego, że procesy twórcze przebiegają w znacznym stopniu w spo-
sób, który nie w pełni rozumiemy i kontrolujemy – nie wynika, że wystar-
czy usiąść i biernie czekać na natchnienie. Do rozwiązywania problemów
możemy przynajmniej starać się przygotować. Każda dziedzina dysponu-
je pewnym zasobem wiedzy o materii, którą operuje. Nawet początkujący
amator, który chce skomponować prostą piosenkę, musi wiedzieć choćby to,
że oktawa dzieli się na tony i półtony, że istnieją tonacje dur i moll, że sam
utwór ma pewną strukturę: są zwrotki, jest refren, jest metrum i schemat
rytmiczny. To podstawowe, warsztatowe wiadomości, a prawdziwy, profe-
sjonalny kompozytor musi ich poznać znacznie, znacznie więcej.
Nie inaczej jest w przypadku algorytmów. Tym, czym dla muzyka są
tonacje, zapis nutowy i podstawowe akordy – dla informatyka są podstawo-
we struktury danych. Poznaliśmy ich już kilka: liczby rzeczywiste i całko-
wite, znaki alfanumeryczne, zmienne logiczne są zestawiane w listy, tabli-
ce, drzewa, grafy, kolejki itd. Projektant algorytmu musi sam zdecydować,
w jakie struktury zorganizuje dane, opisujące jego problem. Bardzo często
odpowiednio trafny wybór odpowiednich struktur danych wręcz decyduje
o sukcesie: o szybkości wykonania i rozmiarze pamięci potrzebnej dla da-
nego algorytmu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Algorytmy zachłanne 73
Algorytmy zachłanne
Już sama nazwa algorytmów zachłannych (ang. greedy algorithms) dobrze
oddaje zasadę, na jakiej są one budowane. Łatwo to pokazać znów na wielo-
krotnie przez nas przywoływanym przykładzie problemu komiwojażera.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Algorytmy heurystyczne 75
N + (N – 1) + (N – 2) + ... + 3 + 2 + 1.
N · (N – 1) · (N – 2) · ... · 3 · 2 · 1 = N!
Algorytmy heurystyczne
Według znanej anegdoty wielki grecki uczony, Archimedes z Syrakuz, właś-
nie był siedział w wannie, kiedy w przebłysku olśnienia zrozumiał, ile traci
pozornie na ciężarze każde ciało zanurzone w cieczy. Poruszony tym od-
kryciem wyskoczył z wanny i wołając: „Heureka! Heureka!” (co po grecku
znaczy odkryłem, wynalazłem), wybiegł na ulicę, by podzielić się tą nowiną
z mieszkańcami Syrakuz. Wzbudził pewną sensację, niekoniecznie dlatego,
że przechodnie natychmiast pojęli wagę odkrycia (znanego odtąd jako pra-
wo Archimedesa), ale przede wszystkim dlatego, że przejęty swym wyna-
lazkiem uczony zapomniał był się ubrać.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Czy tak było, czy nie – od wspomnianego greckiego słowa biorą swą
nazwę algorytmy heurystyczne (ang. heuristic algorithms). Opierają się one
na jakimś odkryciu, ulepszeniu, dobrym pomyśle, na który wpadł twórca al-
gorytmu. Pomysł taki nie musi mieć głębszego teoretycznego uzasadnienia:
bardzo często bierze się on z intuicji, nagłego skojarzenia, wynalazczego
olśnienia, ale jeśli po sprawdzeniu okaże się, że daje dobre rezultaty – to
wart jest zastosowania.
Często okazuje się potem, ex post, że taki heurystyczny pomysł ma
w rzeczywistości dobre teoretyczne uzasadnienie. To zresztą w matematyce
czy innych działach nauki prawie reguła: intuicja czy błysk geniuszu za-
zwyczaj wyprzedza eksperymentalne czy też formalne, teoretyczne dowody,
bywa nawet, że o wiele lat.
W przypadku algorytmów heurystycznych nie musi chodzić o na-
prawdę przełomowe odkrycia. Nie ma też obowiązku biegania goło po ulicy
i powiadamiania przechodniów o swym wynalazku. Wystarczy, jeśli pomysł
sprawdza się w praktyce. Prawdę mówiąc, termin „heurystyczny”, ponie-
waż brzmi uczenie i pochodzi z greki, często zastępuje proste wyjaśnienie:
„zrobiłem tak, bo mi się wydawało, że tak będzie lepiej – i okazało się, że
w większości przypadków mam rację”. Mądrzej brzmi, jeśli powiemy: „ko-
ledzy, tu trzeba by opracować jakąś dobrą heurystykę”, zamiast „koledzy,
musimy tu coś sprytnego wymyślić...”.
Terminy „heurystyka” i „heurystyczny” często występują w takim
kontekście i nie ma w tym nic złego. A z tego, co napisaliśmy wyżej o sztu-
ce tworzenia algorytmów, wynika, że wszystkie algorytmy są w pewnym
sensie heurystyczne: na początku jest jakiś odkrywczy pomysł, który potem
staje się wszystkim znany, powszednieje i nie jest uważany za coś niezwy-
kłego.
Heurystyczne ulepszenia algorytmów nie przychodzą do głowy same,
bez żadnego powodu. Z reguły pojawiają się po pewnym czasie intensyw-
nego zajmowania się problemem, gdy wykonamy kilka prób ręcznie, na pa-
pierze, gdy zyskamy już pewien wgląd w istotę problemu i jego specyfikę,
odkryjemy szczególne przypadki, które być może są wcale nie tak rzadkie,
a przede wszystkim wtedy, gdy problem nas prawdziwie dręczy, gdyż nie
daje się łatwo rozwiązać albo gdy znane rozwiązanie ma wady, których bar-
dzo chcielibyśmy się pozbyć.
W poprzednim podrozdziale omówiliśmy zachłanne rozwiązanie pro-
blemu komiwojażera. Męczące w nim jest to, że wybierając lokalnie naj-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Algorytmy heurystyczne 77
krótszy odcinek drogi, być może tracimy z oczu jakieś lepsze rozwiązanie.
Nagle przychodzi nam do głowy pomysł: a może algorytm działałby lepiej,
gdybyśmy dokonywali wyboru nie na podstawie porównania długości poje-
dynczych odcinków, ale na podstawie porównania potencjalnych dróg o dłu-
gości dwóch odcinków? To najprostszy przykład możliwego heurystycznego
ulepszenia algorytmu zachłannego. Pomysł sprowadza się do tego, by po-
czynając od korzenia drzewa decyzyjnego, obliczyć długość N · (N –1) wy-
chodzących z niego dwuodcinkowych dróg, znaleźć najkrótszą z nich, prze-
sunąć się wzdłuż niej o jedno miasto, znów obliczyć i porównać długości
(N –1) · (N –2) dwuodcinkowych dróg, przesunąć się o jedno miasto wzdłuż
najkrótszej z nich i tak dalej, aż do zamknięcia cyklu. Oczywiście, takie roz-
wiązanie też nie gwarantuje globalnej optymalności, ale tak na zdrowy rozsą-
dek wydaje się nieco lepsze. Z drugiej strony, zapłacimy za to większą liczbą
operacji. A czy złożoność obliczeniowa pozostanie wielomianowa? Czy taka
heurystyka się opłaci? Może warto to sprawdzić?
Akurat dla problemu komiwojażera tej metody sprawdzać nie warto,
znane są już bowiem sposoby znacznie skuteczniejsze. Problem komiwo-
jażera od dawna zajmuje matematyków i informatyków, gdyż jest dobrze
przemawiającym do intuicji przykładem zadania NP-zupełnego, na którym
można sprawdzać pomysły na nowe algorytmy w nadziei, że dadzą się one
zastosować również do innych problemów tej klasy. W rezultacie, na prze-
strzeni ostatnich kilkudziesięciu lat opracowano dziesiątki bardzo spraw-
nych algorytmów, wykorzystujących zarówno różne formy podziału zadania
na podzadania, jak inne techniki (np. wczesne eliminowanie poddrzew drze-
wa decyzyjnego, o których już w trakcie obliczeń wiadomo, że są na pewno
gorsze od innych) i różne pomysły heurystyczne.
Nie będziemy tu omawiać tych algorytmów dokładniej. Powiemy je-
dynie, że są bardzo skuteczne, a miarą ich skuteczności niech będzie fakt, że
w roku 2001 wyznaczono przy ich użyciu najkrótszy cykl Hamiltona wio-
dący przez 15 112 miast w Niemczech, w roku 2004 rozwiązano problem
komiwojażera dla 24 978 miejscowości w Szwecji, a w roku 2006 znale-
ziono najkrótszą drogę poprzez ponad 85 000 punktów rozmieszczonych
na płytce drukowanej do montażu elementów elektronicznych. W ostatnim
wspomnianym przypadku obliczenia te były wykonywane przez sieć złożoną
ze 110 procesorów. Oceniono, że na pojedynczym komputerze (i to niezłym,
z zegarem 500 MHz) rozwiązanie tego zadania zajęłoby ponad 136 lat. Ale
na tym nie koniec. W Internecie można znaleźć doniesienia o konkursie na
obliczenie najkrótszego cyklu poprzez 1 904 711 miejscowości na ziemskim
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
globie. Szacuje się, że aktualny wynik jest tylko o niecałe 0,05% gorszy od
optymalnego.
Algorytmy rekurencyjne
Algorytmy oparte na zasadzie rekurencji albo inaczej: rekursji (ang. recur-
rence, recursion, recursive algorithms), wykorzystują w najbardziej konse-
kwentny sposób zasadę dekompozycji problemu na zadania prostsze. W tym
przypadku, zarówno wszystkie te prostsze zadania (podproblemy), jak pro-
ces ich łączenia w celu rozwiązania całości – przebiegają według jednej,
dokładnie tej samej procedury. Dlatego forma algorytmu jest wyjątkowo
przejrzysta i zwarta, można powiedzieć – elegancka w swojej prostocie.
Algorytmy rekurencyjne są chętnie wykorzystywane w praktyce
programowania, ponieważ ambicją dobrego programisty powinno być za-
pisanie złożonej procedury obliczeniowej możliwie jak najkrócej. Niech
potem komputer się napracuje, od tego jest. Natomiast rozwlekłość tekstu
programu jest zazwyczaj oznaką amatorskiego, chałupniczego stylu pro-
gramowania.
Jednocześnie bywa, że zrozumienie zasad działania algorytmów reku-
rencyjnych sprawia trudności. Procedura rekurencyjna wywołuje samą sie-
bie, co w pierwszej chwili może wydawać się pomysłem równie dziwacz-
nym i niedorzecznym jak wąż, który poczynając od ogona sam siebie zjada
i w końcu znika. W rzeczywistości mechanizm rekurencyjnego wykonania
jest bardzo pomysłowy i warto włożyć nieco wysiłku w jego zrozumienie,
z korzyścią dla znajomości zasad eleganckiego konstruowania algorytmów.
Dla zilustrowania problemu posłużymy się przykładem rekurencyjne-
go algorytmu sortowania ze scalaniem (ang. merge sort). Sortowanie – to po
prostu porządkowanie listy nieuporządkowanej. Scalanie (ang. merging) jest
natomiast operacją, w której z dwóch już uporządkowanych list tworzy się
jedną, łączną (i tak samo uporządkowaną) listę zawierającą wszystkie ele-
menty z obu list wejściowych. Sortowanie ze scalaniem jest jedną z wielu
możliwych odmian algorytmu sortowania.
Zacznijmy od prostszej, jeszcze nie rekurencyjnej operacji scalania.
Sama jej zasada jest zilustrowana na rysunku 4.2. Załóżmy, że mamy dwie
listy: LA oraz LB, których elementami są (jak poprzednio) liczby naturalne.
Listy te mogą być różnej długości, ale – co bardzo ważne – są już uporząd-
kowane, przyjmijmy, że w kierunku niemalejącym. Na razie nie martwmy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Algorytmy rekurencyjne 79
jest spełniony wtedy i tylko wtedy, gdy jest prawdą, że (L(a) = #) i jedno-
cześnie jest prawdą, że (L(b) = #).
Jak widać, algorytm, dopóki nie dotrze do końca żadnej z list – krąży
w lewej (na rysunku) pętli, kopiując do listy LC kolejne pozycje z obu list,
w odpowiednim (niemalejącym) porządku. Jeżeli dotarł w ten sposób do
końca obu list – zatrzymuje się. Jeżeli natomiast jedna z list skończyła się
wcześniej – kopiuje (w prawej pętli) do LC pozostałą jeszcze resztę drugiej
z nich. Dalsze szczegóły Czytelnik łatwo zanalizuje sam, najlepiej – ręcznie
wykonując jakiś prosty przykład.
Ponieważ będziemy operację scalania wykonywali wielokrotnie (i to
dla różnych list) – zdefiniujmy ją w taki sposób, by można było ją potem
wykorzystywać w innych obliczeniach jako pewną gotową, zamkniętą ca-
łość, tak jak gdyby była to wręcz pojedyncza instrukcja. Taką konstrukcję
nazywa się w programowaniu funkcją (ang. function), a czasami procedurą
(ang. procedure). Nie będziemy się tu wdawali w subtelne różnice między
tymi terminami ani komentowali tego, jak sam termin „funkcja” jest rozu-
miany w matematyce i w praktyce programowania. Weźmy po prostu nową
kartkę i zatytułujmy ją:
Algorytmy rekurencyjne 81
Algorytmy rekurencyjne 83
L = 23; 4; 33; 2; #
nowaL := sortuj(L);
Algorytmy rekurencyjne 85
L1 = 23; #.
L3 = 23; #
L4 = 4; #.
Algorytmy rekurencyjne 87
y = 4; 23; #,
2 Kto okaże wytrwałość i dotrwa do rozdziału 17, ten dowie się, w jaki sposób
tworzony jest stos i jak obsługiwane są zagnieżdżone wywołania funkcji w praw-
dziwym systemie komputerowym.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Algorytmy rekurencyjne 89
posługując się przy tym jednym stosem pomocniczym, ale wolno im w jednym
ruchu przekładać tylko jeden krążek i nie wolno nigdy kłaść krążka większego
na mniejszym.
Kiedy już przełożą w ten sposób wszystkie 64 krążki – nastąpi koniec
świata.
Fakt, że rozwiązanie tego zadania w przypadku N krążków ma zło-
żoność O(2N) powinien nas nieco uspokoić. Kto potrafi wyobrazić sobie,
co to znaczy 264, ten zrozumie, że nawet jeśli ci kapłani są bardzo zręczni
i wykonują milion ruchów na sekundę – to jeszcze trochę pożyjemy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
5. Algorytmy probabilistyczne
i ewolucyjne
Żeby otrzymany wynik był wiarygodny, muszą być spełnione dwa warunki.
Po pierwsze, losowane punkty muszą się rozkładać rzeczywiście przypad-
kowo i równomiernie po całej powierzchni prostokąta. Gdyby punkty miały
tendencję do grupowania się na przykład w środku albo w prawym dolnym
rogu prostokąta – rezultat bardzo by odbiegał od rzeczywistości.
Po drugie, losowań musi być odpowiednio wiele. Statystyka mate-
matyczna pozwala oszacować, ile ich powinno być, by uzyskać założony
poziom wiarygodności wyniku. Generalnie, będzie on tym większy, im wię-
cej losowań wykonamy. Jeżeli ocenimy, że tysiąc losowań to zbyt mało, to
wykonamy ich dziesięć tysięcy, sto tysięcy czy może milion.
Jak jednak ten pomysł przekształcić w algorytm, a w konsekwencji
– w program komputerowy? Przede wszystkim, trzeba znaleźć sposób rów-
nomiernego losowania (przez komputer) przypadkowych liczb z zadanego
przedziału. Dwa takie losowania (jedno – z przedziału na osi czasu, drugie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
tej chwili już zawsze ciąg kolejnych liczb pseudolosowych byłby dokładnie
taki sam jak kiedyś. Przeczy to zasadzie „czystej” losowości.
Dlatego obecnie w praktyce stosuje się inne, bardzo sprawne algoryt-
my generowania liczb pseudolosowych. Operują one na liczbach dwójko-
wych (nie dziesiętnych, jak w powyższym przykładzie), lecz ich zasada jest
zbliżona do wyżej opisanej. „Ziarno” inicjuje proces deterministycznych,
cyklicznych obliczeń, w których w każdym kolejnym kroku wytwarza się
„za długą” liczbę i – jako wynik – bierze się jej umówioną część. Istnieją
także generatory liczb (naprawdę) przypadkowych (ang. random number
generators), w których wykorzystuje się zewnętrzne (w stosunku do kom-
putera) fizyczne zjawiska o rzeczywiście losowym charakterze (np. związa-
ne z promieniowaniem substancji radioaktywnych, szumem radiowym) lub
losowe zjawiska związane z ruchem w sieci komputerowej.
Pseudolosowość ma jednak również swoje zalety. Często zdarza się,
że program, wykorzystujący losowe wartości, stwarza – jak zresztą każ-
dy program – kłopoty na etapie jego tworzenia i uruchamiania. Bywa, że
zawiesza się, produkuje bezsensowny rezultat, wchodzi w nieprawidłową
ścieżkę itp. Twórca programu chciałby prześledzić powoli, krok po kroku,
jak do tego doszło, w którym momencie program podjął nieprawidłową de-
cyzję. Zadając takie samo ziarno, jak w poprzednim, nieudanym przebiegu
– programista może odtworzyć krok po kroku dokładnie takie same, jak po-
przednio, niby-losowe decyzje i zlokalizować błąd. Gdyby te decyzje były
prawdziwie losowe – prawdopodobieństwo powtórzenia się błędnej sekwen-
cji działań byłoby znikomo małe, a systematyczne zanalizowanie przyczyn
błędu – praktycznie niemożliwe.
Podany wyżej przykład całkowania funkcji jednej zmiennej (określa-
nia pola pod wykresem) w zadanym przedziale posłużył jedynie do zilustro-
wania samej zasady algorytmu probabilistycznego. Prawdę mówiąc, gdyby
rzeczywiście chodziło tylko o wyliczenie drogi, jaką samochód przebywa
w ciągu kilkudziesięciu minut jazdy – metoda polegająca na sumowaniu
prostokątów lub trapezów zadowoliłaby nas całkowicie. W tym przypadku
wystarczyłoby zapewne podzielić przedział czasu na zaledwie kilkadziesiąt
(może 50? Może 100?) wąskich odcinków i pola tyluż prostokątów obli-
czyć i dodać do siebie. Algorytm probabilistyczny, który wykonywałby
– powiedzmy – 10 000 losowań, nie dałby nam żadnej oszczędności czasu,
zwłaszcza że wyprodukowanie każdej liczby pseudolosowej też wymaga
operacji (na przykład podnoszenia do kwadratu) porównywalnej z oblicza-
niem pola każdego elementarnego prostokąta.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Algorytmy ewolucyjne
Spośród algorytmów probabilistycznych na szczególną uwagę zasługują al-
gorytmy ewolucyjne (lub genetyczne1, ang. evolutionary algorithms, genetic
algorithms), których działanie intrygująco (i nieprzypadkowo) przypomina
zasady, znane z obserwacji przyrody. Ich prostota, pomysłowość, uniwersal-
ność i jednocześnie skuteczność – powodują, że są one stosowane w wielu
praktycznych dziedzinach. Są też pojęciowo bardzo ciekawe i mogą skłonić
do głębszych refleksji, wykraczających daleko poza granice informatyki.
Podobnie, jak poprzednio, wesprzyjmy się konkretnym przykładem.
Potraktujmy go jako pretekst dla zrozumienia zasady działania algorytmu
ewolucyjnego, a nie jako rozwiązanie jakiegoś bardzo konkretnego pro-
blemu.
Powiedzmy więc, że mamy zadany zbiór kilkunastu punktów na
płaszczyźnie i chcemy dopasować linię krzywą, która by możliwie dokładnie
przechodziła przez te punkty. Ogólną postać takiej krzywej założymy z góry
w postaci równania, a rozwiązanie problemu będzie polegało na dobraniu
1 Między tymi dwoma terminami są pewne szczegółowe różnice, ale nie będziemy
się tu wdawać w ich analizowanie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
2π
y ( x) = a ⋅ sin ⋅ x + p ,
T
gdzie x jest zmienną niezależną, a jest amplitudą sinusoidy, T jej okresem, zaś
p – jej przesunięciem fazowym, to znaczy odległością najbliższego początku
okresu sinusoidy od punktu x = 0. Dla uproszczenia, zastąpmy czynnik
2π
T
przez k. Wielkość
2π
k= = 2πf
T
(gdzie f jest częstotliwością) jest nazywana pulsacją danej sinusoidy.
Przy takich oznaczeniach poszukiwana krzywa ma następującą ogól-
ną postać:
n
∑ ( yi − yv ( xi ))2
i =1
ϕ (v ) = ,
n
istnieją ich miliony. Konkurują one ze sobą lub współpracują. Zjadają się
wzajemnie, tworząc złożone łańcuchy pokarmowe. Dobór partnerów nie jest
losowy (choć przecież przypadek też odgrywa rolę), a u niektórych gatun-
ków jest kształtowany przez wyrafinowane reguły społeczne, jak np. rytuał
zalotów czy walki o przywództwo w stadzie. Taką listę oczywistych różnic
można by ciągnąć dowolnie długo. A jednak…
Jednak nawet nie wypowiadając się co do tego, czy tak się rzeczy-
wiście w przyrodzie dzieje, czy nie – musimy przyznać, że zastosowane
w algorytmach ewolucyjnych mechanizmy optymalizacji całej zbiorowo-
ści osobników są po prostu skuteczne. Choć w porównaniu z naturalnymi
uproszczone, niezdarne i prymitywne – opierają się na tym samym uniwer-
salnym cyklu rozmnażania i śmierci. Zasada dwupłciowego rozmnażania
(z niewielkim udziałem mutacji) powoduje, że potomstwo jest trochę po-
dobne, a trochę niepodobne do rodziców. Rodząc się, potomstwo ma pew-
ne przypadkowe właściwości, które podlegają weryfikacji dopiero później.
Udało się – to przeżyje, nie udało się – no cóż, trudno… Te reguły, choć nie
zakładają żadnego przemyślanego, celowego działania i są dla poszczegól-
nych jednostek dość bezwzględne – zapewniają całemu gatunkowi doskona-
lenie się, którego ostateczny cel nie jest osobnikom znany.
Nawet jeśli w naturze tak się nie dzieje – to mogłoby się tak dziać
i tłumaczyłoby wiele faktów, znanych z obserwacji przyrody.
A więc jak to w końcu jest: Bóg czy przypadek? Nie będziemy tutaj,
w książce o podstawach informatyki, zabierać głosu w sporze dotyczącym
najbardziej zasadniczych zagadnień filozofii i teologii. Ale może sprzecz-
ności w ogóle nie ma? Może to sam Stwórca, zamiast osobiście decydować
o długości nóżek każdego żuczka i barwie ogonka każdej pliszki – stwo-
rzył i uruchomił genialny, uniwersalny mechanizm oparty na rozmnażaniu,
śmierci i przypadku, który później, na przestrzeni miliardów lat „sam” wy-
produkował oszałamiające bogactwo różnych form życia? Może Pan Bóg
z zaciekawieniem i satysfakcją patrzy teraz na swoje dzieło, tak jak my pa-
trzyliśmy na rysunek 5.9, i tylko z rzadka interweniuje wtedy, kiedy zmia-
ny nie przebiegają zgodnie z tylko Jemu znanym celem, albo wtedy, gdy
zechce dać znak swojego Bożego działania?
Niech na takie pytania każdy odpowie sobie sam.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
1 Nie jest do końca rozstrzygnięte, czy pierwotnym autorem wynalazku był Gugliel-
mo Marconi, Nicola Tesla czy Aleksander Popow, choć to jednak Marconi otrzy-
mał w 1909 r. Nagrodę Nobla za wkład w rozwój radiokomunikacji.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
kilkanaście lat poszukiwano takiej uniwersalnej teorii, tak jak ongiś kamie-
nia filozoficznego, który miał każdy metal przemieniać w złoto.
Dziś, z perspektywy sześćdziesięciu lat, widać, że nadzieje na po-
wstanie jednej, wszechogarniającej supernauki nie spełniły się.
To prawda, że kilka podstawowych dla cybernetyki pojęć upowszech-
niło się i stanowi zestaw terminów, pozwalających na wygodny opis zja-
wisk, chociaż na dość wysokim poziomie abstrakcji. Wejście (input), wyj-
ście (output), sprzężenie zwrotne (feedback), czarna skrzynka (black box),
sterowanie (control), stabilność (stability) – to terminy używane dziś w wie-
lu dziedzinach wiedzy. To prawda, że każda z dziedzin, których integrację
przewidywał Wiener, przeżyły (i nadal przeżywają) okres niezwykłego roz-
woju, a niektóre z nich bardzo zbliżyły się do siebie. Jednak pozostała wiel-
ka różnorodność form opisu rzeczywistości, stosowanych matematycznych
formalizmów i kategorii pojęciowych. Współczesna teoria sterowania po-
sługuje się innym aparatem pojęciowym niż teoria automatów i lingwistyka
matematyczna. Współczesna telekomunikacja – innym niż neurologia; robo-
tyka – innym niż logika, socjologia czy ekonomia. Nie ma jednej, wspólnej
teorii, która obejmowałaby te wszystkie dziedziny wiedzy.
Wiener przewidywał, że wraz z cybernetyką na świecie zapanuje era
serwomechanizmów; my już wiemy, że druga połowa XX wieku (i nasze
dzisiejsze czasy) – to raczej era informatyki, komputerów i Internetu. To one
obecnie tworzą grunt, na którym przebiega integracja i współpraca między
różnymi dziedzinami wiedzy. O cybernetyce mówi się dziś znacznie mniej,
ale nadal działają instytuty, fundacje i stowarzyszenia, które mają ją w swej
nazwie. Zgodnie z wizją Wienera zajmują się one z zasady badaniami inter-
dyscyplinarnymi, których celem jest zbliżenie metod i pojęć stosowanych
w pozornie odległych dziedzinach, takich jak biologia, technika, medycyna
i nauki społeczne.
Tak więc z powodów, które staraliśmy się wyżej przedstawić, metody
analogowe (i urządzenia na nich oparte) rzeczywiście ustępują na naszych
oczach pola technikom cyfrowym i komputerom. Jednak nie znikły cał-
kiem z nauki i techniki. Są wciąż wykorzystywane w urządzeniach bardzo
prostych (choć zapewne wkrótce nie tylko termostat w naszej lodówce, ale
również spłuczka klozetowa będą obsługiwane przez cyfrowy mikroproce-
sorowy sterownik połączony z Internetem) i przeciwnie, w działach techniki
wymagających najbardziej zaawansowanych obliczeń.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
1 Przykładem sygnału, który jest z natury dyskretny w czasie, może być ciąg dzien-
nych notowań kursu złotego w stosunku do euro, mierzonych w chwili zamknię-
cia sesji giełdowej. On również niesie informację, niekiedy o bardzo istotnych
skutkach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
6, 7, 2, 2, 2, 4, 5, 6, 6, …
5 Przeciwko temu faktowi burzy się intuicja wielu ludzi. Jak to prawdopodobień-
stwo równe zeru? Przecież w końcu zawsze wylosujemy jednak jakiś pojedynczy
punkt? Dla uspokojenia intuicji warto sobie uświadomić, że jeśli pewne zdarzenie
ma prawdopodobieństwo równe zeru, to nie znaczy jeszcze, że jego wystąpienie
jest niemożliwe.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
6 Wyrazy podziękowania należą się prof. Jerzemu Szabatinowi, który dostarczył ten
przykład i w zasadniczy sposób przyczynił się do gruntownego merytorycznego
uporządkowania treści tego rozdziału.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
f p ≥ 2 f max .
Innymi słowy, okres próbkowania tp powinien być nie dłuższy niż po-
łowa okresu sinusoidy o owej największej częstotliwości:
1
tp ≤ .
2 ⋅ f max
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Jeśli ten warunek jest spełniony, to z tak otrzymanego ciągu próbek można
zrekonstruować pierwotny sygnał bez żadnej straty, którą mógłby spowodo-
wać proces dyskretyzacji w czasie. Oznacza to, że na podstawie znajomo-
ści próbek sygnału, pobieranych nie rzadziej niż co czas 1/2f max, jesteśmy
w stanie wyznaczyć z pełną dokładnością wartości sygnału we wszystkich
chwilach pomiędzy chwilami próbkowania. Jest to bardzo mocne twierdze-
nie, stanowiące swoisty pomost między światem analogowym a światem
cyfrowym.
Przypomnijmy fragment B przebiegu w czasie, pokazanego na ry-
sunku 7.1. Jeżeli sygnał wykonuje taką szybką ewolucję w górę i w dół,
to znaczy, że wśród jego składowych sinusoid znajduje się też i taka, która
zmienia się w ten właśnie sposób. Może to ona ma ze wszystkich składo-
wych największą częstotliwość f max? Okres próbkowania jest tu ewidentnie
źle dobrany: powinien być znacznie krótszy, tak by na przestrzeni jedne-
go okresu tej najszybciej zmiennej sinusoidy wystąpiły co najmniej dwie
próbki.
Jak można wykorzystać ten rezultat przy planowaniu analogowo-cy-
frowej konwersji np. nagrań dźwiękowych? Wiadomo, że ludzkie ucho sły-
szy drgania o częstotliwościach z zakresu od około 20 Hz do 20 kHz. Jeśli
w pewnym nagraniu występują składowe sinusoidalne o częstotliwościach
wyższych niż owe 20 kHz, to my ich po prostu nie usłyszymy. Dlatego,
dla nagrań audio można przyjąć, że fmax = 20 kHz, a częstotliwości wyż-
sze – pominąć. Wtedy – zgodnie z twierdzeniem o próbkowaniu – należy
próbkować sygnał z częstotliwością co najmniej 40 kHz, a więc rejestrować
jego wartości chwilowe częściej niż 40 000 razy na sekundę7. Nie powinno
więc nas dziwić, że standard nagrań CD określa częstość próbkowania na
44,1 kHz8. Oczywiście, dla sygnałów i urządzeń ultradźwiękowych, tele-
wizyjnych, radarowych itp. niezbędna częstość próbkowania będzie odpo-
wiednio większa.
7 Być może, nasz pies czy kot (nie mówiąc już o naszym nietoperzu) nie jest za-
dowolony z jakości sygnału dźwiękowego odtworzonego z takiego ciągu próbek.
Nie dla nich jednak budujemy cyfrowe odtwarzacze audio. Jak im się nie podoba
– niech sobie zbudują własne.
8 Ta akurat, konkretna wartość wynika dodatkowo z pewnych technicznych wła-
ściwości urządzeń do nagrywania analogowych telewizyjnych taśm wideo, które
początkowo służyły do rejestracji cyfrowych nagrań audio.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
9 Taki sam efekt uzyskamy, skanując np. starą, analogową fotografię i poddając
otrzymany sygnał konwersji analogowo-cyfrowej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
8. Maszyna Turinga
2 Zauważmy, że skoro alfabet ma zawierać nie mniej niż dwa symbole, to znaczy,
że oprócz # w alfabecie A musi być co najmniej jeszcze jeden inny symbol. U nas
potrzebne są jeszcze dwa, gdyż tak wynika z warunków zadania.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
jedna (albo nawet obie) początkowe liczby mają wartość zero4. Popatrzmy
więc z większym szacunkiem na graf z rysunku 8.3. Jeśli ktoś go wskaże
i zapyta, co to jest – mamy prawo odpowiedzieć, że to jest właśnie całe
dodawanie liczb naturalnych.
Od tej chwili ze spokojnym sumieniem możemy stwierdzić, że doda-
wanie liczb naturalnych daje się wykonać (jest wykonywalne) przez maszynę.
Możemy całemu temu obliczeniu przypisać symbol „+” i używać go później
w opisach algorytmów. Odtąd, ilekroć w opisie algorytmu pojawi się wyraże-
nie typu x:=a+b (gdzie a i b są liczbami naturalnymi) – wszystko będzie ja-
sne: jest to wywołanie dobrze zdefiniowanej, wykonalnej funkcji obliczalnej
o nazwie +, która otrzymawszy wejściowe argumenty a i b, zwróci wynik x.
Zastosowany tu jedynkowy (czy też „patyczkowy”) sposób zapisy-
wania liczb nie jest jednak wygodny. Czy maszyna nie mogłaby zakomuni-
kować nam wyniku dodawania w sposób, do którego się przyzwyczailiśmy,
na przykład w postaci konwencjonalnej liczby dziesiętnej? Ależ oczywiście,
proszę bardzo. Skonstruujmy maszynę, która otrzymywałaby jako daną po-
czątkową ciąg jedynek o dowolnej długości (właśnie taki, jaki jest wyni-
kiem opisanego wyżej dodawania) i potrafiłaby napisać, ile tych jedynek
jest, posługując się cyframi arabskimi, zgodnie z konwencjonalnym sposo-
bem zapisywania liczb dziesiętnych.
Alfabet tej maszyny stanowi zbiór {#, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}.
Podobnie jak poprzednio symbol # oznacza „puste”, a symbol 1 wykorzystamy
zarówno w jedynkowej postaci liczby, jak w jej dziesiętnym odpowiedniku. Na
początku, na taśmie jest ciąg jedynek, a głowica jest ustawiona na pierwszej
jedynce z lewej strony. Na lewo od tego ciągu pozostawimy jedną komórkę
pustą (jako separator), a jeszcze dalej w lewo utworzymy licznik dziesiętny
(rysunek 8.4). Początkowo, nic w nim nie ma: jest on tylko umownie zarezer-
wowanym na licznik dziesiętny obszarem pustych komórek taśmy.
Graf sterowania pokazany jest również na rysunku 8.4. Wszystko za-
czyna się od stanu początkowego 1. Jeśli zadany ciąg jest pusty – głowica
Fot. 8.5. Alan Mathison Turing (1912–1954). Zdjęcie ze zbioru National Portrait Gallery,
Wielka Brytania
z tych praktycznych wynalazków nie stał się podstawą tak ważnego uogól-
nienia, jak prościutka i abstrakcyjna maszyna Turinga.
Oczywiście nigdy nie będziemy wiedzieli na pewno, dlaczego właśnie
Alanowi Turingowi udało się skonstruować tak przejrzysty model obliczeń
cyfrowych. Warto jednak zauważyć, że wspomniani poprzednicy Turinga
pojęcie obliczenia kojarzyli prawie wyłącznie z liczbami, i to dziesiętnymi:
z ich dodawaniem, odejmowaniem, mnożeniem, dzieleniem, potęgowaniem
itd. Zadaniem budowanych przez nich maszyn było głównie uwolnienie
człowieka od konieczności nudnego, powtarzalnego wykonywania tysięcy
takich właśnie operacji arytmetycznych. Było to ważne, gdyż zajmowały
one w praktyce mnóstwo czasu każdego inżyniera, statystyka, księgowego
czy nawigatora, a błędy, spowodowane zwykłym ludzkim znużeniem, mie-
wały fatalne skutki.
Tymczasem Alan Turing był – między innymi – wybitnym specjali-
stą od kryptografii: dziedziny matematyki zajmującej się teorią i praktyką
skutecznego szyfrowania i odszyfrowywania tajnych informacji. Arytmetyki
i liczb dziesiętnych tam nie za wiele, są za to sekwencje nienumerycznych
symboli (choćby były one ukryte pod postacią dziesiętnych cyfr), które trze-
ba przepisywać, zamieniać, kodować z jednego alfabetu w inny. Być może
właśnie taka natura danych kryptograficznych skierowała myśl uczonego od
dziesiętnej arytmetyki w stronę bardziej uniwersalnych zasad przetwarzania.
Czy tak było, czy nie – możemy jedynie spekulować.
Warto dodać, że życie Alana Turinga przeczy stereotypowym wyobra-
żeniom o postaci teoretyka, roztargnionego matematyka, gubiącego paraso-
le, zatopionego w swoich abstrakcjach i świata spoza nich niewidzącego.
Jako wybitny kryptolog, Alan Turing współpracował już w latach poprze-
dzających wybuch II wojny światowej z najtajniejszą komórką brytyjskiego
wywiadu, zajmującą się deszyfrowaniem komunikatów kodowanych za po-
mocą niemieckiej wojskowej maszyny szyfrującej Enigma. W czasie wojny,
Turing był nawet przez pewien czas naukowym kierownikiem (a później
głównym konsultantem) ośrodka kryptologicznego w Bletchley Park, miej-
scu tak tajnym, że nie było go w ogóle na mapach. Działalność ośrodka była
otoczona tajemnicą aż do lat siedemdziesiątych XX wieku, a więc jeszcze
przez trzydzieści lat po zakończeniu wojny.
Dziś wiemy (co z pewnym ociąganiem przyznali także sami Brytyj-
czycy), że jako pierwsi zasady kodu Enigmy złamali na długo przed wybu-
chem II wojny światowej trzej polscy matematycy: Marian Rejewski, Jerzy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
9. O lingwistyce matematycznej
zyku. Razem tworzą one język polski i rzeczywiście jest on częścią (czyli
podzbiorem) zbioru A*. Poza nim, w zbiorze A* znajdują się oczywiście
takie sekwencje, które do języka polskiego nie należą.
Tak zdefiniowany język polski zawiera nieskończenie wiele sekwen-
cji, jest więc nieskończonym podzbiorem zbioru A*. Sekwencji nienależą-
cych do języka polskiego (ale należących do A*) jest również nieskończenie
wiele. Nie ma w tym oczywiście żadnej sprzeczności: w nieskończonym
zbiorze może kryć się wiele nieskończonych podzbiorów1.
Inny język, na przykład angielski, jest językiem nad tym samym kil-
kudziesięcioznakowym alfabetem, który znamy z klawiatury, tyle, że jest to
inny podzbiór tego zbioru A*. Nie jest on rozłączny z językiem polskim. Są
ciągi znaków, które (jak np. car, most, brat itd.) należą do obu podzbio-
rów: polskiego i angielskiego, choć różnią się znaczeniem. Podobnie, znane
w informatyce języki programowania i języki opisu struktur danych (jak np.
C, C++, Pascal, Java, PHP, HTML, XML...) – to też różne (i wzajemnie nie
rozłączne) podzbiory tego samego A*.
Tak więc każdy podzbiór A* jest jakimś językiem. Niektóre z nich
potrafimy zidentyfikować i nazwać, ale ogromna większość spośród pod-
zbiorów A* nie ma swoistych nazw, bo nie były one widocznie do tej pory
nikomu potrzebne. Jednak to kwestia wyłącznie konwencji. Na przykład
podzbiór A*, zawierający wszystkie takie sekwencje, które nie należą do ję-
zyka polskiego, jest też językiem nad A. Moglibyśmy go nazwać, powiedz-
my, językiem niepolskim. Podobnie, wolno nam wybrać własny podzbiór
A* (skończony lub nie), z jakiegoś powodu wygodny do naszych celów,
i będzie on również jakimś językiem nad alfabetem A.
Taka definicja języka zapewne nie wzbudzi entuzjazmu u osób, które
podchodzą do języka w sposób emocjonalny. Rzeczywiście, przy takim for-
malnym podejściu nawet tak naładowane emocjami słowa, jak mama czy dom,
tak pełne skojarzeń frazy, jak Litwo, ojczyzno moja albo Stoi na stacji
lokomotywa – stają się bezdusznymi, zimnymi sekwencjami znaków, wcale
nie ważniejszymi czy ładniejszymi niż jakiekolwiek inne. Jednak zabieg po-
G = <S, T, P, s0 >;
4 Zauważmy przy okazji często używaną sztuczkę: użycie znaku podkreślenia za-
miast spacji, dla zachowania konwencji, że symbol należący do S nie zawiera
spacji w środku.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
<zdanie>
Następnie wyszukajmy w zbiorze podstawień P jakąkolwiek regułę, która
pozwoliłaby na zastąpienie tego symbolu przez jakąś inną konstrukcję.
W naszej gramatyce mamy dwie takie możliwości, zapisane w pierw-
szym wierszu zbioru P. Możemy więc zastosować albo podstawienie
<zdanie> ::= <zdanie_proste>, albo <zdanie> ::= <zdanie_złożone>.
Wybór należy do nas, obie możliwości są całkowicie równouprawnione.
Powiedzmy, że zastosujemy pierwszą z nich, tak więc na kartce dopisujemy
drugi wiersz:
<zdanie>
<zdanie_proste>
I znów poszukujemy w zbiorze reguł P takiego podstawienia, które dałoby
się w tej sytuacji zastosować. Tym razem wyboru nie ma: <zdanie_proste>
występuje po lewej stronie tylko jednego podstawienia. Na naszej kartce
pojawia się więc wiersz trzeci:
<zdanie>
<zdanie_proste>
<podmiot> <orzeczenie> <dopełnienie>
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
<zdanie>
<zdanie_proste>
<podmiot> <orzeczenie> <dopełnienie>
<podmiot> <czasownik> <dopełnienie>
Skoncentrujmy się raz jeszcze na środkowym symbolu (choć moglibyśmy
zająć się dowolnym innym). Przewidziano dla niego cztery możliwe pro-
dukcje, zapisane w przedostatnim wierszu zbioru P. Znów każda z nich jest
tak samo uprawniona, a my wybierzmy, dla przykładu, pierwszą z nich.
Dokonujemy podstawienia, pozostałe symbole zostawiamy bez zmian i do-
pisujemy wiersz piąty:
<zdanie>
<zdanie_proste>
<podmiot> <orzeczenie> <dopełnienie>
<podmiot> <czasownik> <dopełnienie>
<podmiot> lubią <dopełnienie>
Teraz sytuacja się nieco zmieniła. Zauważmy, że w żadnym z podstawień
należących do zbioru P, po lewej stronie metasymbolu podstawienia (::=)
nie ma symbolu lubią. Co więcej, żaden symbol końcowy, a więc należący
do zbioru T, nie stoi po lewej stronie żadnej produkcji w zbiorze P. To nie
przypadek: „końcowość” symboli terminalnych polega właśnie na tym, że
żadnego z nich nie można już zastąpić czymś innym. Jego pojawienie się
w ciągu symboli oznacza, że na tej pozycji proces podstawiania się już za-
kończył, a my musimy zająć się pozostałymi elementami ciągu. Zabieramy
się więc za <podmiot> lub <dopełnienie>.
Łatwo sobie wyobrazić dalszy ciąg procesu tworzenia gramatycznie
poprawnego zdania. Mógłby on, na przykład, wyglądać następująco:
<zdanie>
<zdanie_proste>
<podmiot> <orzeczenie> <dopełnienie>
<podmiot> <czasownik> <dopełnienie>
<podmiot> lubią <dopełnienie>
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
1. <liczba_całkowita> ::= 0;
2. <liczba_całkowita> ::= <liczba_naturalna>;
3. <liczba_całkowita> ::= <znak><liczba_naturalna>;
4. <znak> ::= +|-;
5. <liczba_naturalna> ::= <cyfra_niezerowa>;
6. <liczba_naturalna> ::= <liczba_naturalna> <cyfra>;
7. <cyfra> ::= 0|<cyfra_niezerowa>;
8. <cyfra_niezerowa> ::= 1|2|3|4|5|6|7|8|9;
<liczba_całkowita>
<liczba_naturalna>
<liczba_naturalna><cyfra>
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
<liczba_całkowita>
<liczba_naturalna>
<liczba_naturalna><cyfra>
<liczba_naturalna><cyfra><cyfra>
<liczba_naturalna><cyfra><cyfra><cyfra>
<liczba_naturalna><cyfra><cyfra><cyfra><cyfra>
<liczba_naturalna><cyfra><cyfra><cyfra><cyfra><cyfra>
<cyfra_niezerowa><cyfra><cyfra><cyfra><cyfra><cyfra>
Dalszy ciąg procesu generowania liczby całkowitej można sobie łatwo wy-
obrazić. Dokonując wyborów spośród podstawień zapisanych w wierszach
7 i 8, szybko otrzymamy konwencjonalną sześciocyfrową, dziesiętną liczbę
całkowitą, na przykład taką jak 123 456 lub 320 089. Wystarczy popatrzeć
na ostatni wiersz na naszej kartce, by zauważyć, że żadna z nich na pewno
nie rozpoczyna się od zera, choć na dalszych pozycjach zera są dopuszczal-
ne. Różnych liczb o tej budowie jest, oczywiście, 900 000. Gdybyśmy na
początku procesu generowania posłużyli się regułą 3 – liczba mogłaby być
jeszcze poprzedzona znakiem (+ albo -).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
x1+201*+y2
i twierdzi, że jest to „wyrażenie” zgodne z podaną wyżej gramatyką. Robimy
sobie roboczą kopię tego napisu, by można było po niej bezkarnie mazać,
nie psując oryginału – i przystępujemy do analizy.
Najpierw postaramy się wyróżnić kolejne jednostki leksykalne i zba-
dać ich zgodność z regułami. Jak to zrobić? Jeżeli jakiś ciąg symboli ma być
jednostką leksykalną, to składa się wyłącznie z symboli końcowych, zde-
finiowanych na tym (leksykalnym) poziomie. W naszym przykładzie są to
litery, cyfry oraz operatory (+, -, *, /).
Zaczynamy więc przeglądać cały ciąg, jadąc od lewej strony do pra-
wej. Po dwóch pierwszych symbolach z poziomu leksykalnego (x oraz 1)
natrafiamy na znak +, który należy już do innej grupy symboli leksykalnych.
Znaczy to, że x1 jest prawdopodobnie jakąś jednostką leksykalną. Ponieważ
zaczyna się od litery, jest to prawdopodobnie zmienna. Zaglądamy do zbio-
ru reguł leksykalnych i stwierdzamy, że x1 jest to rzeczywiście leksykalnie
poprawna zmienna. Zastępujemy w naszej roboczej kopii już sprawdzony
fragment nowym, umówionym symbolem końcowym, np. zmienna (bez na-
wiasów kątowych!). Będzie on oznaczał właśnie to, że w tym miejscu wy-
stępuje już sprawdzona, poprawna zmienna.
zmienna+201*+y2
Posuwamy się dalej w prawo. Znak + łatwo rozpoznajemy jako po-
prawny operator. Zastępujemy go więc nowym symbolem końcowym ope-
rator (znów bez nawiasów kątowych!). Otrzymujemy następujący ciąg:
się odnosi, a klęskę – ponosi. Nie powinno się mówić (ani pisać), że ktoś
poniósł zwycięstwo lub odniósł klęskę; tak więc jednemu czasownikowi
wolno przebywać w bezpośrednim lewostronnym kontekście z jednym,
a innemu – z drugim rzeczownikiem. Takich przykładów można podać ty-
siące i łatwo jest wyobrazić sobie, jak wielką i żmudną pracą byłoby spi-
sanie ich wszystkich w postaci zbioru odpowiednich kontekstowych reguł
podstawiania.
Co więcej, samo pojęcie kontekstu skłonni jesteśmy rozumieć znacz-
nie szerzej. Na poprawność lub niepoprawność użycia takiego czy innego
słowa lub większej konstrukcji językowej wpływa nie tylko dosłowny kon-
tekst (a więc „tekst stojący tuż obok”), ale kontekst nienapisany, bardziej
abstrakcyjny: emocjonalny, sytuacyjny, czy wręcz kulturowy, którego próż-
no by szukać mechanicznie, w ciągu znaków, z lewej lub prawej strony ana-
lizowanego fragmentu.
Co gorsza, zarówno słownik naturalnego języka, jak jego gramatycz-
ne reguły zmieniają się w czasie. Nawet w oficjalnym, literackim języku
pewne formy odchodzą do przeszłości stając się archaizmami, a powstają
nowe słowa i zwroty, które często (zazwyczaj po pewnym okresie sporów
i wahań) same stają się językową normą. W języku potocznym (nie mówiąc
już na przykład o gwarze młodzieżowej czy slangu informatyków) ta zmien-
ność jest jeszcze gwałtowniejsza: każdego dnia wchodzą w użycie nowe
słowa i zwroty, powstają nowe idiomy, znane określenia nabierają nowych
znaczeń, a niektóre dotychczasowe – z dnia na dzień stają się niemodne
czy wręcz obciachowe. Próba pełnego skodyfikowania – w formalny spo-
sób – całej gramatyki języka naturalnego byłaby więc pracą niekończącą się
i prawdziwie syzyfową.
Ponadto reguły językowe zmieniają się w zależności od sytuacji.
Inaczej wypowiadamy się gniewnie, inaczej – czule, inaczej konstruujemy
zdania, gdy piszemy instrukcję posługiwania się młotkiem, a inaczej – gdy
wiersz lub sentymentalne opowiadanie. Inaczej mówimy w oficjalnych wy-
stąpieniach dyplomatycznych, a inaczej – z kolegami przy piwie czy w roz-
mowie z małym dzieckiem. Mówi się wręcz, że język naturalny, obok skład-
ni i semantyki ma też swoją pragmatykę, czyli zasady poprawnego użycia
w różnych sytuacjach. To kolejna trudność w formalizacji języka.
Z tych – między innymi – powodów algorytmiczne tłumaczenia
z jednego języka naturalnego na drugi (na przykład z angielskiego na pol-
ski), oferowane dziś przez liczne serwisy internetowe – są rozpaczliwie nie-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Intelekt i lewicowość
Na oddzielną wzmiankę zasługuje też sam intelektualny ojciec lingwistyki
matematycznej, Noam Chomsky. Wybitny amerykański uczony, urodzony
w roku 1928 jako syn żydowskich emigrantów z Ukrainy, od prawie 60 lat
wciąż aktywny profesor sławnego bostońskiego Massachusetts Institute of
Technology, jest od lat zaliczany do wąskiego grona kilku intelektualistów,
uznawanych za najważniejszych dla całej współczesnej światowej nauki.
Jego znaczenie wynika nie tylko z liczby prac naukowych, które opubliko-
wał, czy niezliczonych nagród i honorowych tytułów, które odebrał w ciągu
swojego długiego życia.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Fot. 9.1. Noam Chomsky w domu w Cape Cod, USA. Zdjęcie otrzymane dzięki
uprzejmości rodziny Noama Chomsky’ego
mentarny rodzaj pamięci. Dzięki temu, automat może np. reagować w różny
sposób (w różnych miejscach sekwencji) na ten sam symbol wejściowy.
Matematyk powie, że aby w pełni zdefiniować automat skończony,
trzeba określić następujące zbiory i relacje:
– dwa alfabety: A (alfabet wejściowy) oraz B (alfabet wyjściowy). Jak
wszystkie alfabety, są one skończonymi, co najmniej dwuelementowymi
zbiorami elementarnych symboli, przy czym tu są one rozłączne, to zna-
czy nie zawierają żadnych symboli wspólnych;
– skończony zbiór S stanów automatu. Automat jest skończony właśnie dla-
tego, że skończony jest jego zbiór stanów;
– stan początkowy s0, który jest oczywiście jednym ze stanów (s0 ∈ S);
– zbiór przejść między stanami. Odpowiadają one strzałkom w grafie, a for-
malnie ich zbiór można zdefiniować jako relację next ⊆ S × S, co objaśni-
my za chwilę;
– funkcję wyjścia out, która każdemu przejściu i symbolowi wejściowemu
przypisze jeden z symboli alfabetu B. Formalnie zapiszemy ją jako out:
next × A → B.
Aby oswoić się z tą matematyczną notacją, zauważmy, że chcąc zde-
finiować strzałkę (przejście) między dwoma stanami, trzeba wymienić parę
stanów (s, s’), gdzie s jest stanem aktualnym, zaś s’ – stanem następnym.
Przypomnijmy także, że znaczek × symbolizuje iloczyn kartezjański dwóch
zbiorów. Na przykład P × Q byłby to zbiór wszystkich możliwych par o po-
staci (p, q) takich, że p należy do zbioru P i q należy do Q. Oczywiście, ilo-
czyn kartezjański S × S jest więc zbiorem wszystkich możliwych par (s, s’)
takich, że zarówno s, jak s’ są stanami.
Sformułowanie, że next ⊆ S × S oznacza, że next jest podzbiorem tego
iloczynu kartezjańskiego, a być może zawiera cały ten iloczyn. Taki mate-
matyczny twór nazywa się relacją. W tym przypadku next jest więc relacją
następnego stanu albo inaczej – relacją przejść.
To – wydawałoby się – nieprzyjazne matematyczne określenie ma
bardzo prostą interpretację. W przykładzie z rysunku 10.1 zbiór stanów jest
czteroelementowy: S = {1, 2, 3, 4}. Iloczyn kartezjański S × S zawiera więc
w tym przypadku 16 par. Choć to nudne, wypiszmy je wszystkie:
S × S = {(1,1), (1,2),(1,3), (1,4), (2,1), (2,2), (2,3), (2,4), (3,1), (3,2), (3,3),
(3,4), (4,1), (4,2), (4,3), (4,4)}.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Dwa pokazane na rysunku 10.3 automaty mają takie same alfabety: wej-
ściowy A = {a, b} oraz wyjściowy B = {x, y}. Poza tym – na pierwszy rzut
oka trudno się doszukać między nimi jakiegokolwiek podobieństwa. Mają
różne zbiory różnie nazywających się stanów, różne relacje przejścia, różne
funkcje wyjścia… Jednak oba reagują identyczną sekwencją wyjściową na
dowolną taśmę wejściową nad wspólnym alfabetem A.
Można to udowodnić, a także podać, jak w systematyczny, algoryt-
miczny sposób można dla zadanego dowolnego automatu Moore’a wyzna-
czyć równoważny mu automat Mealy’ego i odwrotnie. To nawet niezbyt
skomplikowane, ale nie będziemy tutaj tego omawiać. Dla potrzeb niniej-
szego wykładu wystarczyłoby, gdybyśmy się o równoważności obu auto-
matów upewnili na kilku przykładach, na przykład ponownie zabawiając
się w przestawianie pionków po obu grafach. Oczywiście, nie sprawdzimy
w ten sposób wszystkich taśm nad alfabetem A, bo jest ich nieskończenie
wiele, ale takie doświadczenie byłoby pouczające.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Symbole wejściowe
a b
1 1, x 2, y
2 4, y 1, x
Stan aktualny
3 1, x 2, y
4 3, x 3, x
Interpretacja obu tych tabel automatu Moore’a jest oczywista. Tablica przejść
określa następny stan, do którego automat przechodzi, jeśli w aktualnym
stanie odpowiadającym wierszowi otrzyma symbol wejściowy odpowiada-
jący kolumnie. Tabela wyjść mówi, jakie symbole wyjściowe odpowiadają
stanom, wypisanym w poszczególnych wierszach.
Z tabelkami tego typu będziemy wielokrotnie mieli do czynienia
w następnych rozdziałach tej książki.
silnie, tworząc bardzo już dziś rozbudowany i ważny dział matematyki i in-
formatyki.
Aby ujawnić choć mały skrawek tej dziedziny wiedzy, pokażemy, jak
można wykorzystać automaty skończone do rozpoznawania poprawności
składniowej konstrukcji językowych zgodnych z regułami gramatycznymi
pewnej klasy języków, zwanych językami regularnymi. Posłużymy się przy-
kładem i nie będziemy tu formalnie definiować tej klasy języków. Powiemy
jedynie, że języki regularne stoją nisko w Chomsky’ego hierarchii języków.
Wszystkie języki skończone są regularne, ale oprócz nich wśród języków
regularnych są również języki nieskończone. Z drugiej strony, wszystkie ję-
zyki regularne są bezkontekstowe, ale są takie języki bezkontekstowe, które
nie są regularne.
Przykład powszechnie znanej i często stosowanej reguły, którą potra-
fimy wyrazić w języku bezkontekstowym, ale która (w ogólnej postaci) nie
daje się wyrazić w gramatyce regularnej – podamy na końcu podrozdziału.
Najpierw jednak zajmijmy się prostym przykładem języka regularnego.
W poprzednim rozdziale, przy okazji omawiania zasad analizy lek-
sykalnej i składniowej, zdefiniowaliśmy przykładową gramatykę „wyrażeń”
i zadaliśmy sobie pytanie, czy taki na przykład ciąg znaków:
x1+201*+y2
jest poprawnym (w świetle tej gramatyki) wyrażeniem. Potem przeprowa-
dziliśmy analizę leksykalną, z której wynikło, że wszystkie występujące
w zadanym ciągu jednostki leksykalne (liczby oraz zmienne) są leksy-
kalnie poprawne. Zastąpiliśmy je więc nowymi symbolami końcowymi
liczba, zmienna oraz operator, a przykładowy ciąg przybrał następującą
postać:
zmienna operator liczba operator operator zmienna
W tym momencie stanęliśmy przed pytaniem, czy ta sekwencja znaków jest
zgodna z regułami poziomu składniowego. Jest on opisywany następującą
prostą gramatyką:
<wyrażenie> ::= <czynnik> | <wyrażenie> operator <wyrażenie> ;
<czynnik> ::= zmienna | liczba ;
Aby pokazać, jak automat skończony może rozstrzygać o składniowej po-
prawności lub niepoprawności takiej sekwencji, wprowadźmy nieco zmody-
fikowany model automatu, zwany automatem Rabina–Scotta.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Warto raz jeszcze zabawić się w przestawianie pionka po grafie tego auto-
matu, aby przekonać się, jak poradzi on sobie z analizą poprawności przy-
toczonego poprzednio ciągu:
zmienna operator liczba operator operator zmienna
Już na samym początku takiego doświadczenia uświadomimy sobie, że au-
tomat nic „nie wie” o przyszłości tego, co otrzyma na wejściu. Nie wie, czy
dany symbol wejściowy jest już ostatni w tym ciągu, czy też pojawią się
jeszcze następne. Decyzję o akceptowaniu (lub nie) podejmuje więc wyłącz-
nie na podstawie tego, co zdarzyło się do tej pory.
Tak więc automat, odczytawszy pierwszy symbol (zmienna), prze-
chodzi do stanu 2 i akceptuje dotychczasowy ciąg: istotnie sama zmienna
jest poprawnym wyrażeniem. Odczytawszy następny symbol (operator),
wraca do (nieakceptującego) stanu 1, bo sekwencja zmienna operator nie
jest poprawnym wyrażeniem. Ale po następnym znaku wraca do akceptacji:
ciąg zmienna operator liczba znów jest poprawny. Po kolejnym symbolu
(jest to operator) znowu znajdzie się w stanie 1. Gdyby potem nastąpiła
zmienna albo liczba – sytuacja powtórzyłaby się. Automat mógłby tak krą-
żyć między stanami 1 i 2 dowolną liczbę razy.
Teraz jednak odczytał symbol operator, co kieruje go do nieakcep-
tującego stanu 3. Stąd już nie ma wyjścia: od tej chwili automat pozostanie
w tym nieakceptującym stanie niezależnie od tego, ile symboli (i jakich) po-
jawi się na jego wejściu. Zapewnia to przejście od stanu 3 z powrotem do 3,
które wykonuje się dla każdego symbolu z alfabetu wejściowego. Pojawienie
się dwóch operatorów tuż obok siebie nieodwołalnie „zepsuło” wejściowy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
ciąg. Choćby potem nastąpiło i sto tysięcy innych symboli – nie naprawi się
ona już nigdy i nie będzie zasługiwała na akceptację.
Nieakceptujący stan 4, też wyposażony w takie „ucho”, również
sygnalizuje błąd, który może pojawić się w innych miejscach sekwencji.
W gruncie rzeczy moglibyśmy zastąpić stany 3 i 4 jednym stanem, jednak
ich rozdzielenie ma ważne znaczenie diagnostyczne: pozwala zidentyfiko-
wać pierwszy wykryty błąd. Jeśli na przykład po analizie długiego ciągu
symboli stwierdzimy, że automat zatrzymał się w stanie 3, to znaczy, że
badana sekwencja – niezgodnie z gramatyką – rozpoczynała się od operato-
ra lub że dwa operatory wystąpiły obok siebie. Jeśli zatrzymał się w stanie
4 – to znaczy, że jakieś dwa czynniki nie były przedzielone operatorem.
Każda taka informacja ułatwia życie komuś, kto ma taki składniowy błąd
zlokalizować i poprawić.
Niech Czytelnik teraz sam przygotuje – dla ćwiczenia – automat
Rabina–Scotta, który rozpoznaje poprawność „wyrażeń”, zgodnych z pełną
wersją gramatyki zdefiniowanej w rozdziale 9, bez jej podziału na poziom
leksykalny i składniowy. W tym przypadku alfabet wejściowy automatu bę-
dzie zawierał wszystkie symbole terminalne tej gramatyki, to znaczy będzie
miał następującą postać:
A = {+, – , *, /, a, b, …, z, 0, 1, 2, …, 9}.
Niech – dla przykładu – zostanie zbadana poprawność znanego nam już
ciągu:
x1+201*+y2
Pomyśleć będzie trzeba, ale rozwiązanie nie okaże się wcale skomplikowa-
ne.
Na początku tego podrozdziału wspomnieliśmy o tym, że automaty
skończone rozpoznają poprawność sekwencji należących do języków re-
gularnych, ale języki te są jedynie pewną ograniczoną klasą języków bez-
kontekstowych. Niektórych reguł gramatycznych, które dają się wyrazić
w gramatyce bezkontekstowej – nie można wyrazić w ramach gramatyki
regularnej, nie można więc użyć automatu skończonego do sprawdzania po-
prawności takich konstrukcji językowych.
Przykładem może być powszechnie stosowana, konwencjonalna re-
guła posługiwania się nawiasami. Przewiduje ona, że nawiasy mogą się
zagnieżdżać („nawiasy w nawiasach”), ale każdy nawias otwierający musi
mieć „do pary” odpowiadający mu nawias zamykający.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
ciskając guma albo baton określić, co chce otrzymać. Jeżeli się rozmyśli
– może nacisnąć anuluj, a otrzyma zwrot pieniędzy. Nie powinien jednak
naciskać dwóch ani trzech przycisków jednocześnie.
Automat sterujący całą maszynerią ma więc skończony alfabet wej-
ściowy, składający się z następujących czterech zdarzeń:
– złotówka – odpowiadający wrzuceniu jednej monety do szczeliny,
– guma – odpowiadający naciśnięciu klawisza z decyzją „daj gumę”,
– baton – oznaczający decyzję „daj batonik”,
– anuluj – oznaczający wycofanie się z transakcji i żądanie zwrotu pienię-
dzy.
Załóżmy, że automat sterujący produkuje na swym wyjściu sygna-
ły trzech rodzajów. Dla odróżnienia, będziemy je pisali wielkimi literami.
Sygnał GUMA jest skierowany do wewnętrznego podzespołu obsługującego
zasobnik z gumami do żucia. Zapewne sygnał GUMA, kiedy się pojawi – uru-
chomi tam jakiś elektromagnes, odsunie jakąś zasuwkę itd., słowem – spo-
woduje wyrzucenie jednego opakowania gumy. Podobnie sygnał BATON spo-
woduje wyrzucenie batonika, a ZWROT – wypadnięcie wszystkich wrzuconych
do tej pory monet. Do tych trzech „prawdziwych” sygnałów dodajmy jeszcze
czwarty: NIC. Będzie on produkowany wtedy, gdy maszyna ma nic nie robić,
to znaczy, gdy żadna z powyższych trzech czynności nie ma miejsca. Ten
czwarty, sztuczny sygnał jest nam potrzebny dla zachowania konwencji, że
automat sterujący w każdym kroku produkuje jakiś sygnał wyjściowy.
Ostatecznie, alfabet wyjściowy sterownika maszyny do sprzedaży jest
więc następujący:
B = {NIC, GUMA, BATON, ZWROT}.
Graf automatu skończonego opisujący zachowanie maszyny jest pokazany
na rysunku 10.6. Oczywiście, można by go zbudować i narysować inaczej,
ale w tej wersji też w sensowny sposób reaguje na zachowanie klienta.
Automat z rysunku 10.6 jest deterministyczny i zupełny. Determinizm
sprawia, że maszyna zachowuje się w sposób jednoznaczny i przewidywal-
ny. Zupełność automatu gwarantuje natomiast, że jest on przygotowany na
obsługę zupełnie dowolnych sekwencji zdarzeń nad alfabetem wejściowym,
nawet takich, które w pierwszej chwili nie przyjdą nam do głowy.
Dla przykładu, graf odpowie nam precyzyjnie, co się zdarzy, jeżeli
klient wrzuci jedną złotówkę, a zażąda batonika, który przecież kosztuje dwa
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
złote. W tej wersji automatu – nic się nie zdarzy. Maszyna będzie czekała na
to, by klient albo dołożył następną złotówkę i ponowił żądanie batonika, albo
zażądał zwrotu pieniędzy. A co będzie, jeśli klient wrzuci trzecią monetę?
W tym przypadku uczciwy automat zwróci wszystkie trzy monety i powróci
do stanu początkowego, gotów do rozpoczęcia transakcji od nowa. A co się
stanie, jeśli wrzucił dwa złote, a zażądał gumy, która kosztuje tylko złotów-
kę? Takich pytań, w pierwszej chwili nieoczywistych, można zadać więcej.
Dlatego, projektując taki automat, warto zadbać o to, by był on zupełny.
Nieco dalej, w rozdziale 13, zaprojektujemy sami podobny (choć nieco
prostszy) automat skończony i doprowadzimy ten projekt aż do etapu logicz-
nego schematu, składającego się z binarnych bramek logicznych i przerzutni-
ków. Jednak po to, byśmy umieli to zrobić – musimy przedtem zaznajomić się
z systemem dwójkowym, algebrą Boole’a i metodami budowania cyfrowych
podzespołów z odpowiednio połączonych podstawowych cegiełek, jakimi są
wspomniane bramki logiczne i przerzutniki.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
... 4 3 2 1 0 Pozycja i
... 10 000 1000 100 10 1 Waga wi
... 104 103 102 101 100 Waga wi = 10 i
5 3 7 1 2
Rys. 11.1. Zasada systematycznego dziesiętnego systemu zapisywania liczb naturalnych
... 7 6 5 4 3 2 1 0 Pozycja i
... 128 64 32 16 8 4 2 1 Waga wi = 2 i
... 0 1 1 1 0 0 1 1
tów, które wcale liczbami nie są. Można na przykład powiedzieć, że ósemka
bitów kodująca jeden znak alfanumeryczny czy ciąg szesnastu bitów ozna-
czających decyzje logiczne (tak–nie) – również mają swój bit najbardziej
i najmniej znaczący. W informatycznym żargonie mówi się także o bitach
„najstarszych” i „najmłodszych”.
Pisząc na papierze – możemy posługiwać się liczbami o dowolnej licz-
bie bitów. Liczba siedmio- czy trzydziestosiedmiobitowa nie różni się przecież
co do zasady od liczb ośmio- czy szesnastobitowych. Jednak w systemie kom-
puterowym, ze względu na budowę pamięci i procesora, stosowane długości
danych (a więc również liczb) są zawsze w pewien sposób ustandaryzowane.
Rozmiar fizycznej pamięci systemu komputerowego, wielkość pli-
ków danych itp. przyjęło się określać w bajtach3. Jeden bajt (ang. byte) ma
długość ośmiu bitów, a więc 1 B = 8 b (proszę zwrócić uwagę na B wielkie
oraz b małe). Jest to jednostka wygodna przede wszystkim ze względu na
sposób kodowania ciągów znaków alfanumerycznych (o czym niżej), ale
dla liczb – niewystarczająca. Najmniejszą ośmiobitową liczbą, jaką możemy
zapisać w kodzie NB, jest oczywiście zero, czyli (00000000)NB, najwięk-
szą (11111111)NB = 25510. Ponieważ w praktyce potrzebny jest zazwyczaj
większy zakres wartości liczb, przyjmuje się, że jednostkami, służącymi do
zapisywania liczb, są słowa (ang. words), o większej długości, zależnej od
charakterystyki komputerowego systemu, ale z zasady będące wielokrotno-
ścią ośmiobitowego bajtu.
Dla przykładu, w systemie 32-bitowym podstawowe słowa (a więc
i liczby całkowite) mają długość właśnie 32 b (i są zapisywane w pamięci
w czterech bajtach), a tzw. słowa podwójnej dokładności (ang. double preci-
sion words) – 64 b (czyli 8 B). W systemie 64-bitowym – odpowiednio 64 b
(8 B) oraz 128 b (16 B) itd. Czasami wyróżnia się jeszcze liczby krótkie,
o „połówkowej” długości. Warto o tym wiedzieć, lecz my nie będziemy
trzymali się sztywno tej zasady: do zilustrowania samych zasad arytmety-
ki dwójkowej na papierze wystarczą w zupełności krótsze, kilkubitowe czy
jednobajtowe przykłady.
Na rysunku 11.3 jest pokazany prosty sposób na prawie mechaniczne
przekształcanie liczb dziesiętnych na liczby w kodzie NB. Zadaną liczbę
3 Dla porządku przypomnijmy (bo znają je chyba już wszyscy) jednostki większe:
1 kilobajt = 1 kB = 210 B = 1024 B; 1 Megabajt = 1 MB = 1024 kB, 1 Gigabajt =
= 1GB = 1024 MB, 1 Terabajt = 1 TB = 1024 GB.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
7 6 5 4 3 2 1 0 Pozycja i
128 64 32 16 8 4 2 1 Waga wi = 2 i
0 1 1 1 0 0 1 1 Liczba A
+
0 0 0 1 1 0 1 0 Liczba B
0 1 1 1 0 0 1 0 0 Przeniesienia
1 0 0 0 1 1 0 1 Wynik A + B
7 6 5 4 3 2 1 0 Pozycja i
128 64 32 16 8 4 2 1 Waga wi = 2i
0 1 1 1 0 0 1 1 Liczba A
+
1 1 0 1 1 0 1 0 Liczba B
1 1 1 1 0 0 1 0 0 Przeniesienia
0 1 0 0 1 1 0 1 Wynik A + B
7 6 5 4 3 2 1 0 Pozycja i
64 32 16 8 4 2 1 Waga wi = 2i
Znak Moduł
0 1 1 1 0 0 1 1 Liczba 11510
1 1 1 1 0 0 1 1 Liczba (–11510)
7 6 5 4 3 2 1 0 Pozycja i
(–128) 64 32 16 8 4 2 1 Waga wi
Liczba A 0 1 1 1 0 0 1 1 = 11510
Liczba B 1 1 1 1 0 0 1 1 = (–13)10
7 6 5 4 3 2 1 0 Pozycja i
(–128) 64 32 16 8 4 2 1 Waga wi
0 1 1 1 0 0 1 1 = 11510
+
1 1 1 1 0 0 1 1 = (–13)10
1 1 1 1 0 0 1 1 Przeniesienie ci
0 1 1 0 0 1 1 0 = 10210
0 1 0 0 0 0 0 1 = 6510
+
0 0 1 1 0 0 1 1 = 5110
0 0 0 0 0 0 1 1 Przeniesienie ci
0 1 1 1 0 1 0 0 = 11610
1 0 0 0 1 1 0 0 = (–116)10
+
1 0 0 1 1 1 1 0 = (–98)10
1 0 0 1 1 1 0 0 Przeniesienie ci
0 0 1 0 1 0 1 0 = 4210 ???
7 6 5 4 3 2 1 0 Pozycja i
(–128) 64 32 16 8 4 2 1 Waga wi
0 1 1 1 0 0 0 0 = 11210
1 0 0 0 1 1 1 1 (Zanegowane bity)
+
0 0 0 1 1 1 1 1 Przeniesienie ci
1 0 0 1 0 0 0 0 = (–112)10
...i z powrotem:
1 0 0 1 0 0 0 0 = (–112)10
0 1 1 0 1 1 1 1 (Zanegowane bity)
+
0 0 0 1 1 1 1 1 Przeniesienie ci
0 1 1 1 0 0 0 0 = 11210
Rys. 11.11. Arytmetyczne przesuwanie w lewo liczby dodatniej (a) i ujemnej (b)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
4 Często stosuje się też rozwiązanie polegające na tym, że przy przesuwaniu logicz-
nym w lewo zawartość najbardziej znaczącego bitu nie ginie, lecz przesuwa się
na miejsce przewidziane na zapamiętywanie przeniesienia wychodzącego poza
granicę słowa.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Kod BCD jest wygodny szczególnie tam, gdzie dane wprowadza się – cyfra
po cyfrze – z dziesiętnej klawiatury, a wyniki wyświetla na dziesiętnych
wyświetlaczach. Dlatego jest do dziś używany w różnych urządzeniach, np.
w aparaturze pomiarowej, w domofonach.
Z patriotycznego sentymentu wspomnimy też całkiem dziś egzotycz-
ny system dwójkowego zapisu stosowany w polskiej maszynie cyfrowej (tak
wtedy nazywano komputery) UMC-1. Była ona zbudowana (jeszcze z uży-
ciem technologii lamp elektronowych), w końcu lat pięćdziesiątych XX wie-
ku, w zakładzie doświadczalnym uczelnianej katedry, która potem przekształ-
ciła się w Instytut Informatyki Politechniki Warszawskiej. Później, na począt-
ku lat sześćdziesiątych, produkowano ją seryjnie we wrocławskich zakładach
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
3
23
16 10
Rys. 11.12. Rola przecinka w liczbach dwójkowych
menty operacji miały taki sam wykładnik. Kiedy już wykładniki będą równe
– można dodać lub odjąć ułamki, a następnie – koniecznie – znormalizować
wynik przed jego zapisaniem do pamięci.
Przyjmuje się, że wykładniki zrównuje się do większego z nich.
Znaczy to, że ułamek liczby o mniejszym wykładniku trzeba przesuwać
(arytmetycznie) w prawo (dodając z każdym przesunięciem do wykładnika
jeden) tak długo, aż wykładniki zrównają się. Oczywiście, liczba nie jest
w tym momencie znormalizowana. Dopiero tak przesunięty ułamek bierze
udział w operacji dodawania lub odejmowania, a wynik musi być potem
znormalizowany.
W praktyce, format liczb zmiennoprzecinkowych we współczesnych
systemach komputerowych jest zwykle zgodny ze standardem, opracowanym
przez IEEE (Institute of Electrical and Electronic Engineers). Jest to wielkie
międzynarodowe stowarzyszenie, grupujące prawie 400 tysięcy profesjonali-
stów z ponad 160 krajów, reprezentujących dziedziny inżynierii elektrycznej,
elektroniki, informatyki, telekomunikacji itd. Standard, nazywający się IEEE
754, definiuje dwa formaty liczb zmiennoprzecinkowych: o pojedynczej pre-
cyzji (liczby 32-bitowe) oraz podwójnej precyzji (64-bitowe).
W obu przypadkach ułamek (mantysa) jest przedstawiana w kodzie
znak–moduł (1 + 23 bity albo 1 + 52 bity, odpowiednio). Wykładnik jest
natomiast przedstawiany w kodzie z przesunięciem (w krótszym formacie
8 bitów z przesunięciem 127, w dłuższym 11 bitów z przesunięciem 1023).
Dodatkowo, pewne wartości ułamka oraz wykładnika są zarezerwowane do
specjalnych celów, np. do sygnalizacji, że liczba (będąca wynikiem jakiejś
operacji) wypada poza dopuszczalny zakres albo że w ogóle nie jest licz-
bą (jest NaN, ang. Not a Number, jak na przykład wynik dzielenia przez
zero).
Warto na koniec dodać, że w praktyce programowania, programiście
daje się możliwość zadeklarowania, czy dana zmienna (przyjmującą warto-
ści liczbowe) jest typu integer (liczba całkowita), czy real (liczba „rzeczy-
wista”). Pierwsza deklaracja (integer) oznacza, że liczba ma być traktowana
jako stałoprzecinkowa i całkowita (a więc z domyślnym przecinkiem po
najmniej znaczącym bicie). Zmienne deklarowane jako real są natomiast
traktowane jako zmiennoprzecinkowe. Translator, przekładający kod źródło-
wy programu na język maszynowy, sam zadba o to, by zarezerwować dla
tych zmiennych odpowiedniego rozmiaru komórki pamięci, a operacje aryt-
metyczne wykonywać według zasad odpowiadających typowi argumentów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
5 To nie upór ani ideologiczny konserwatyzm, tylko świadoma polityka tej fir-
my. Dzięki temu zostaje zachowana ciągłość i kompatybilność z oprogramowa-
niem, bazami danych itd., zgromadzonymi przez lata i wykorzystywanymi od
lat przez wielkie korporacje będące głównymi użytkownikami mainframes IBM.
Koncernowi pomaga to utrzymać nieprzerwanie dominującą pozycję na rynku
wielkich systemów do celów biznesowych.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Pierwsze dwie kolumny zawierają 32 umowne znaki, które nie mają graficz-
nej postaci i nie widujemy ich w druku6. Są to tak zwane znaki (lub: kody)
sterujące (ang. control codes). Pełnią one funkcje jak gdyby wskazówek
organizacyjnych w procesie konstruowania i przekazywania wiadomości.
Dla przykładu:
– SOH (Start of Heading) oznacza początek nagłówka,
– STX oraz ETX oznaczają – odpowiednio – początek i koniec tekstu (Start
of Text, End of Text),
6 Jest jeszcze trzydziesty trzeci: DEL, na samym końcu tabeli, w niektórych wer-
sjach kodu niewykorzystywany.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
7 Można też przyjąć konwencję przeciwną: aby liczba jedynek była parzysta.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
uzupełniane nie bitem parzystości, lecz po prostu stałym zerem. Tak więc
na przykład:
– cyfra 9, stojąca w kolumnie 011 i w wierszu 1001 – ma w siedmiobitowym
kodzie ASCII postać 011 1001, a w ośmiobitowym 0011 1001 (heksadecy-
malnie 39),
– litera T ma kod 0101 0100 (heksadecymalnie 54),
– znak zapytania ? ma kod 0011 1111 (heksadecymalnie 3F)
...i tak dalej.
Łatwo zauważyć, że systematyczny sposób rozmieszczenia cyfr zna-
komicie upraszcza konwersję liczby z postaci tekstowej na kod BCD (Binary
Coded Decimal) i z powrotem. Z każdego bajtu kodującego cyfrę dziesiętną
wystarczy odciąć cztery najbardziej znaczące bity – a otrzymuje się od razu
kod BCD tej cyfry. I w drugą stronę: mając liczbę zakodowaną w kodzie
BCD i pisząc przed każdą czwórką bitów (kodującą jedną cyfrę dziesiętną)
stały prefiks 0011 – otrzymamy ośmiobitowy znak ASCII, już gotowy do
wydrukowania lub wyświetlenia na ekranie, jeśli tylko ta drukarka czy mo-
nitor ekranowy obsługują kod ASCII.
Podobne zalety ma sposób rozmieszczenia małych i dużych liter.
Gdybyśmy całe, ośmiobitowe odpowiedniki poszczególnych liter odczytali
jako liczby w kodzie NB – to okaże się, że wszystkie litery są ponumero-
wane w porządku rosnącym: najpierw spacja, potem duże litery, następnie
małe. Można to wykorzystać w procesie sortowania danych tekstowych (np.
nazwisk itd.) według alfabetu. Porównując (arytmetycznie) takie liczby na-
turalne i umieszczając na liście mniejszą liczbę przed większą – uzyskujemy
faktycznie kolejność zgodną z porządkiem alfabetycznym.
Niestety, w praktyce posługujemy się jeszcze wieloma innymi zna-
kami, dla których nie starczyło miejsca w tabeli kodu ASCII. Są przecież
symbole, takie jak np. §, £, ®, są symbole matematyczne, są wreszcie znaki
diakrytyczne: ogonki i kropki, jak w języku polskim w znakach ą, ć, ę, ó,
ż, ź itd. Inne narodowe alfabety (nawet ograniczając się tylko do tych, któ-
re są oparte na alfabecie łacińskim) mają również swoje szczególne znaki:
z akcentem, daszkiem, przekreśleniem i tak dalej. A przecież jest jeszcze
cała cyrylica, alfabet grecki, hebrajski, arabski...
Repertuar znaków można stosunkowo łatwo rozszerzyć, wzorując się
na omówionych wyżej zasadach budowy kodu ASCII. Można mianowicie
narysować drugą taką tablicę o ośmiu kolumnach i szesnastu wierszach, wy-
pełnić ją znakami, które nie zmieściły się w pierwszej, podstawowej tabeli,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Projekt Unicode
Pomysł powołania organizacji, która zajęłaby się unifikacją sposobów ko-
dowania alfanumerycznej informacji w skali światowej – pojawił się już
w roku 1991. Powstało międzynarodowe konsorcjum Unicode (ang. The
Unicode Consortium), wspierane przez praktycznie wszystkie liczące się na
świecie firmy, zajmujące się produkcją sprzętu komputerowego, oprogramo-
wania, budową baz danych, aplikacji internetowych itd., a także organizacje
rządowe i prywatne, badawcze i gospodarcze działające w tej i w pokrew-
nych dziedzinach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
11001110 10101000.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Ta zasada rozciąga się na dalsze znaki: pierwszy bajt zaczyna się za-
wsze od tylu jedynek, ile bajtów jest w danej grupie, a po tym następuje
stałe zero. Każdy następny bajt zaczyna się od dwóch stałych bitów 10.
Pozostałe bity są wypełnione zerami i jedynkami właściwymi dla danego
znaku.
Początkowo przewidywano, że po to, by móc reprezentować w ten
sposób wszystkie punkty kodowe z pełnej, wielkiej tablicy Unicode – tak
skonstruowane grupy będą miały długość od dwóch aż do sześciu bajtów.
Ponieważ jednak konsorcjum zdecydowało się ograniczyć do pierwszych sie-
demnastu kolumn, grupy pięcio- i sześciobajtowe nie są wykorzystywane.
Formaty UTF-16 oraz UTF-32 są rzadko stosowane. W formacie
UTF-16 każdy znak jest reprezentowany w postaci dwóch bajtów, zaś UTF-
32 przewiduje kodowanie każdego znaku wprost za pomocą 32-bitowego
słowa. Każdy z tych formatów (podobnie zresztą, jak i UTF-8) ma zresz-
tą szereg dodatkowych szczegółowych konwencji, restrykcji, zabronionych
kombinacji bitów itd.
Standard Unicode stanowi ważny krok w kierunku ujednolicenia spo-
sobu kodowania znaków. Nie jest to jednak koniec problemów z kodowa-
niem informacji alfanumerycznej. Weźmy choćby sposób zapisywania dat.
Czy jedenasty września 2001 roku, to 9/11/2001 (jak piszą Amerykanie),
czy 11.09.2001, czy 2001-09-11? A może 2001.09.11? A dlaczego nie 11 IX
2001? A właściwie dlaczego 2001? W tradycji muzułmańskiej, judaistycznej
czy chińskiej liczy się przecież lata inaczej niż od narodzenia Chrystusa. Już
tylko z punktu widzenia organizacji systemów komputerowych – ewentual-
ne rozbieżności w sposobie zapisywania dat mogłyby zakłócić na przykład
chronologiczne porządkowanie plików w katalogach (folderach) i wiado-
mości w skrzynkach mailowych, automatyczne datowanie dokumentów itd.
Musi być to (i rzeczywiście jest) uzgodnione w skali międzynarodowej.
A w jakim kierunku mają się rozwijać paski narzędzi, menu, wykaz
zainstalowanych programów itp., jak również same teksty w dokumentach
czy na internetowych stronach? Wierszami, z lewa na prawo i z góry na dół
(po europejsku), wierszami z prawa na lewo i z góry na dół (po hebrajsku
i arabsku), czy też kolumnami, z góry w dół i z prawa w lewo (po japoń-
sku)? Ma to niemałe znaczenie zwłaszcza w dobie globalnej gospodarki,
gdy banki, organizacje handlowe itd. mają przedstawicielstwa i oddziały
w różnych częściach świata, a kontakt z nimi za pośrednictwem Internetu
trzeba umożliwić także osobom, które nawet nie znają łacińskiego alfabetu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Jak widać, zdanie p LUB q (zwane alternatywą lub sumą logiczną) jest – jako
całość – fałszywe jedynie wtedy, gdy zarówno p, jak q są jednocześnie fałszy-
we. W pozostałych trzech przypadkach jest prawdziwe. Tymczasem zdanie
p I q (koniunkcja lub iloczyn logiczny) jest prawdziwe jedynie wtedy, gdy p i q
są jednocześnie prawdziwe, a w pozostałych przypadkach – jest fałszywe.
Przyjęliśmy wyżej formalne założenie o tym, że nie będziemy się
zajmować treścią i znaczeniem zdań prostych ani zbudowanych z nich bar-
dziej złożonych konstrukcji. Jednak jeśli podłożymy pod p i q przykładowe
proste zdania zaczerpnięte z potocznego języka, to stwierdzimy, że te tabel-
ki rzeczywiście opisują rolę spójników NIE, LUB oraz I w sposób zgodny
z codzienną intuicją.
Układy przełączające
Układy przełączające – to elektryczne urządzenia, powstałe przez połącze-
nie (być może bardzo skomplikowane) zestawów elektrycznych styków.
Najprostsze układy tego typu mamy pod ręką, w domu. Każdy wyłącznik
w naszej lampie jest układem przełączającym: w jednym jego położeniu ja-
kieś styki są rozwarte i prąd do żarówki nie płynie, w drugim – są zwarte,
elektryczny obwód zamyka się i żarówka świeci.
Stosując szeregowe i równoległe połączenia wyłączników, można uzy-
skać bardziej złożone możliwości: na przykład w układzie z rysunku 12.1a
obwód zamknie się i światło się zapali, jeśli są włączone oba włączniki a
i b i jednocześnie co najmniej jeden z włączników c i d. Wykorzystując
nie wyłączniki, lecz dwupołożeniowe przełączniki – możemy skonstruować
sprytny układ elektryczny, który bywa instalowany na schodach lub w ko-
rytarzach hotelowych (rysunek 12.1b). Dzięki niemu możemy dowolnie
włączać i wyłączać oświetlenie każdym z przełączników (zarówno e, jak f ),
ale także wchodząc na schody, włączyć światło przełącznikiem np. e (na
dole) i wyłączyć przełącznikiem f, gdy jesteśmy już na górze, lub odwrot-
nie. Łatwo to prześledzić, badając, jak zmiany położeń przełączników e oraz
f wpływają na to, czy obwód z rysunku 12.1b jest zamknięty, czy też nie.
2 Rola przekaźników w takiej linii telegraficznej dobrze tłumaczy ich nazwę, a re-
lay – to po angielsku sztafeta.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
tem, po zakończeniu wojny, raport odtajniono, a jego treść stała się znana
w środowisku akademickim i technicznym. Od tego czasu nie buduje się
już komputerów inaczej: na koncepcji von Neumanna oparte jest działanie
praktycznie wszystkich komputerów, dużych i małych, od tamtych lat aż do
dnia dzisiejszego3.
Późniejsze prawie siedemdziesiąt lat – to czas nieustannych ulepszeń
koncepcji Shannona i von Neumanna, nowych technologii, nowych pomy-
słów technicznych i teoretycznych, które doprowadziły światową informa-
tykę i telekomunikację do dzisiejszego stanu. Powiemy o tym więcej w na-
stępnych rozdziałach tej książki. Oczywiście, pracowały nad tym tysiące
ludzi, w tym wielu bardzo wybitnych, ale to Claude Shannon był tym, który
jako pierwszy uchylił drzwi do cyfrowego świata.
w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b ).
w = 0 ⋅ (0 ⋅ 0 + 0 ⋅ 0) + 0 ⋅ (0 ⋅ 0 + 0 ⋅ 0 ).
w = 1 ⋅ (0 ⋅1 + 1 ⋅ 0) + 0 ⋅ (0 ⋅ 0 + 1 ⋅1).
w = 1 ⋅ (0 + 0) + 0 ⋅ (0 + 1),
w = 1 ⋅ 0 + 0 ⋅1,
w = 0 + 0,
w = 0.
w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b )
i weźmy pod uwagę zawartość pierwszej pary nawiasów jej prawej strony.
Oznaczmy tę zawartość przez p. Innymi słowy, p = a ⋅ b + a ⋅ b . Wprowadźmy
teraz pomocniczą zmienną q = p . Oczywiście, wtedy q = a ⋅ b + a ⋅ b.
Stosując do q prawo de Morgana (tożsamość 15), możemy napisać:
q = a ⋅ b + a ⋅ b = ( a ⋅ b ) + ( a ⋅ b) = ( a ⋅ b ) ⋅ ( a ⋅ b) .
( a ⋅ b ) = ( a + b ) = ( a + b),
( a ⋅ b) = ( a + b ) = ( a + b ).
Tak więc po podstawieniu z powrotem do q i pomnożeniu zawartości obu
nawiasów:
q = ( a + b) ⋅ ( a + b ) = a ⋅ a + a ⋅ b + b ⋅ a + b ⋅ b .
q = 0 + a ⋅ b + a ⋅ b + 0.
Czyli ostatecznie, posługując się jeszcze po drodze tożsamościami numer 1
i 9:
q = a ⋅b + a ⋅b.
w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b )
w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
p = a ⋅ b + a ⋅ b,
w = c ⋅ p + c ⋅ p,
p = a ⋅ b + a ⋅ b,
w = c ⋅ p + c ⋅ p.
w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b) .
d = c ⋅ b ⋅ a + c ⋅ b ⋅ a + c ⋅ b ⋅ a + c ⋅ b ⋅ a.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
d = b ⋅ a ⋅ (c + c) + c ⋅ (b ⋅ a + b ⋅ a ).
d = a ⋅ b + c ⋅ ( a ⋅ b + a ⋅ b ).
d = (c + b + a ) ⋅ (c + b + a ) ⋅ (c + b + a ) ⋅ (c + b + a ).
w = c ⋅ b ⋅ a + c ⋅ b ⋅ a + c ⋅ b ⋅ a + c ⋅ b ⋅ a.
Wyciągając z pierwszych dwóch składników przed nawias c , a z ostatnich
dwóch c, otrzymujemy:
w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b ).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b)
i zrealizować w postaci sieci takiej, jak na rysunku 12.5, albo jeszcze prost-
szej, składającej się z dwóch bramek XOR jak na rysunku 12.9. Otrzymana
sieć nazywana jest półsumatorem.
Drugą funkcję logiczną, uzależniającą ci + 1 od ai, bi, ci, również już znamy.
To przecież funkcja, opisująca działanie układu głosującego:
d = a ⋅ b + c ⋅ (a ⋅ b + a ⋅ b )
w2, w1, w0) pojawi się 32-bitowe słowo W, będące arytmetyczną (tym razem
oczywiście nie logiczną) sumą liczb A i B.
Sumator trzeba jeszcze wyposażyć w dodatkowe wyjścia, niosące informa-
cję o wyniku dodawania. O bitach C (przeniesienie) oraz V (nadmiar, czyli
przekroczenie zakresu) już wspominaliśmy. W rzeczywistości podobne pod-
zespoły arytmetyczne zazwyczaj podają jeszcze dwie inne jednobitowe in-
formacje: N oraz Z. Gdy N = 1, to znaczy, że wynik jest liczbą ujemną (od
ang, negative), natomiast Z = 1 sygnalizuje, że wynik jest równy zeru.
Ten standardowy zestaw czterech jednobitowych informacji (N, Z, V,
C), zwanych bitami warunków, jest ważny ze względu na budowę i rolę
niektórych instrukcji języka maszynowego. Będziemy później mieli okazję
o tym powiedzieć więcej. Żeby jednak nie pozostawiać rzeczy całkiem bez
wyjaśnienia, wspomnimy, że chodzi o warunkowe wykonywanie niektórych
instrukcji.
Już w drugim rozdziale, przy omawianiu najprostszych algorytmów,
zetknęliśmy się z sytuacjami, w których badany jest pewien warunek, a od
wyniku badania (TAK albo NIE) zależy, jaką ścieżką w sieci działań teraz
podążymy. W tłumaczeniu na język wewnętrzny maszyny badanie warunku
jest przedstawiane w postaci prostej operacji arytmetycznej. Dla przykładu,
badanie „Czy i = n?” można sprowadzić do arytmetycznego odejmowa-
nia dwóch liczb całkowitych: i oraz n. Jeżeli wynik jest równy zeru – to
TAK, są one równe, w przeciwnym przypadku – NIE, nie są. Podstaw do
takiej decyzji dostarcza właśnie jednostka arytmetyczna: w tym przykła-
dzie o dalszym przebiegu obliczenia będzie decydowała wartość bitu Z.
Podobną rolę mają trzy pozostałe bity warunków, produkowane przez jed-
nostkę arytmetyczną procesora.
Nie jest trudno zrealizować te cztery bity warunków:
– C = c32 (przeniesienie wybiegające poza granice słowa),
– V = c32 ⋅ c31 + c32 ⋅ c31, co pozwala zrealizować funkcję V za pomocą jednej
bramki XOR (patrz tabelka prawdy dla zmiennej V na rysunku 12.9),
– N = w31. Istotnie, w kodzie U2 najbardziej znaczący bit wyniku decyduje
o znaku liczby: gdy w31 = 1, liczba W jest ujemna,
– Z = w31 + w30 + w29 + + w2 + w1 + w0 . Rzeczywiście, wynik jest równy
zeru, kiedy wszystkie jego bity mają wartość 0. Wtedy (i tylko wtedy)
negacja boolowskiej sumy wszystkich bitów wyniku jest równa 1. Do wy-
produkowania bitu warunku Z wystarczy więc wielowejściowa bramka
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
7 Jeżeli 32-wejściowa bramka NOR nie jest dostępna – można to samo osiągnąć,
tworząc kaskadę sum logicznych (sumę sum) o mniejszej liczbie wejść, kończącą
się negacją lub bramką NOR.
8 Wspomnieliśmy wyżej, że nazywa się go jednostką arytmetyczno-logiczną (ALU
– arithmetic and logic unit).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Przerzutniki
W układach kombinacyjnych wartość każdej zmiennej wyjściowej sieci wy-
nika z tabelki prawdy danej funkcji, a to znaczy, że zależy wyłącznie od
aktualnego stanu zmiennych wejściowych – i od niczego innego. Możemy
więc powiedzieć, że sieć logiczna (podobnie, jak pojedyncza bramka) nie
ma zdolności pamiętania czegokolwiek. O wszystkim decyduje (oprócz
struktury sieci) wyłącznie aktualny stan jej wejść.
Z drugiej strony wiemy, że wszelkie procesy obliczeniowe mają cha-
rakter sekwencji pewnych elementarnych operacji, przy czym wybór czyn-
ności następnej może zależeć od wyniku czynności ją poprzedzającej. Aby
wykonywanie takich sekwencji można było powierzyć maszynie, trzeba ko-
niecznie rozszerzyć możliwości sieci logicznych o elementarną umiejętność
pamiętania choćby tylko jednej rzeczy: stanu własnego wejścia albo wyjścia
sprzed krótkiej chwili. Wtedy zyskamy możliwość budowania układów se-
kwencyjnych (ang. sequential circuits), to znaczy takich, które będą w stanie
samodzielnie wykonać właśnie sekwencję działań w czasie.
Obok znanych nam już bramek logicznych, cegiełkami, z których bu-
duje się układy sekwencyjne, są elektroniczne układy, zwane przerzutnikami.
Są one również od dawna znane: najstarszy typ przerzutnika opatentowali
już w roku 1918. William Eccles i Frank Jordan. Wtedy nie myślano jesz-
cze o układach logicznych ani o dwójkowych obliczeniach: był to wówczas
jeszcze jeden układ elektroniczny, o ograniczonych zastosowaniach, ale
ciekawy, bo różny np. od powszechnie znanych, analogowych wzmacnia-
czy. Przerzutnik Eccles–Jordana był oczywiście układem lampowym, obec-
nie przerzutniki są układami półprzewodnikowymi, wykonanymi w technice
układów scalonych. Opracowano ich wiele rodzajów, my omówimy tylko
kilka przykładowych rozwiązań.
Zasadą każdego przerzutnika jest to, że ma on dwa stabilne stany.
Przejście od jednego stanu do drugiego powinno mieć charakter błyskawicz-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Przerzutniki 315
Przerzutniki 317
Przerzutniki 319
Rejestry i liczniki
W poprzednich rozdziałach wielokrotnie opisywaliśmy operacje na bajtach lub
słowach, które są zespołami kilku, kilkunastu czy kilkudziesięciu bitów. Na
przykład, mówiąc o zasadach dodawania liczb w kodzie U2, zakładaliśmy, że
mamy dwie 32-bitowe liczby A oraz B, które zapisywaliśmy w postaci „okie-
nek” wypełnionych zerami i jedynkami. W technicznej realizacji owe okienka
są zespołami przerzutników, z których każdy przechowuje („pamięta”) wartość
jednego bitu danego bajtu lub słowa.
Taki zestaw przerzutników (wraz z obudowującymi je bramkami lo-
gicznymi, niezbędnymi do wykonywania podstawowych operacji) nazywa
się rejestrem (ang. register). To stara, tradycyjna nazwa, pochodząca jesz-
cze z czasów, gdy komputer kojarzył się przede wszystkim z prostą ma-
szynką do dodawania i odejmowania. Powszechnie znanym pierwowzorem
takich urządzeń były mechaniczne rejestratory kasowe (ang. cash register),
które w sklepach i barach służyły do sumowania opłat za towar lub piwo.
Okienko, w którym pojawiał się wynik, nazywano właśnie rejestrem. A że
suma zbierała się (akumulowała) w nim stopniowo w miarę dodawania ko-
lejnych pozycji – to i we wczesnych komputerach pojawił się binarny re-
jestr akumulatora. I nazwa została4.
się jednak projektuje sam układ sterowania, który kontroluje stan tych linii
sterujących?
Postaramy się to pokazać na bardzo prostym przykładzie. Właśnie dla
prostoty nie będzie to komputer, nie będą w nim występowały rejestry ani
liczniki, jednak będzie on potrafił odbierać sygnały z zewnątrz i produko-
wać sygnały sterujące dla współpracujących z nim podzespołów, tak by ich
działania układały się w skoordynowane, sensowne sekwencje.
Przypomnijmy sobie maszynę sprzedającą gumę do żucia i batoni-
ki, której układ sterowania posłużył nam w rozdziale 10 za przykład au-
tomatu skończonego. Teraz, żeby przykład był jeszcze prostszy – zajmie-
my się mniejszym podzespołem tejże maszynerii, a mianowicie prostym
układem sekwencyjnym, obsługującym samą tylko szczelinę do wrzuca-
nia monet. Niech się on nazywa Nasz Sterownik, w skrócie NS. Będzie on
współpracował z głównym układem sterowania całej maszyny (nazwijmy
go GUS), który działa mniej więcej tak, jak pokazano na grafie na rysun-
ku 10.65.
Nic 00
, 01
→ Czekaj
albo prościej:
x
Nic 0 → Czekaj ,
gdzie znaczek x jest interpretowany jako „wszystko jedno co, 0 lub 1”6.
Zaraz, zaraz... A co będzie, jeśli automat w stanie Nic otrzyma na wej-
ściu dwa pozostałe elementy alfabetu wejściowego, to znaczy (10) lub (11)?
Na szkicowym rysunku tego nie przewidzieliśmy, ale teraz musimy to spre-
cyzować, inaczej automat będzie niezupełny. Po chwili zastanowienia decy-
dujemy, że gdy wejściowa para (foto, nast) ma wartość (10) albo (11) – to
automat powinien pozostać w stanie Nic:
x
Nic 1 → Nic .
6 W żargonie projektantów sprzętu taki x nazywa się don’t care (po polsku bez-
pretensjonalnie mówią – ale nigdy nie piszą – dontker), od ang. don’t care – to
nieistotne, tym się nie przejmuj.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Jak w każdym automacie Moore’a tabela 13.1 ma dwie części. W obu, wier-
sze odpowiadają stanom automatu. W tabeli funkcji przejścia kolumny od-
powiadają dwubitowym wejściom (foto, nast), w polu na przecięciu wiersza
i kolumny wpisany jest stan następny (docelowy). W tabeli funkcji wyjścia
w poszczególnych kolumnach podane są wartości zmiennych wyjściowych
(klapka, gotowe), przypisane stanom automatu.
Otrzymany w ten sposób automat projektanci nazywają abstrakcyjnym,
nie dlatego, że jest jakiś nierzeczywisty czy nie do zrealizowania, ale dla
odróżnienia od jego wersji zakodowanej, binarnej, do której dojdziemy za
chwilę.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
01 01 01 10 10 0 0
11 xx xx xx xx x x
10 10 00 00 10 1 1
– po pierwsze, automat musi pamiętać swój aktualny stan, który jest teraz
reprezentowany przez parę bitów. Będziemy więc potrzebowali dwóch
przerzutników, które będą stanowiły pamięć stanu. Stanem automatu bę-
dzie para (q1, q0) wartości logicznych na wyjściach tych dwóch przerzut-
ników;
– po drugie, musimy spowodować, by oba przerzutniki przechodziły do na-
stępnego stanu dokładnie zgodnie z tabelą przejść. Decyzja o tym, jaki
ma być ów następny stan – zależy od stanu aktualnego (a więc od q0 oraz
q1), ale także od wejść automatu (a więc foto, nast);
– po trzecie, musimy wyprodukować odpowiednie pary wartości bi-
tów (klapka, gotowe) na wyjściach automatu. Ponieważ jest to automat
Moore’a, wartości te zależą wyłącznie od stanu automatu, zgodnie z ta-
belką funkcji wyjścia.
Przyjmijmy, że zastosujemy w naszym projekcie synchronicz-
ne przerzutniki typu D. Niech się nazywają D0 oraz D1. Takie rozwiązanie
jest stosunkowo najprostsze. Wiemy już, że każdy z nich ma jedno wejście
d (odpowiednio: d0 i d1), którego aktualna wartość (0 lub 1) pojawia się na
wyjściu q w następnej chwili zegarowej.
Podjąwszy tę decyzję, możemy już narysować blokowy schemat
układu (rysunek 13.9). Jest to – co do swej zasady – rozwiązanie bardzo
typowe i konsekwentne. Widać też z niego dokładnie, co jeszcze pozostaje
do zrobienia: musimy zaprojektować wnętrze bloku, nazywającego się „sieć
logiczna”. Będzie ona zawierała układy kombinacyjne, stanowiące realiza-
cję następujących czterech funkcji logicznych:
– d0 = f1(foto, nast, q0, q1),
– d1 = f2(foto, nast, q0, q1),
– klapka = f3(q0, q1),
– gotowe = f4(q0, q1).
Pierwsze dwie z nich – to funkcje wzbudzeń. Ich zadaniem jest do-
starczenie na wejścia obu przerzutników takich wartości d = 0 lub d = 1, by
następny stan q był taki, jak to przewidziano w tabeli przejść.
W przypadku przerzutników typu D zdefiniowanie funkcji wzbudzeń
jest wyjątkowo łatwe. Dla każdej kombinacji zmiennych wejściowych (foto,
nast, q0, q1), na wejście d0 trzeba dostarczyć taką wartość (0 albo 1), jaki
ma być (według tabeli przejść) następny stan przerzutnika D0, a na wej-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
ście d1 – taką wartość, jaką tabela przejść przewiduje jako następny stan
przerzutnika D1. Ten następny stan (w postaci dwubitowego kodu) jest wpi-
sany w odpowiednie okienka tabeli przejść. Krótko mówiąc, trzeba z tablicy
przejść utworzyć dwie tablice o identycznie oznaczonych wierszach i ko-
lumnach, następnie z każdego okienka tablicy przejść przepisać pierwszą ze
stojących tam cyfr do pierwszej z tych nowych tabel, a drugą – do drugiej.
Otrzymamy w ten sposób tablice funkcji wzbudzeń (tabela 13.3).
01 0 0 1 1 01 1 1 0 0
11 x x x x 11 x x x x
10 1 0 0 1 10 0 0 0 0
Obie te tabelki – to nic innego, jak dobrze nam znane tabele prawdy, tyle że
nieco inaczej zapisane. Dotychczas tabelki prawdy zapisywaliśmy pionowo,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
... q1 ⋅ q 0 ⋅ foto...
d 0 = q1 ⋅ foto .
W większej tablicy Karnaugh może nam się udać znalezienie prostokąta
z ośmiu (a może nawet szesnastu) pól wypełnionych jedynkami, co pozwala
na wyeliminowanie trzech lub czterech zmiennych itd.
Pojęcie „sąsiedztwa” jedynek jest przy tym szersze, niż się na pierw-
szy rzut oka wydaje. Kod Greya jest cykliczny, co znaczy, że binarny numer
ostatniej kolumny tabeli różni się od numeru pierwszej także tylko na jednej
pozycji. Tak więc np. w tabeli dla d1, stojące w wierszu 10 dwie jedynki
także sąsiadują ze sobą, tak jak gdyby tabelka była zwinięta w rulonik. To
samo dotyczy kolumn: tabelka jest więc teoretycznie „zwinięta w rulonik”
(bardziej formalnie: torus) zarówno w poziomie, jak i w pionie. Pozwala to
– przy pewnej wprawie – na wyszukiwanie różnych wzorców sąsiedztwa
jedynek.
Ale to nie koniec możliwości upraszczania formuł boolowskich.
Możemy także wykorzystać te pola tabeli, w których figurują znaczki x.
Zgodnie z zasadą don’t care, możemy tam wpisać cokolwiek, 0 albo 1, po-
nieważ i tak nie wpływa to na działanie układu. Jeśli więc spostrzeżemy, że
wpisanie w takie pole jedynki pozwoli na skompletowanie pary lub kwadra-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Ostatecznie:
d1 = q1 ⋅ nast + q 0 ⋅ foto
Tablice Karnaugh wykorzystują więc w pomysłowy sposób zdolność na-
szego zmysłu wzroku do wyszukiwania pewnych wzorców w dwuwymia-
rowym obrazie. Przy pewnej wprawie, takie „wzrokowe” projektowanie
formuł logicznych jest znacznie prostsze niż wtedy, gdy posługujemy się
algebraicznymi przekształceniami postaci kanonicznej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
d 0 = q1 ⋅ foto ,
d1 = q1 ⋅ nast + q 0 ⋅ foto.
Pozostają jeszcze do wyznaczenia dwie boolowskie funkcje wyjściowe, dla
linii sterujących gotowe oraz klapka. Rzut oka na tabelkę obu funkcji wyj-
ścia (tabela 13.2) pozwala stwierdzić, że jeśli w miejsce obu x-ów (don’t
care!) wpiszemy 1, to obie funkcje wyjściowe sprowadzają się do identycz-
nej, skądinąd wyjątkowo banalnej postaci:
klapka = q1,
gotowe = q1.
Możemy już rozwinąć blokowy schemat z rysunku 13.9, tak by stał się lo-
gicznym schematem naszego sterownika (rysunek 13.12). Należałoby jesz-
cze zadbać o to, by układ rozpoczynał swoje działanie od założonego stanu
początkowego (q1, q0) = (00). Wymaga to pewnych modyfikacji, zapewne
doprowadzenia dodatkowego sygnału zerowania obu przerzutników, poja-
Praojcowie informatyki
W popularnej literaturze i publicystyce często mówi się o różnych „wie-
kach”. Wiek XIX bywa nazywany „wiekiem pary i elektryczności”. Wiek
XX – to „wiek elektroniki”. Od połowy XX w. żyjemy w „wieku energii
atomowej” i tak dalej. Dlaczego więc mamy sobie odmówić nazwania na-
szych czasów wiekiem informatyki?
Każdy z takich „wieków” rozpoczyna się od jakiegoś umownego
momentu, jakiegoś znaczącego wydarzenia, takiego, jakim dla wieku pary
i elektryczności było skonstruowanie w końcu XVIII wieku maszyny paro-
wej. Ale zawsze jest on poprzedzony dziesięcioleciami, a nawet stuleciami
prób, wynalazków, badań, które – choć czasami dziś zapomniane – tworzą
podglebie, z którego wyrasta potem coś bardzo ważnego dla rozwoju całej
naszej cywilizacji.
Nasz wiek informatyki zaczyna się na dobre w połowie lat czterdzie-
stych XX wieku. Za jego umowny początek możemy uznać sformułowanie
przez Johna von Neumanna koncepcji maszyny z pamiętanym programem
(ang. stored program computer). Temu pomysłowi poświęcimy znacznie
więcej uwagi w następnym rozdziale. Zaczniemy jednak od krótkiego rzutu
oka na czasy wcześniejsze, które stanowią jak gdyby prehistorię tej dzie-
dziny. Warto to zrobić nie tylko dlatego, że to ciekawe, ale też dlatego, że
pokazuje wyraźnie, jak poziom technologii wpływa na realizację pomysłów,
pochodzących nawet od najwybitniejszych umysłów swoich czasów. Tak
było wieki temu – i tak jest obecnie.
Zostawmy historykom czasy starożytne i średniowiecze, a zwróćmy
się od razu ku epoce nowożytnej. Oto niemiecki teolog, hebraista, matema-
tyk i astronom Wilhelm Schickard (1592–1635) już w 1623 roku zaprojekto-
wał dziesiętny mechaniczny kalkulator, który miał ułatwić obliczenia astro-
nomiczne. Projekt nie został do końca zrealizowany za życia uczonego, gdyż
był – jak na tamte czasy – zbyt mechanicznie skomplikowany. Ale według
oryginalnych planów Schickarda działający egzemplarz urządzenia udało się
zbudować, choć po przeszło trzystu latach, w roku 1960.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Fot. 14.1. Charles Babbage (1791–1871). Zdjęcie ze zbioru Science & Society Picture
Library, Wielka Brytania
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Fot. 14.2. Maszyna różnicowa Babbage’a. Zdjęcie ze zbioru Science & Society Picture
Library, Wielka Brytania
Bariera niezawodnościowa
Oczywiście, lampowe komputery budowane na początku lat pięćdziesiątych
XX wieku (nazwano je później komputerami pierwszej generacji) miały
też swoje techniczne ograniczenia. Były wielkie, drogie, zużywały mnó-
stwo energii elektrycznej. Trudności sprawiała realizacja odpowiednio po-
jemnej pamięci, która przecież odgrywała kluczową rolę w koncepcji von
Neumanna. Jednak mało kto (z nieelektroników) zdaje sobie sprawę z faktu,
że podstawowym ograniczeniem nie był ani niedostatek ludzkiej wyobraźni,
ani wielkość pobieranej mocy, ani bariera kosztów, lecz bariera niezawod-
ności. Spowodowana była ona ogromną złożonością elektrycznego układu,
nieporównanie większą niż w przypadku radioodbiornika, telewizora czy
stacji radarowej.
Początkowo urządzenia te były niesłychanie zawodne. W kompute-
rze, zawierającym kilkanaście tysięcy lamp, losowe przepalenie się którejś
lampy musiało się zdarzać praktycznie co kilka godzin. Jednak główną
przyczyną nieustannych awarii były nie same delikatne lampy próżniowe,
lecz przede wszystkim owe miliony punktów połączeniowych: miejsca lu-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
gdy mówił, że jego kraj wkrótce dogoni i przegoni Zachód nie tylko w dzie-
dzinie techniki i nauki, ale pod względem jakości życia obywateli?
Nie jesteśmy dziś w stanie wyobrazić sobie, jakie emocje budziły
wówczas badania kosmiczne. Teraz loty kosmiczne już spowszedniały. Setki
sztucznych satelitów od dawna krążą wokół Ziemi, obsługując nasze łącza te-
lekomunikacyjne, transmisje telewizyjne i globalne systemy nawigacyjne. Ale
wtedy, ponad pół wieku temu, miliony ludzi na całym świecie czuły się świad-
kami zupełnie epokowych wydarzeń, tak jak gdyby na ich oczach realizowały
się niewiarygodne pomysły pochodzące wprost z kart fantastyki naukowej:
oto ludzkość wychodzi poza Ziemię i stawia pierwsze kroki w kosmosie!
Pierwszeństwo w wyścigu kosmicznym dawało Rosjanom poczu-
cie dumy i przekonanie o nieograniczonych możliwościach ich systemu.
Było im w stanie wynagrodzić wyrzeczenia i biedę codziennego życia. Dla
Amerykanów niepowodzenia w tymże wyścigu oznaczały z kolei upoko-
rzenie i zwątpienie w przyszłość ich kraju, a zwątpienia tego nie był im
w stanie zrównoważyć ani codzienny dobrobyt, ani powszechne swobody
demokratyczne.
W tej politycznie i psychologicznie trudnej sytuacji, w maju 1961 roku,
niespełna sześć tygodni po locie Gagarina, amerykański prezydent John
Fitzgerald Kennedy podejmuje inicjatywę, która zmierza do odwrócenia nie-
korzystnego biegu zdarzeń. Ogłasza przed amerykańskim Kongresem nowy,
narodowy program. „Nasz wielki kraj – mówi Kennedy – musi być stać na to,
by do końca tej dekady, dekady lat sześćdziesiątych, Amerykanie wylądowali
na Księżycu, a następnie bezpiecznie powrócili na Ziemię”.
Było to dalekowzroczne posunięcie. Miało w sobie wielkość, ambicję
i dumę, tak potrzebne w tym czasie Ameryce. Stawiało jasno sprecyzowany,
ale bardzo trudny cel, do osiągnięcia którego należało zaprząc wiele gałęzi
nauki i techniki. Pilnie potrzebne były nowe badania podstawowe i metody
teoretyczne, nowe technologie i nowe materiały z bardzo wielu dziedzin: od
matematyki, fizyki i astronomii, poprzez aerodynamikę i technikę rakietową
– do elektroniki, telekomunikacji, automatyki i medycyny. Rząd USA prze-
znaczył znaczne sumy na finansowanie badań naukowych oraz prac badaw-
czo-rozwojowych i cała dekada lat sześćdziesiątych stała się okresem badań
i bardzo intensywnego rozwoju nowych technologii.
Jak dziś wiemy, program zainicjowany przez Johna F. Kennedy’ego
zakończył się pełnym sukcesem. Istotnie, 20 lipca 1969 roku, po cztero-
dniowym locie, załoga misji Apollo 11 stanęła na powierzchni Księżyca,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
5 Wspomniał o tym jedynie wielki wizjoner techniki, Arthur C. Clarke. Już w po-
czątku lat sześćdziesiątych przewidywał on znaczny rozwój telekomunikacji, któ-
ry doprowadzi do tego, że kiedyś ludzie będą się porozumiewali łatwo ze sobą,
niezależnie od miejsca, w którym się aktualnie znajdują.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
nym środkiem służącym realizacji tego celu. To już znacznie późniejsza hi-
storia, ale pamiętajmy, że wszystko zaczęło się w roku 19696.
Dziś, z perspektywy kilkudziesięciu lat wyraźnie widać, jak ważne
dla informatyki były lata sześćdziesiąte XX wieku. Pisaliśmy tu głównie
o pierwszych układach scalonych, minikomputerach i zaczątkach sieci kom-
puterowych, ale przecież z tamtych lat pochodzi i kod ASCII, i notacja BNF,
i metody projektowania układów logicznych, i programowanie strukturalne,
i język programowania ALGOL (a wkrótce potem Pascal), i system ope-
racyjny UNIX, i koncepcja pamięci wirtualnej, i IBM System/360, i wiele
innych idei, które odnajdujemy w rozwiniętej i ulepszonej formie w sprzęcie
i oprogramowaniu współczesnych systemów komputerowych.
7 Miał on 16-bitowy procesor Intel 8088 (ok. 30 000 tranzystorów, zegar 4,77 MHz),
pamięć (w wersji standardowej) 64 kB, monochromatyczny monitor i dwie stacje
dyskietek.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Od internetów do Internetu
Równolegle postępował rozwój sieci komputerowych. Obok sieci ARPA (i czę-
ściowo w połączeniu z nią) zaczęły się szybko rozrastać sieci przede wszystkim
uczelniane i międzyuczelniane, których pierwotnym zadaniem było udostępnie-
nie ośrodkom naukowym i akademickim możliwości zdalnego korzystania z su-
perkomputerów, zainstalowanych w kilku centrach na terenie USA. Wkrótce
militarną część sieci wydzielono (m.in. ze względów bezpieczeństwa), a w roku
1983 upubliczniono protokół komunikacyjny TCP/IP obowiązujący w sieci
ARPA. Uporządkowano też system nazw domen sieciowych (.org, .gov, .mil,
.edu, .com, .net itd.) oraz nadawanie adresów w sieci.
Podobne procesy przebiegały również w Europie, a potem w pozo-
stałej części świata. Oddzielne sieci komputerowe łączyły się stopniowo ze
sobą. Sieć łączącą kilka sieci określano terminem internet (od ang. intercon-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
nection network). Tak więc początkowo takich internetów było wiele, ale
niedługo trzeba było czekać na połączenie się ich wszystkich w jedną, glo-
balną całość: Internet, już jeden, pisany wielką literą. Lata osiemdziesiątych
XX wieku były więc czasem tworzenia się i porządkowania podstawowej
technicznej infrastruktury tej ogólnoświatowej sieci.
Początkowo cywilne sieci komputerowe miały służyć wyłącznie ce-
lom naukowym, Były one finansowane z państwowych środków na badania
i ich wykorzystywanie do celów komercyjnych było wręcz prawnie zaka-
zane. Ograniczenie to zniesiono dopiero na początku lat dziewięćdziesią-
tych. W tym samym czasie, w roku 1990, Timothy Berners-Lee z ośrodka
badań jądrowych CERN w Szwajcarii zaproponował opracowanie i wdro-
żenie w Internecie usługi WWW (ang. World Wide Web, dosłownie ogól-
noświatowa pajęczyna). Umożliwia ona publikowanie w sieci informacji
w postaci stron WWW (ang. web page), które mogą zawierać zarówno tekst
i grafikę, jak dźwięk i animację. Ideę tę szybko wdrożono i dekada lat
dziewięćdziesiątych stała się czasem lawinowego wzrostu informacyjnej
zawartości światowej sieci, powstawania konkurencyjnych wyszukiwarek,
nowych internetowych usług, e-handlu, nowych form rozrywki, wymiany
filmów i nagrań dźwiękowych...
Nie można zapomnieć także o równoległym, równie lawinowym roz-
powszechnianiu się telefonii komórkowej, najpierw analogowej, a potem
cyfrowej, która też jest niczym innym, jak rozproszoną siecią komputerową
opartą na zasadzie przesyłania pakietów. Również i w tym przypadku decy-
dującą rolę odegrało opracowanie technologii masowej produkcji układów
scalonych. Bez nich – nie nosilibyśmy w kieszeni małego aparaciku, zawie-
rającego wewnątrz kilka procesorów, z których każdy ma moc obliczeniową
równą zapewne paru tysiącom ENIAC-ów.
Wszystko to razem zapoczątkowało zjawisko, które nazywamy rewo-
lucją informacyjną przełomu XX i XXI wieku.
się działo – nic nie było oczywiste. Nic się nie musiało zdarzyć. Nie było
wiadomo, ku czemu to wszystko zmierza i jak się zakończy. Zimnowojenna
rywalizacja na polu militarnym, gospodarczym i politycznym była bardzo
daleka od rozstrzygnięcia. Ani tempa postępu technologicznego, ani poli-
tycznego kształtu dzisiejszego świata, ani społeczeństwa informacyjnego
nie umiał przewidzieć wtedy nikt.
Początkowo nic nie wskazywało na to, by szala rywalizacji przechyla-
ła się na korzyść krajów zachodnich. Przeciwnie, w latach sześćdziesiątych,
gdy powstawały pierwsze układy scalone i pierwsze minikomputery, Stany
Zjednoczone były wstrząsane konfliktami rasowymi i politycznymi zabój-
stwami: prezydenta Johna Kennedy’ego w 1963 roku, jego brata Roberta
oraz pastora Martina Luthera Kinga w 1968 r. W roku 1965 USA zaan-
gażowały się militarnie w antykomunistyczną wojnę w Wietnamie, Laosie
i Kambodży. Reportaże i bezpośrednie transmisje telewizyjne wkrótce uka-
zały światu mroczne strony tej wojny i na całym świecie rozpoczęły się
antywojenne i antyamerykańskie protesty.
W tym samym czasie, gdy zaczęto budować minikomputery – zachodni
świat przeżywał powstawanie nowej subkultury młodzieżowej, z całą jej nową
obyczajowością, modą, rock and rollem, festiwalem w Woodstock i barwnym
stylem życia8. Miała ona nie tylko antywojenny charakter (przypomnijmy
choćby hasło ówczesnych hippies: make love not war – kochajcie się, zamiast
wojować), ale także kwestionowała podstawowe zasady, na których opierał
się zachodni system ekonomiczny i polityczny.
W roku 1968 przez środowiska młodzieżowe i studenckie zarówno
USA, jak zachodniej Europy przetoczyła się fala antywojennych i antysys-
temowych demonstracji, które – zwłaszcza we Francji – miały zabarwie-
nie mocno lewicowe i antyamerykańskie. W tym samym roku zupełnie inne
niepokoje miały miejsce po drugiej stronie żelaznej kurtyny: w ówczesnej
Czechosłowacji usiłowano zmodyfikować styl działania rządzącej partii ko-
munistycznej, wprowadzając socjalizm o ludzkiej twarzy. Zakończyło się to
interwencją wojsk Układu Warszawskiego i przywróceniem dotychczaso-
wych porządków. Zimna wojna trwała więc nadal i nic nie wskazywało na
to, by blok komunistyczny tracił jakieś punkty w tej rywalizacji.
Choć misja Apollo 11 zakończyła się powodzeniem, a na Ziemi za-
częto produkować układy LSI – początek lat siedemdziesiątych również
nie przyniósł większych politycznych zmian. Co gorsza, nagłe zwiększenie
cen ropy naftowej przez organizację OPEC (zrzeszającą kraje produkujące
ropę) spowodowało w latach 1973–74 kryzys naftowy, który mocno ugo-
dził uzależnione od tego surowca gospodarki zachodnich krajów. Na morale
Amerykanów na pewno nie wpłynęło dobrze odejście w niesławie z urzędu
prezydenta Richarda Nixona (w 1974 roku, w wyniku afery Watergate) oraz
upokarzające wycofanie się z Wietnamu (w 1975 roku).
Jednak zapoczątkowany w latach sześćdziesiątych proces przy-
spieszonej modernizacji zachodniej gospodarki był obiektywnym faktem.
Postępował on nieuchronnie, choć na co dzień przesłaniały go wspomniane
wyżej polityczne i obyczajowe wydarzenia znacznie bardziej spektakularne
i ciekawsze dla szerokiej publiczności.
Jednocześnie, kraje zachodnie dość skutecznie utrudniały przepływ
nowych technologii do państw obozu komunistycznego. Już od początku
zimnej wojny działał na Zachodzie międzypaństwowy komitet koordyna-
cyjny (zwany w skrócie CoCom), który prowadził listę urządzeń i techno-
logii (nie tylko komputerowych), których ze względów strategicznych nie
wolno było sprzedawać do krajów bloku wschodniego. Znajdowały się na
niej w swoim czasie i kolejne nowe układy scalone, i komputery o pamięci
większej niż 256 kB, i monitory ekranowe, i mysz komputerowa...
Nie trzeba dodawać, że najgorliwszymi czytelnikami listy CoComu byli
rezydenci wschodnich wywiadów, którzy dokładali wszelkich starań, aby te
obłożone embargiem urządzenia i technologie wykraść lub kupić przez łań-
cuszek pośredników. Odnosili w tym oczywiście pewne sukcesy, jednak od
zdobycia dokumentacji czy wzorców urządzeń do ich skopiowania i opraco-
wania własnej technologii upływało zwykle kilka lat. W przypadku elektroniki
i techniki cyfrowej oznaczało to całą epokę.
Dobrym przykładem były choćby losy Jednolitego Systemu
Elektronicznych Maszyn Cyfrowych (w skrócie JS EMC) Riad, który miał
skomputeryzować gospodarki wszystkich krajów obozu socjalistycznego.
Miała to być dokładna (lecz „socjalistyczna”) kopia całego systemu maszyn,
którego produkcję korporacja IBM zaanonsowała w roku 1964.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Fot. 15.1. John von Neumann przy komputerze w Institute for Advanced Study.
Fotografia autorstwa Alana Richardsa, ze zbioru Shelby White and Leon Levy Archive
Center, Institute for Advanced Study, Princeton, NJ, USA
123, 52 + 432,152 .
z = x 2 + y 2,
musieli wykonać tysiąc (czy dziesięć tysięcy razy) dla tysiąca (czy dziesię-
ciu tysięcy) par liczb x i y. Zapewne trwałoby to dziesiątki godzin, a dodat-
kowo my na skutek znużenia wielokrotnie pomylilibyśmy się w operowaniu
tymi kilkunastoma przyciskami.
To właśnie potrzeba automatyzacji wielokrotnie powtarzanych ob-
liczeń numerycznych skłaniała matematyków i inżynierów do budowania
coraz bardziej złożonych maszyn liczących. Przypomnijmy, że już Charles
Babbage planował wykorzystanie swojej maszyny analitycznej przede
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
to, czy zawartość rejestru A jest w tej chwili równa zeru. Jeżeli badany wa-
runek jest spełniony – to rozkaz wykona się, w liczniku rozkazów znajdzie
się zero i w rezultacie nastąpi skok do rozkazu o adresie 0. Jeśli jednak wa-
runek nie jest spełniony, to instrukcja 150 kończy się bez zmiany zawartości
PC. Ponieważ zaś w chwili wykonywania instrukcji nr 150 zawartość liczni-
ka PC wynosi już 151, to w tym przypadku następny rozkaz zostanie pobra-
ny właśnie spod adresu 151. Oznacza to możliwość rozgałęzienia programu:
albo skok do adresu 0 (jeśli warunek jest spełniony), albo w przeciwnym
przypadku – kontynuacja dotychczasowej sekwencji adresów.
Operowanie zawartością licznika rozkazów daje możliwość tworzenia
praktycznie dowolnie złożonych struktur programu. Prosty przykład poka-
zany jest na rysunku 15.5. Z lewej jego strony widzimy sieć działań pew-
nego algorytmu, narysowaną tak, jak to wielokrotnie robiliśmy w poprzed-
nich rozdziałach. Nie wnikajmy w to, czemu ten algorytm służy: teraz dla
nas ważne jest jedynie to, że składa się on z trzech sekwencyjnych części,
powiązanych tak jak na rysunku.
w pętlę, składającą się z części 2 oraz 1 i krąży w niej dopóty, dopóki przy
jej kolejnym obiegu warunek C nie okaże się wreszcie spełniony.
Przyjmijmy dla przykładu, że po przetłumaczeniu na język maszy-
nowy pierwsza część algorytmu składa się – powiedzmy – z 25 rozkazów,
umieszczonych w kolejnych komórkach o numerach od 125 do 149. Podobnie
pozostałe dwie części: ich rozmiary i adresy są dopisane przy trzech od-
powiednich fragmentach sieci działań. Proszę sprawdzić, że umieszczenie
w komórkach 150 i 166 dwóch instrukcji skoku wiąże ze sobą poszczególne
części algorytmu dokładnie tak, jak nakazywała sieć działań.
To oczywiście tylko ilustracja. Dla prostoty przykładu operowaliśmy
tu konkretnymi, wymyślonymi ad hoc adresami skoku (adres 167 w rozka-
zie 150, adres 125 w rozkazie 166 itd.), ale wiemy już, że nie muszą być one
z góry zadane w rozkazie: mogą być one wynikiem pomocniczych obliczeń,
poprzedzających ewentualne zapisanie nowej zawartości do licznika PC.
Wprowadzenie licznika instrukcji oraz opisana kolejność czynności
w ramach cyklu rozkazowego bardzo sprawnie rozwiązują problem warun-
kowych rozgałęzień i pętli w programie, a nawet pozwalają konstruować
programy, które są w stanie wpływać na swój dalszy ciąg. Już to wystarczy-
ło, by nadać maszynom z pamiętanym programem zupełnie nową jakość,
nieosiągalną dla maszyn zewnętrznie programowanych.
Pozostaje do omówienia trzeci pomysł, a mianowicie sprawa sposobu
kodowania rozkazów maszynowych.
Zauważmy, że już w rozwiązaniu z rysunku 15.3 sami mimowol-
nie zastosowaliśmy pewien rodzaj dwójkowego kodowania instrukcji.
Rzeczywiście, programując każdą instrukcję, dokonujemy szeregu dwój-
kowych wyborów: w każdy z otworów na pionowej linii możemy albo
wetknąć wtyczkę, albo nie. Trzeciej możliwości nie ma, tertium non da-
tur, jak w rachunku zdań czy algebrze Boole’a. Tak, to jest swego rodza-
ju układ przełączający. O takich mówił kilka lat wcześniej w dyplomowej
pracy Claude Shannon.
Inna rzecz, że ten sposób kodowania – choć uzasadniony mechanicz-
ną konstrukcją panelu sterowania – jest z logicznego punktu widzenia nie-
oszczędny i mało pomysłowy. Pójdźmy więc za sugestią Shannona i popa-
trzmy na problem kodowania rozkazów od strony logiki, a nie mechaniki.
W modelu z rysunku 15.3 mieliśmy do zakodowania 17 operacji plus
pewną liczbę adresów komórek, zależną od rozmiaru pamięci. Teraz lista
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
operacji jest nieco dłuższa, ponieważ dodaliśmy jeszcze kilka innych, zwią-
zanych z licznikiem rozkazów. Jeśli jednak przyjmiemy, że lista operacji nie
będzie przekraczała np. 32 pozycji i zastosujemy proste dwójkowe kodowa-
nie elementów takiej listy – to do jednoznacznego zapisania każdej operacji
wystarczy jedynie pięciobitowy kod, nazywany częścią operacyjną rozkazu
(ang. opcode). W układzie sterowania trzeba będzie tylko umieścić binar-
ny dekoder, który otrzymawszy na wejściu pięciobitowy kod części opera-
cyjnej – wyprodukuje jedynkę na odpowiednim wyjściu, jednym z 32 wyjść
sterujących arytmetycznymi podzespołami maszyny.
Podobnie możemy postąpić z adresami. Są to liczby naturalne, wy-
starczy więc zastosować najprostszy dwójkowy kod NKB, w którym za po-
mocą n-bitowych kodów możemy jednoznacznie ponumerować 2n komórek
pamięci. W rezultacie, gdyby nasza maszyna miała na przykład 211 = 2048
komórek pamięci, wtedy cały rozkaz maszynowy zmieściłby się w 16 bi-
tach i składałby się z 5-bitowej części operacyjnej oraz 11-bitowej części
adresowej.
To oczywiście rozwiązanie najprostsze, oparte na pierwszym skoja-
rzeniu. Zastanowiwszy się chwilę, dostrzeżemy jeszcze inne możliwości, na
przykład to, że część adresowa jest potrzebna jedynie w rozkazach dotyczą-
cych pamięci. W rozkazach wykonywanych wyłącznie na zawartości reje-
strów procesora miejsce przewidziane na część adresową można wykorzy-
stać w inny sposób, np. na rozszerzenie repertuaru operacji lub na wprowa-
dzenie możliwości adresowania (za pomocą kilkubitowych kodów) owych
wewnętrznych rejestrów procesora. W rozkazach wejścia i wyjścia część
adresową można by wykorzystać do wskazywania numeru urządzenia ze-
wnętrznego i rodzaju operacji, która ma być w tym urządzeniu wykonana.
Z kolei w rozkazach dotyczących pamięci, w części adresowej nie-
koniecznie trzeba podawać bezpośredni, numeryczny adres. Można tam za-
wrzeć umowny kod tzw. trybu adresowania (ang. addressing mode), to zna-
czy wskazać sposób wyliczania tego adresu itd. Rozkazy maszynowe mogą
mieć więc kilka różnych formatów, zależnych od rodzaju operacji.
Nie będziemy tu rozwijać tego tematu. Ale na takiej właśnie zasadzie
są kodowane instrukcje maszynowe we wszystkich komputerach od czasów
von Neumanna aż do dziś. Dla każdego typu procesora istnieje ustalona lista
rozkazów (ang. instruction set), licząca (w dzisiejszych komputerach) od
kilkudziesięciu do stu kilkudziesięciu czy nawet kilkuset pozycji. Definiuje
ona repertuar operacji, jakie potrafi rozpoznać i wykonać dany procesor.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Otwierało się więc rozległe pole zupełnie nowych problemów, na którym do-
piero trzeba będzie wytyczać ścieżki i ustawiać drogowskazy. Wiadomo było
jednak, że wszystko to, co będzie potrzebne – prawdopodobnie da się zrobić
przy wykorzystaniu algebry Boole’a i układów logicznych. Dostrzegł to już
i rozgłosił dyplomant studiów magisterskich w MIT, Claude Shannon,
Z drugiej strony, wiadomo było także, iż techniczna realizacja dwój-
kowych podzespołów: zarówno pamięci, jak układów arytmetyki i układu
sterowania powinna być łatwiejsza niż ich dziesiętnych odpowiedników.
Doświadczenia z elektronicznymi układami impulsowymi już wówczas
wskazywały na to, że układy o dwóch stanach (jak choćby znany już wte-
dy przerzutnik Eccles–Jordana) buduje się łatwiej niż elektroniczne układy
dziesięciostanowe. Przewidywania te wkrótce okazały się słuszne, a dwój-
kowy charakter wewnętrznego języka maszyny zdecydował w rezultacie
o niezwykłej uniwersalności komputerów.
Można sobie wyobrazić, że postulat konsekwentnego zastosowania
dwójkowych zasad kodowania wszelkiej informacji budził początkowo
u niektórych intelektualny opór. Jak to, mamy zrezygnować z dziesiętnej
arytmetyki, którą wszyscy znają od przedszkola? A któż się będzie bawił
w przekładanie wszystkich danych na postać dwójkową? Kto będzie umiał
interpretować dwójkowe wyniki? Jaki będzie pożytek z dwójkowych tablic
wyników, których i tak nikt nie będzie umiał odczytać?
Odpowiedź jest prosta: konwersją (przekształcaniem) z systemu dzie-
siętnego na dwójkowy i odwrotnie będzie się zajmował sam komputer: jego
sprzęt i oprogramowanie. Człowiek może (skoro tak lubi) dalej posługiwać
się systemem dziesiętnym. Trzeba tylko zbudować dziesiętną klawiaturę,
która – wyposażona w odpowiedni koder – będzie przerabiała naciśnięcie
każdego klawisza na odpowiadający mu kilkubitowy dwójkowy kod, a tak-
że opracować program wprowadzający, który będzie dokonywał konwersji
ciągu cyfr dziesiętnych (tworzących liczbę) – na postać dwójkową.
Podobnie trzeba opracować program wyprowadzający, który rozłoży
dwójkowy wynik obliczeń na ciąg kilkubitowych kodów cyfr dziesiętnych,
a także zbudować drukarkę z odpowiednim dekoderem. Otrzymawszy na
wejściu binarny kod dziesiętnej cyfry – dekoder uruchomi czcionkę z od-
powiednim graficznym znakiem. Użytkownik w ogóle nie musi więc wie-
dzieć, że jego dane są wewnątrz komputera pamiętane i przetwarzane w po-
staci dwójkowej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
była już wcześniej oczywista. Jak już wyżej wspomnieliśmy, jego zadaniem
była na przykład konwersja ciągów binarnie zakodowanych cyfr dziesięt-
nych na liczby dwójkowe reprezentujące dane. Ale skoro taki program już
był, to można było go rozbudować tak, by oprócz danych wczytywał rów-
nież sam tekst programu, przekształcając ciągi znaków z arkusza progra-
mowania na kolejne rozkazy maszynowe i zapisując je do pamięci. Niejako
przy okazji można było wprowadzić wiele ułatwień dla programisty.
Taki program wczytujący programy użytkowe nazwano asemblerem
(od ang. to assembly: składać, kompletować). Jest to najstarsza i najprostsza
formą translatora, czyli programu tłumaczącego napisany przez człowieka
tekst programu użytkowego na wewnętrzny język maszyny. Programista
projektuje program z instrukcji maszynowych i koduje je za pomocą znaków
dostępnych na klawiaturze. Asembler odczytuje kolejne znaki i stopniowo
składa z nich instrukcje maszynowe (już w postaci dwójkowej), a z kolej-
nych maszynowych instrukcji kompletuje cały program maszynowy.
Pierwszym i najbardziej oczywistym ulepszeniem asemblera było wpro-
wadzenie łatwiejszych do zapamiętania (mnemotechnicznych) skrótów nazw
operacji zamiast binarnych kodów części operacyjnej. Można na przykład
umówić się, że ADD oznacza dodawanie (ang. addition), SUB – odejmowanie
(ang. subtraction), JMP – skok bezwarunkowy (ang. jump) itd. – i na arkuszu
programowania używać tego typu skrótów, zamiast uczyć się szczegółowych
zero-jedynkowych kodów odpowiadających tym operacjom. Natrafiwszy na
ADD, SUB czy JMP – asembler rozpoznawał te grupy znaków i wstawiał do
części operacyjnej kompletowanego rozkazu odpowiedni dwójkowy kod.
Następnym krokiem było wprowadzenie adresów symbolicznych.
Początkowo programista posługiwał się adresami bezwzględnymi, to zna-
czy po prostu numerami komórek pamięci. Ulepszono więc assembler tak,
by programista mógł na arkuszu programowania obok (lub zamiast) numeru
wiersza dopisać wymyśloną przez siebie kilkuliterową (najczęściej również
mnemotechniczną) nazwę (inaczej – etykietę, ang. label) tego rozkazu lub
jednostki danych. Asembler sam rozmieszcza w kolejnych komórkach roz-
kazy, zapisane w kolejnych wierszach arkusza, może więc przy okazji zapa-
miętać, jakiemu adresowi dana nazwa odpowiada. Dzięki temu programista
nie musiał już liczyć komórek pamięci i mógł (np. w rozkazach skoków czy
przy odwoływaniu się do danych) posługiwać się tak wprowadzonymi przez
siebie adresami symbolicznymi. Asembler sam dbał o to, by zostały one
przetworzone na właściwe adresy rzeczywiste.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
4 Szybko pojawił się pomysł, że jeśli pojemność pamięci przekracza owo 2n, to
można adres przekazywać w dwóch krokach (kolejno „w dwóch kawałkach”), po
tych samych liniach adresowych.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
czy to aby nie jej właśnie poszukuje nowy master. Jedna spośród jedno-
stek śledzących stan linii adresowych stwierdza, że adres odnosi się do niej
i grzecznie sama zgłasza się jako slave. Pobiera wtedy adres z linii adreso-
wych, lokalizuje odpowiednią komórkę i odczytuje jej zawartość na linie
danych magistrali. Master (w tym przypadku CPU) kopiuje zawartość słowa
z linii danych do jednego ze swoich wewnętrznych rejestrów i zwalnia sys-
temową szynę. Interakcja się kończy.
Operacja zapisywania do pamięci RAM przebiega podobnie, z tym
że procesor, oprócz adresu, ustawia także od razu na liniach danych binarną
zawartość, która ma być zapisana do wskazanej komórki, a na odpowied-
niej linii sterującej wskazuje, że chodzi o zapisanie (nie zaś odczytanie).
Podobnie jak poprzednio, jedna z jednostek RAM sama zgłasza się jako
slave i zapisuje zawartość z linii danych do komórki o adresie podanym
na liniach adresowych. Po potwierdzeniu, że czynność ta została wykonana
(lub po umówionym czasie), procesor zwalnia magistralę i rozpoczyna się
nowy cykl szyny.
Standard magistrali określa także dopuszczalny czas oczekiwania na
zgłoszenie się slave (ang. timeout). Jeżeli przez ten czas odpowiedź ze stro-
ny poszukiwanego sługi nie nadejdzie – oznacza to poważną awarię sprzętu
komputera: brak, wyłączenie lub uszkodzenie co najmniej jednej jednost-
ki w systemie. Podobnie jest określony maksymalny dopuszczalny czas, po
którym master musi zwolnić magistralę. Jeśli tego nie zrobi – sygnalizowa-
na jest awaria.
Kolejna sytuacja, przedstawiona na rysunku 16.2b, ma miejsce naj-
częściej w przypadku inicjowania przez procesor operacji wejścia-wyjścia,
która ma być wykonana w urządzeniu zewnętrznym, zarówno znakowym,
jak i pracującym w trybie DMA.
Jednostka sterująca każdego urządzenia zewnętrznego jest w istocie
mniejszym, specjalizowanym procesorem, zdolnym do samodzielnego wy-
konywania całkiem złożonych operacji. Nie muszą to być wprawdzie dzia-
łania arytmetyczne czy logiczne, z których można by zestawiać uniwersalne
algorytmy, niemniej sama obsługa mechanizmów danego urządzenia wyma-
ga dość złożonych sekwencji działań.
Dla wykonywania swoich operacji jednostka sterująca każdego urzą-
dzenia ma zwykle kilka własnych, lokalnych rejestrów. Jest wśród nich
zapewne rejestr sterujący, który przechowuje kod operacji zleconej przez
CPU. Inny rejestr zapewne przechowuje samo słowo danych, przenoszone
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
7 W przyzwoitej pamięci dyskowej dysk obraca się z prędkością rzędu 100 obrotów
na sekundę. W takim przypadku, szybkość transferu danych wynosiłaby zaledwie
100 słów, a więc – w naszym przykładzie – 3200 bitów (400 bajtów) na sekundę.
To śmiesznie mało.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
17. Procesor
stać się masterem magistrali itd. – ale przed chwilą umówiliśmy się, że nie
będziemy się tym zajmowali.
To, co znajduje się wewnątrz procesora, można umownie podzielić na
trzy wzajemnie powiązane części:
– Pierwsza z nich – to jednostka arytmetyczno-logiczna (w skrócie: aryt-
mometr, oznaczony jako JAL, z angielska – ALU), grupa kilku (tutaj
– ośmiu) uniwersalnych rejestrów (RU), rejestr adresowy pamięci (RAP),
rejestr buforowy pamięci (RBP) oraz drogi przesyłania danych między
nimi, mające na rysunku postać cieńszych i grubszych kresek.
– Drugą część stanowi układ sterowania procesora, zaznaczony jedynie
bardzo schematycznie. Jego główną częścią jest synchroniczny układ
sekwencyjny (sekwenser), czyli automat skończony, który produkuje na
swoim wyjściu pewne sekwencje sygnałów wyjściowych. Do wejść au-
tomatu należy między innymi dekoder, czyli układ logiczny dekodują-
cy zawartość Rejestru Instrukcji (RI). Sam Rejestr Instrukcji (RI) będzie
przechowywał jedną instrukcję maszynową: tę, którą procesor właśnie
wykonuje. Uproszczony graf automatu sterującego (a przynajmniej zasa-
dę jego budowy) poznamy za chwilę.
– Trzecia część procesora w ogóle nie jest pokazana na rysunku 17.1. Jest
nią układ przerwań (ang. interrupt system). To bardzo ważna część pro-
cesora, ponieważ jego działanie buduje pomost pomiędzy sprzętem (har-
dware) procesora a funkcjami, realizowanymi przez oprogramowanie
systemowe. Tym zagadnieniem zajmiemy się w rozdziale 18, kiedy bę-
dziemy już znali możliwości i zasady działania pierwszych dwóch części
procesora.
Podzespoły, które zaliczyliśmy umownie do pierwszej grupy, kon-
centrują się wokół jednostki arytmetyczno-logicznej. Jednostka ta potrafi
wykonywać jedno- i dwuargumentowe operacje arytmetyczne i logiczne na
danych, które u nas mają postać jednego binarnego słowa. Dla ustalenia
uwagi, przyjmijmy (tak jak to rzeczywiście było w procesorze PDP-11),
że zarówno argumenty wejściowe, jak i wynik mają format 16-bitowy. Tak
więc na przykład, chcąc dodać do siebie (arytmetycznie) dwie zakodowane
dwójkowo 16-bitowe liczby, powinniśmy podać jedną z nich na lewe wej-
ście (L) jednostki, drugą – na wejście prawe (P), a jednocześnie na wejście
sterujące arytmometru (symbolizowane tutaj strzałką z napisem s1) dostar-
czyć kilkubitowy kod, stwierdzający, że tym razem chodzi o operację aryt-
metycznego dodawania.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Kto zadał sobie trud przeczytania rozdziału 12, ten wie, co się dalej
dzieje we wnętrzu JAL i skąd na wyjściu arytmometru (oznaczonym jako
W) bierze się wynik tej operacji. Z rozdziału 12 wiemy też, że równo-
legle z wynikiem W produkowane są dodatkowo cztery bity (N, Z, V, C),
opisujące ten wynik. N przyjmuje wartość 1, gdy wynik W jest ujemny
(Negative), Z – gdy W jest równy zeru, V – gdy wynik nie mieści się w za-
kresie (oVerflow), C – gdy powstało przeniesienie (Carry) poza granicę
słowa. Wartości tych bitów są wykorzystywane w decyzjach dotyczących
operacji warunkowych.
Skąd jednak pochodzi lewy i prawy argument operacji wykonywanej
przez JAL? Są to oczywiście zawartości dwóch wybranych rejestrów proce-
sora. W naszym schemacie do wyboru mamy:
– grupę rejestrów (oznaczoną RU), zwanych rejestrami uniwersalnymi
(ang. GPR, General Purpose Registers). Przyjmijmy (też za procesorem
PDP-11), że jest ich osiem i nazywają się one odpowiednio R0, R1, R2,
…, R7;
– Rejestr Buforowy Pamięci (RBP);
– adresowy fragment Rejestru Instrukcji (RI), który jako całość należy lo-
gicznie do układu sterowania.
Czy nie zapomnieliśmy jednak o czymś bardzo ważnym dla samej
istoty koncepcji maszyn von Neumanna z pamiętanym programem? Gdzie
jest rejestr licznika rozkazów (PC), ten, który przechowuje adres następnego
rozkazu maszynowego?
Nie, nie zapomnieliśmy. Rolę rejestru PC odgrywa jeden z rejestrów
uniwersalnych. W PDP-11 był to rejestr R7. Owa uniwersalność rejestrów
RU polega właśnie na tym, że mogą one służyć zarówno do przechowywania
danych biorących udział w operacjach Jednostki Arytmetyczno-Logicznej,
jak i innych informacji, w tym także adresów, a między nimi – zawartości
licznika rozkazów PC1.
Wśród rejestrów uniwersalnych jest zresztą jeszcze jeden, mianowicie
R6, który odgrywa rolę tzw. wskaźnika wierzchołka stosu (SP, ang. Stack
2 Dla uproszczenia i tak skomplikowanego rysunku 17.1 nie pokazano na nim sy-
gnałów sterujących tego typu. Są one doprowadzone do każdego z rejestrów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
potem B, na końcu A. Stąd też nazwa tego typu stosu. LIFO, angielskie
Last In – First Out oznacza właśnie, że „to, co ostatnie weszło – pierwsze
wychodzi”3.
Po prawej stronie, na rysunku 17.4b pokazano, jak można tę zasadę
zrealizować na potrzeby systemu komputerowego. Zarezerwujmy na stos
pewien obszar w pamięci operacyjnej. Przyjmijmy, że „dno” stosu znajdu-
je się – powiedzmy – w komórce o adresie 50004. Obiektami wkładany-
mi na stos będą słowa maszynowe, najczęściej zawierające pewne adresy.
Pierwsze słowo wkładane na stos będzie zapisane w tej właśnie komórce.
Następne wkładane słowo zajmie komórkę 4999, kolejne – 4998 i tak dalej:
stos będzie więc rósł w kierunku coraz mniejszych adresów.
Do wybranego rejestru procesora wpiszmy adres wierzchołka stosu, to
znaczy adres pierwszej wolnej komórki, do której ma być zapisane następne
słowo, jeśli będziemy chcieli je odłożyć na stos. W naszym przykładowym
Zasada przerwań
O zjawisku przerwań (ang. interrupt) wspomnieliśmy już w rozdziale 16,
przy okazji omawiania rysunku 16.2d. Teraz, po zapoznaniu się z działa-
niem procesora, możemy o obsłudze przerwań powiedzieć znacznie więcej.
To bardzo ważna część systemu komputerowego, a jednocześnie najtrud-
niejsza do opisania i najmniej znana. Nie tylko laicy, ale nawet zawodowi
programiści często mają słabe pojęcie o tym zagadnieniu.
Zacznijmy od zrozumiałego dla wszystkich przykładu. Przeciętny
użytkownik komputera przyjmuje za rzecz naturalną, że w każdej chwili
komputer wykonuje wiele czynności na raz. Podczas gdy my surfujemy
po sieci lub piszemy jakiś tekst przy użyciu edytora – komputer „sam” sy-
gnalizuje, że przyszła do nas nowa wiadomość w poczcie elektronicznej.
Zawiadamia nas, że stan baterii w naszym laptopie zbliża się niebezpiecznie
do rozładowania. Gdzieś w rogu ekranu wskazuje czas astronomiczny, z do-
kładnością do minut. Wyświetla niespodziewane komunikaty, na przykład
ostrzegające o zerwaniu połączenia internetowego i tak dalej.
Z drugiej strony wiemy, że jedyne, co komputer „umie” robić – to wy-
konywanie programów, z góry przygotowanych, przetłumaczonych na język
maszynowy i zainstalowanych w jego pamięci operacyjnej. Wygląda więc na
to, że w komputerze działa jednocześnie wiele takich programów: przeglądar-
ka internetowa, aplikacja obsługująca pocztę, oprogramowanie antywirusowe,
program wyświetlający astronomiczny czas i wiele, wiele innych. Niektóre
z nich sami uruchomiliśmy. Istnienia dziesiątek innych nawet nie jesteśmy
świadomi, bo sprowadził je do pamięci RAM system operacyjny w momencie
uruchamiania komputera i to całkiem bez naszego udziału.
Jednak dowiedzieliśmy się też, że nasz jednoprocesorowy komputer
jest w stanie wykonywać naprawdę tylko jeden program na raz, niezależnie
od tego, czy jest to aplikacja biurowa, czy jakiś inny program. Procesor jest
jeden, w procesorze jest tylko jeden licznik rozkazów oraz sprzętowy układ
sterowania, zbudowany tak, by na zmianę pobierał i wykonywał, jedną po
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Taka jest zasada. Czas teraz powiedzieć więcej o sposobie jej reali-
zacji.
Jak wspomnieliśmy, zgłoszenia przerwań są produkowane przez wie-
le układów elektronicznych, które wykrywają zdarzenia wymagające inter-
wencji systemu. Możemy przyjąć, że każde z tych źródeł zgłoszeń ma pe-
wien przerzutnik, który zostaje ustawiony w stan 1 wtedy, kiedy pojawia się
zdarzenie wymagające obsługi.
Jak napisaliśmy już w rozdziale 16, zgłoszenia przerwań napływają
do procesora i arbitra za pośrednictwem kilku linii sterujących należących
do systemowej magistrali. Oznaczyliśmy je tam skrótem BR (Bus Request 1)
z dodatkowym numerem, na przykład BR7, BR6, BR5 itd. Im wyższy numer
– tym wyższy priorytet takiego żądania. Poszczególne źródła zgłoszeń są
łączone do tych linii zgodnie z priorytetem: te najważniejsze – do linii BR7,
mniej ważne – do BR6 i tak dalej.
Do każdej z linii można dołączyć wiele źródeł (o tym samym prio-
rytecie), pochodzących z różnych miejsc systemu. Sposób ich dołączania
odpowiada sumie logicznej. W rezultacie stan (0 albo 1) każdej linii jest
sumą logiczną zgłoszeń na tej linii. Jeżeli na przykład stan linii BR7 od-
powiada logicznej jedynce – znaczy to, że któreś ze źródeł (a może kilka
z nich) o najwyższym priorytecie zgłasza potrzebę przerwania. Jeśli dotyczy
to linii BR6 – to znaczy, że przerwanie zgłasza któreś ze źródeł do niej dołą-
czonych i tak dalej. Wzorując się na rozwiązaniu zastosowanym w PDP-11,
powiedzmy, że są cztery poziomy tak rozumianego priorytetu, a więc cztery
linie zgłoszeń przerwań.
Linie zgłoszeń docierają do układu przerwań w procesorze. Ich stan
(0 albo 1) jest tam najpierw konfrontowany z zawartością Rejestru Maski
Przerwań. To rejestr dla nas nowy, nie wystąpił dotąd w opisie wnętrza pro-
cesora. Jest on programowo dostępny. Znaczy to, że na liście rozkazów języ-
ka maszynowego znajduje się instrukcja, za pomocą której można wpisać do
tego rejestru dowolną binarną zawartość, zwaną – jak łatwo zgadnąć – ma-
ską przerwań. Maska przerwań ma tyle bitów, ile jest linii zgłoszeń. Każdy
z bitów maski jest podawany jako argument na wejście iloczynu logicznego,
którego drugim argumentem jest odpowiadająca mu linia zgłoszeń. Wyjścia
1 W wielu procesorach są one oznaczane po prostu jako IRQ (od ang. Interrupt
ReQuest, żądanie przerwania).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Po pierwsze, należą do nich wartości czterech znanych nam już dobrze, typo-
wych warunków N, Z, V, C, wytwarzanych przez Jednostkę Arytmetyczno-
-Logiczną. Trzeba je zapamiętać, bo może przerywany program właśnie
wykonał jakąś operację arytmetyczną i za chwilę zamierzał zbadać któryś
z tych warunków przy okazji np. skoku warunkowego. Teraz tego nie zrobi,
bo został przerwany, ale będzie mógł to zrobić po wznowieniu, gdyż warun-
ki te zostaną zapamiętane i w przyszłości wrócą na swoje miejsce.
Po drugie, w słowie stanu procesora zapamiętuje się zawartość
Rejestru Maski Przerwań. Jest to ważny atrybut przerywanego programu,
określający jego pozycję w hierarchii przerwań. Po wznowieniu ta część
słowa wróci też na swoje miejsce i program będzie tak samo wrażliwy na
jedne, a niewrażliwy na inne przerwania, jak poprzednio.
Trzecim elementem słowa stanu procesora jest jednobitowy wskaź-
nik, który tu nazwaliśmy WSU, wskaźnik system-użytkownik (ang. system-
-user flag). Jego rola wymaga dodatkowego wyjaśnienia.
Rzecz w tym, że programy (procesy) systemowe mają większe upraw-
nienia niż aplikacje (programy użytkowe). Niektóre instrukcje, występujące
na liście rozkazów maszynowych, mogą pojawiać się wyłącznie w progra-
mach systemowych, natomiast dla zwykłych aplikacji ich użycie jest zabro-
nione.
Przykładem mogą być same instrukcje zapisywania Rejestru Maski
Przerwań oraz włączania i wyłączania wskaźnika blokady przerwań. Cały
system przerwań byłby przecież na nic, gdyby każdy użytkownik mógł go
sobie wyłączyć, ustawiając wskaźnik blokady przerwań na zero. Jego pro-
gram byłby wtedy całkowicie nieprzerywalny, a komputer nie reagowałby
na żadne zgłoszenia przerwań, nawet na zdarzenia awaryjne o najwyższym
priorytecie. Użytkownik nie może manipulować również przy masce prze-
rwań. Gdyby mógł – na pewno znalazłby się ktoś, kto na przykład zama-
skowałby (omyłkowo albo choćby dla zabawy, jak ci, którzy tworzą wirusy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Programy i procesy
Od kilkudziesięciu już lat informatycy obok pojęcia programu posługują się
pojęciem procesu. Nie są to pojęcia tożsame. Program jest pewnym statycz-
nym wzorcem postępowania, przepisem, tekstem, który wymienia kolejne
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
1 We wspomnianej maszynie IBM 7030 taki układ sterujący nazywał się look-ahe-
ad unit, a więc było to sterowanie „z patrzeniem w przód” czy też „wybieganiem
w przyszłość”. Dobrze to oddaje istotę pomysłu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
3 Dodajmy dla porządku, że ten termin nie ma teraz nic wspólnego z pamięcią
rdzeniową (core memory), o której wspomnieliśmy w rozdziale 14.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
dziesiątki, a nawet setki tysięcy (!) procesorów. Mogą to być seryjnie produ-
kowane procesory wielordzeniowe, takie o jakich mówiliśmy wyżej. Bardzo
się rozwinęły również metody programowania równoległego. Można się
więc spodziewać, że doświadczenia z użytkowaniem systemów wieloproce-
sorowych zaczną się wkrótce przenosić na zwykłe komputery.
Zapewne niezadługo będziemy mieli w domu nie tylko komputer
z wielordzeniowym mikroprocesorem, lecz jeszcze bardziej rozbudowany
wieloprocesorowy system, z zupełnie nowym oprogramowaniem – zarówno
systemem operacyjnym, jak i aplikacjami – wykonanym zgodnie z zasadami
programowania równoległego. Będzie on wielokrotnie szybszy niż ten, któ-
rego używamy obecnie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Zakończenie
Skorowidz nazwisk
Skorowidz rzeczowy
A Architektura
— harwardzka 349
Adres 381, 419
— von Neumanna 349
— bezwzględny 393 Argumenty
— symboliczny 393 — aktualne 81, 83
Akcelerator graficzny 465 — formalne 81
Aksjomat języka 194 ARPA (ARPANET) 360, 367
Alfabet 120, 122, 190 Asembler 393
— dwójkowy (binarny) 235 Automat
— Morse’a 260 — abstrakcyjny 329
— wejściowy (automatu) 220 — deterministyczny 226
— wyjściowy (automatu) 220 — Mealy’ego 221
Algebra Boole'a 20, 276, 283, 289, 359, 388 — Moore’a 221, 329
ALGOL 362 — niedeterministyczny 225
Algorytm 12, 13 — niezupełny 224
— Espresso 340 — Rabina–Scotta 227
— ewolucyjny 71, 103 — skończony 19, 210, 217, 327
— FFT 150, 158 — zakodowany 331
— genetyczny 103 — ze stosem 210
— heurystyczny 71, 76 — zupełny 224
— Monte Carlo 147
— probabilistyczny 71 B
— rekurencyjny 71, 78, 87 Badania kosmiczne 357
— własności 29 Bajt 241
— zachłanny 71, 73 Bariera
Algorytmiczne tłumaczenia 211 — cieplna 466
Altair 8800 364 — niezawodności 350
Alternatywa 275 Bezpośredni dostęp do pamięci 413
Alternatywa wykluczająca 276 Bit 240
Analiza — najbardziej znaczący 240
— Fouriera 144 — najmniej znaczący 240
— leksykalna 227 — parzystości 263
— składniowa (syntaktyczna) 227 — warunku 307, 422
Apollo (program lotów kosmicznych) 125, — znaku 245
356, 358 Blokada przerwań 444, 448
Apple 365 BMP – podstawowa płaszczyzna
Arbiter 402, 407 wielojęzyczna 268
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Glif 269 J
Graf Jądro systemu operacyjnego 450
— nieskierowany 48 Jednolity System EMC Riad 370
— pełny 48 Jednostka
— sterowania 164, 231 — arbitrażu 402
Grafofon 126 — arytmetyczno-logiczna (ALU) 303,
Gramatyka 121, 190, 193, 194 309, 359, 421
— kombinatoryczna 194 — pamięci RAM 404
— uniwersalna 215 — sterująca urządzeń zewnętrznych 404,
Gramofon 126 411
Język 189, 191
H — asemblerowy 395
— bezkontekstowy 209, 227
Hieroglif egipski 268 — kontekstowy 210
Hipoteza Churcha–Turinga 119 — maszynowy 87
Homeostaza 134 — nieskończony 202, 227
— opisu struktur danych 192
I — programowania 14, 192
-- wyższego poziomu 394
IBM (firma) 261, 346, 349, 362
— regularny 210, 227
IBM (komputery)
— skończony 202, 227
— IBM 5150 (IBM PC) 366
— IBM 7030 (Stretch) 456 K
— System/360 362, 371 Kalkulator 362, 378
— System/370 371 Karta
Ideogram Han 268 — dźwiękowa 466
— graficzna 465, 466
Idiomy 201
Karty dziurkowane (perforowane) 343, 346,
Iloczyn
381
— boolowski 289
Kilobajt 241
— elementarny 300 Kod
— kartezjański 220 — ASCII 261, 362
— logiczny 275 — Baudota 260
Implikacja 276 — BCD 252, 265, 311
Instrukcja — detekcyjny 264
— maszynowa 12, 383, 393, 395 — EBCDIC 261
— skoku 386 — Greya 335
— korekcyjny 264
— warunkowa 169
— MKT nr 2 261
Inwerter 297 — Morse’a 260
Inżynieria — NB (NKB) 240, 389
— oprogramowania 399 — sterujący (w ASCII) 262
— systemowa 400 — źródłowy 14, 87, 398
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Koder 311, 391 — systemowa 21, 401, 402, 406, 407, 461
Kolejka 97, 98, 433 — szeregowa 406, 462
Kompresja sygnału 156 Mainframe 360, 371
Koniunkcja 275 Mantysa 254
Kontroler (urządzenia zewnętrznego) 462 Mark I 348, 376
Konwersja Maska przerwań 443, 446, 448, 451, 453
— analogowo-cyfrowa 139, 153 Master-slave 407
— cyfrowo-analogowa 142 Maszyna
Konwerter analogowo-cyfrowy 139
— analityczna 181, 343
Kryptografia 182
— analogowa 119, 132
Krzyżowanie 109, 110
— cyfrowa UMC-1 252
L — księgująca 346, 347, 348
— różnicowa 342
Lampa próżniowa (elektronowa) 127, 284,
— szyfrująca Enigma 182
351
— Turinga 18, 119, 163, 210, 232, 287,
Liczba
441
— pseudolosowa 94
— zewnętrznie programowana 348
— stałoprzecinkowa 254
Maszyny skończenie stanowe 217
— zmiennoprzecinkowa 254
— znormalizowana 256 Megabajt 241
Licznik 323 Metasymbole notacji BNF 196
— dwójkowy 324 Miara dostosowania 106, 107
— programu (zobacz: licznik rozkazów) Mikroinstrukcja 425
384 Mikrooperacje 424
— rozkazów 383, 384, 422, 445, 451 Mikroprocesor 132, 362, 403, 461
LIFO (zobacz: stos LIFO) — wielordzeniowy 467
Lingwistyka matematyczna 19, 136, 189, 395
Mikroprogram 445
Linie (magistrali)
Minikomputer 22, 132, 359
— adresowe 406
Minimalizacja funkcji boolowskich 339
— danych 406
— sterujące 322, 406 Minuteman (program rakiet balistycznych)
354, 358
Lista 26
— nieuporządkowana 32 MS DOS 366
— rozkazów 389 Multiplekser 309, 359, 423
— uporządkowana 32 Mutacja 109, 110
Logika formalna 274
N
M Nadmiar 247
Macintosh 365 Napięcie L i H 295
Magistrala Narzędzia CASE 399
— równoległa 406 Northbridge 462, 465
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782
Tryb Wskaźnik
— adresowania 389, 430 — stosu 422, 434
— pracy procesora 447 — system-użytkownik 446
Twierdzenie o próbkowaniu (Shannona) 142, Współczynnik dostosowania 107
152 www (usługa) 368
Wykładnik (liczby zmiennoprzecinkowej)
U 254
Układ
Z
— kombinacyjny 273
— LSI 22, 363, 370, 403, 456 Zadanie obliczeniowe 12
— MSI 359 Zagnieżdżanie 435, 441
— przekaźnikowy 278 Zagnieżdżone wykonanie funkcji 87
— przełączający 273, 279 Zapis systematyczny, wagowo-pozycyjny 238
— przerwań 421 Zarządzanie zadaniami 438
— scalony 353 Zasada
— sekwencyjny 20, 273, 313 — lokalności odwołań 459
— SSI 359 — wyłączonego środka 274
— sterowania procesora 382, 421, 457 Zbocze (narastające, opadające) 314
— tranzystorowy 352 Zegar 314
— VLSI 402
Zgłoszenie przerwania 416
Ułamek znormalizowany 255
Ziarno 94, 95
Unicode 266
Złożoność
UNIVAC 350
— obliczeniowa 34
UNIX 362 -- czasowa 35
Urządzenie -- pamięciowa 35
— blokowe 405 — wielomianowa 41, 54, 75
— DMA 404 — wykładnicza 41
— zewnętrzne 21, 392, 400, 401 Zmienna
— znakowe 404 — boolowska 289
UTF-16 270 — lokalna 80, 82, 83
UTF-32 270 — losowa 97, 98
UTF-8 270 — zdaniowa 274
Znaki
W — CJK 268
Waga 238 — diakrytyczne 265
Wektor przerwań 445, 451 — sterujące (zobacz: kod sterujący
Widmo (w ASCII))
— mocy sygnału 149
— sygnału 148
Wieże Hanoi 89
Własność stopu 30, 43, 46
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782