Wstęp Do Informatyki Nie Tylko Dla Informatyków - Jerzy Mieścicki

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 482

Norbert Niziołek (norbert.niziolek@gmail.com).

Zamówienie numer: Kamami/073782


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

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ą.

Sekretarz redakcji: mgr Katarzyna Kempista


Redaktor merytoryczny: mgr inż. Mariola Mańkowska
Redaktor techniczny: mgr Delfina Korabiewska

Fotografię autora zamieszczoną na tylnej okładce wykonała Anna Lidderdale

ISBN 978-83-60233-94-8
© Copyright by Wydawnictwo BTC
Legionowo 2013

Wydawnictwo BTC Książki Wydawnictwa BTC


ul. Lwowska 5 w księgarni MARGINES.pl
05-120 Legionowo
fax: (22) 767-36-33
http://www.btc.pl
e-mail: redakcja@btc.pl

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.

Druk i oprawa: Drukarnia Totem S.C. w Inowrocławiu


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Spis treści

Od autora ....................................................................................................................................................... 7

1. O czym będzie traktować ta książka .................................................................................. 11


Od zadania obliczeniowego do jego wykonania przez komputer.............................11
O czym opowiemy.............................................................................................................................. 16

2. O algorytmach i złożoności obliczeniowej na kilku łatwych


przykładach .............................................................................................................................................. 23
Co to właściwie jest „obliczenie”? ............................................................................................ 23
Weź, Jasiu, kajecik i notuj .............................................................................................................. 25
Przeszukiwanie listy nieuporządkowanej i uporządkowanej ..................................... 30
Pojęcie złożoności obliczeniowej (czasowej). Notacja O(...) .................................... 35
Problem Collatza i badanie własności stopu ....................................................................... 42

3. Trudne problemy, które wybuchają .................................................................................... 47


Komiwojażer ma problem .............................................................................................................. 47
Dlaczego NP? ........................................................................................................................................ 53
Plecak, układanki i różne zabawy z kredkami .................................................................... 56
Pytanie za milion dolarów: czy P = NP? ............................................................................... 63

4. O metodach konstruowania algorytmów ....................................................................... 67


Skąd się biorą algorytmy? .............................................................................................................. 67
Metoda „dziel i zwyciężaj”............................................................................................................ 71
Algorytmy zachłanne ........................................................................................................................ 73
Algorytmy heurystyczne ................................................................................................................. 75
Algorytmy rekurencyjne ................................................................................................................. 78

5. Algorytmy probabilistyczne i ewolucyjne ...................................................................... 91


Metody Monte Carlo ......................................................................................................................... 91
Symulacja losowych zjawisk zachodzących w czasie ................................................... 97
Algorytmy ewolucyjne...................................................................................................................103
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

4 Spis treści

6. Obliczenia analogowe i cyfrowe ........................................................................................... 118


Czy obliczenie musi się wykonywać w jakichś krokach? ......................................... 118
Analogowe i cyfrowe techniki przetwarzania informacji .......................................... 119
Domowe i szkolne przykłady obliczeń analogowych ..................................................123
Analogowe urządzenia w technice ..........................................................................................126
Analogowe układy automatycznej regulacji ......................................................................128
Cybernetyczne wizje: serwomechanizmy czy komputery?.......................................133

7. Cyfrowe przetwarzanie sygnałów ...................................................................................... 138


Dyskretyzacja ciągłego sygnału................................................................................................138
Widmo sygnału i przekształcenie (transformata) Fouriera ........................................143
Korzyści ze znajomości widma .................................................................................................152
Matematyka i francuska epopeja ..............................................................................................159

8. Maszyna Turinga............................................................................................................................... 163


Zasada działania maszyny Turinga .........................................................................................163
Teza Churcha–Turinga dla obliczeń sekwencyjnych ....................................................177
Enigma życia i działalności Alana Turinga ........................................................................181

9. O lingwistyce matematycznej................................................................................................. 189


Czy lingwistyka może być matematyczna?........................................................................189
Język jako zbiór .................................................................................................................................190
Model gramatyki kombinatorycznej Chomsky’ego i notacja BNF ......................194
Panie gryzą psy ponieważ dzieci lubią koty ......................................................................196
Języki skończone i nieskończone .............................................................................................202
Poziom leksykalny i składniowy (syntaktyczny) gramatyki ....................................205
Języki bezkontekstowe i kontekstowe...................................................................................209
A jak się to ma do języków naturalnych? ............................................................................210
Intelekt i lewicowość.......................................................................................................................213

10. Automaty skończone ...................................................................................................................... 217


Podstawowa definicja automatu skończonego .................................................................217
Niezupełność i niedeterminizm automatu ...........................................................................224
Automat skończony a badanie poprawności składniowej ..........................................226
Automat skończony jako model zachowania fizycznego urządzenia .................231
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Spis treści 5

11. System dwójkowy ............................................................................................................................. 235


Dlaczego właśnie dwójkowy?....................................................................................................235
Dwójkowe liczby całkowite i podstawowe arytmetyczne operacje na nich ....238
Inne sposoby przedstawiania liczb całkowitych ..............................................................251
Liczby stałoprzecinkowe i zmiennoprzecinkowe ...........................................................253
Notacja heksadecymalna (szesnastkowa) ............................................................................258
Kodowanie znaków alfanumerycznych ................................................................................259
Projekt Unicode..................................................................................................................................266

12. Elementarz syntezy logicznej ................................................................................................. 273


Co oznacza ten tytuł? ......................................................................................................................273
Od Arystotelesa ze Stagiry do Claude'a Shannona z Gaylord w stanie
Michigan ................................................................................................................................................274
Układy przełączające ......................................................................................................................279
Niezwykła kariera naukowa Claude'a Shannona............................................................286
Algebra Boole’a i pomysł na „automatyzację” obliczeń logicznych...................289
Od tabelki prawdy do sieci logicznej .....................................................................................298
Jak zrobić trzydziestodwubitowy sumator? .......................................................................303
Inne „prefabrykowane” podzespoły logiczne ...................................................................309

13. Układy sekwencyjne ....................................................................................................................... 313


Przerzutniki...........................................................................................................................................313
Rejestry i liczniki ..............................................................................................................................320
Wrzuć monetę... czyli prosty układ sterowania ...............................................................324

14. Wiek informatyki .............................................................................................................................. 341


Praojcowie informatyki .................................................................................................................341
Potrzeby obliczeniowe okresu II wojny światowej .......................................................347
Bariera niezawodnościowa ..........................................................................................................350
Bardzo dobry, ale bardzo drogi pomysł ................................................................................352
Powojenne problemy globalnej polityki ..............................................................................355
Wielki program na przełomowe lata sześćdziesiąte ......................................................356
Komputery lat sześćdziesiątych ................................................................................................358
Lata siedemdziesiąte: postęp nie zwalnia ............................................................................362
Kolejny przełom: komputery prywatne ................................................................................364
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

6 Spis treści

Od internetów do Internetu ..........................................................................................................367


A co tam, panie, w polityce? .......................................................................................................368
Nieoczekiwany koniec zimnej wojny ....................................................................................372

15. Von Neumanna komputer z programem w pamięci .......................................... 376


Jak to się zaczęło ...............................................................................................................................376
Od prostego kalkulatora do czegoś w rodzaju własnej roboty ENIAC-a..........378
Zasada maszyny z programem w pamięci ..........................................................................383
Kilka słów o programowaniu maszyn z pamiętanym programem ........................392
Inżynieria oprogramowania i inżynieria systemowa.....................................................397

16. Organizacja jednoprocesorowego systemu komputerowego ..................... 401


O jakim komputerze mowa? .......................................................................................................401
Podstawowy schemat blokowy systemu ..............................................................................402
Pan, sługa i arbiter, czyli jak uzyskać dostęp do magistrali ......................................407
Czego pan może wymagać od sługi .......................................................................................409

17. Procesor ..................................................................................................................................................... 419


Schemat blokowy procesora .......................................................................................................419
Stos systemowy i operacje na stosie.......................................................................................431

18. System przerwań ............................................................................................................................... 437


Zasada przerwań ................................................................................................................................437
Od zgłoszenia przerwania do reakcji układu sterowania procesora .....................442
Inicjowanie obsługi przerwania ................................................................................................445
Program obsługi przerwania .......................................................................................................448
Programy i procesy ..........................................................................................................................450

19. Co było dalej?....................................................................................................................................... 456


Zakończenie ........................................................................................................................................... 470
Skorowidz nazwisk .......................................................................................................................... 471
Skorowidz rzeczowy ....................................................................................................................... 473
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

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

Oczywiście, zawsze pozostaje do rozstrzygnięcia problem, co do


owych wartych zrozumienia podstaw należy. Pisząc niniejszą książkę, doko-
nałem subiektywnego wyboru i biorę za niego odpowiedzialność. Kto chce
dowiedzieć się więcej o zawartości tej książki – niech zajrzy do pierwszego
rozdziału, gdzie jest ona pokrótce streszczona.
Wybór ten nie był dziełem przypadku. Zakres tematyczny tego,
o czym ta książka traktuje, jest wzorowany na treści wykładu (też o nazwie
Wstęp do informatyki), który prowadziłem przez blisko dziesięć lat dla stu-
dentów pierwszego semestru kierunku informatyka na Wydziale Elektroniki
i Technik Informacyjnych w Politechnice Warszawskiej. Treść wykładu,
stopniowo ulepszana, miała więc czas, by się „uleżeć” i sprawdzić w prak-
tyce.
Choć początkowo były wątpliwości, czy taki wstępny wykład jest
w ogóle w programie studiów potrzebny, to anonimowe ankiety, przeprowa-
dzone po kilku latach wśród jego słuchaczy, którzy w międzyczasie dotarli
już do dyplomu – potwierdziły jego przydatność. Ku mojej wielkiej satys-
fakcji znaczna większość respondentów była zdania, że ten autorski wykład,
właśnie dlatego, że przeglądowy i encyklopedyczny, znacznie ułatwił im
później rozumienie i studiowanie przedmiotów bardziej specjalistycznych.
Niniejsza książka nie jest jednak ani monografią, ani akademickim
podręcznikiem dla studentów informatyki. Ze wspomnianego wykładu za-
chował się podstawowy, już sprawdzony układ tematyczny i niektóre przy-
kłady, lecz została ona napisana zupełnie od nowa, dla innego, w zamyśle
znacznie szerszego kręgu czytelników.
W stosunku do wykładu – pominąłem pewną ilość zagadnień zbyt
szczegółowych, uprościłem przykłady, a przede wszystkim starałem się bar-
dzo, by wszystkie występujące w tekście pojęcia były wyjaśnione możliwie
przystępnie, w sposób przemawiający do intuicji, nieformalny i zrozumia-
ły dla kogoś, kto nie ma matematycznego przygotowania, wykraczającego
poza przeciętną szkolną wiedzę.
Od potencjalnego czytelnika nie są też wymagane żadne szczególne
wiadomości z dziedziny samej informatyki. Ponieważ jednak zdaję sobie
sprawę, że dziś praktycznie już wszyscy posługują się komputerami i coś
o informatyce wiedzą – dla ważniejszych pojawiających się w książce ter-
minów starałem się podawać ich angielskie odpowiedniki. Tym, którzy ze-
chcą znaleźć dodatkowe informacje, powinno to ułatwić samodzielne ich
poszukiwanie w światowej sieci.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

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

Najpierw podziękuję mojej żonie, Małgosi i córce, Ani – za zachętę


i nieustające wsparcie, nie tylko zresztą w sprawach, które książki dotyczą.
Wdzięczność należy się także moim przyjaciołom i współpracownikom,
doktorom: Wiktorowi Daszczukowi i Andrzejowi Pająkowi, za to, że ze-
chcieli przeczytać całe to przydługie dzieło i wnieśli liczne uwagi, które
przyczyniły się do poprawienia jego jakości. Na wnikliwe skonsultowanie
treści rozdziału 7 sporo czasu poświęcił prof. Jerzy Szabatin, za co rów-
nież jestem mu wdzięczny. Specjalne wyrazy podziękowania kieruję też do
Redaktora Naczelnego Wydawnictwa BTC, pana Piotra Zbysińskiego, za
jego życzliwość i doprawdy nadludzką cierpliwość, jaką okazał, czekając na
ukończenie przeze mnie tej książki.
Jerzy Mieścicki
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

1. O czym będzie traktować ta


książka

Od zadania obliczeniowego do jego


wykonania przez komputer
Korzystając z komputera, nie myślimy zazwyczaj o tym, że to, co widzimy
na ekranie lub to, co powodujemy, klikając na odpowiednią ikonkę – jest wy-
nikiem działania pewnego programu. Tak jednak w rzeczywistości jest. Co
więcej, ktoś ten program kiedyś opracował, a przedtem go nie było. Powstał
on zapewne jako wynik dłuższego procesu, którego końcową, najbardziej
widowiskową fazę właśnie obserwujemy, siedząc przy komputerze.
W największym skrócie, ów proces (rysunek 1.1) rozpoczyna się od
tego, że ktoś – nazwijmy go umownie projektantem – postanawia rozwiązać

Rys. 1.1. Od zadania obliczeniowego do wykonania programu maszynowego


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

12 1. O czym będzie traktować ta książka

pewne zadanie obliczeniowe. Zapewne jest to komuś potrzebne, opłacalne,


ciekawe, albo projektant podejrzewa, że można je rozwiązać lepiej, niż to
robiono do tej pory.
Jeśli to zadanie obliczeniowe (cokolwiek to na razie znaczy) ma być
skutecznie wykonane przez komputer – musi na końcu przybrać postać cią-
gu instrukcji maszynowych, czyli elementarnych czynności, jakie komputer
jest w stanie wykonać. Ciąg ten zostanie zapisany w pamięci komputera
jako program w języku maszynowym.
Sam termin pamięć może sugerować, że komputer coś „pamięta”
w taki sam sposób, jak pamiętają różne rzeczy ludzie i zwierzęta; na przy-
kład, że może sobie coś „przypomnieć” albo coś z czymś „skojarzyć” itp.
Tak jednak nie jest. Pamięć komputera jest zbiorem całkowicie biernych,
ponumerowanych komórek, jak gdyby przegródek o identycznych rozmia-
rach, do których można wkładać (i z nich wyjmować) zakodowane dane lub
instrukcje. Z której komórki coś się wyjmuje, co się z tym dalej robi, do
której komórki zapisuje się wynik – wszystko to jest przewidziane właśnie
w maszynowym programie.
Komputer jest zbudowany tak i po to, by kolejno pobierał ze swojej
pamięci i wykonywał owe maszynowe instrukcje. Skoro więc program w ję-
zyku maszynowym będzie gotów (i będzie poprawny) – komputer obliczy
co trzeba, wyświetli co trzeba, wydrukuje wynik i tak dalej. I to właśnie
obserwujemy, pracując przy komputerze.
Nikt jednak nie projektuje rozwiązania zadania obliczeniowego od
razu w postaci programu w języku maszyny. Byłoby to bardzo żmudne
i wymagałoby od każdego projektanta dość szczegółowej znajomości zasad
budowy i działania komputera, i to określonego typu komputera, bo przecież
jest ich wiele. Tak może postępowano kiedyś, na samym początku ery kom-
puterowej, obecnie też tak się robi w pewnych szczególnych przypadkach,
ale na ogół od przeciętnego projektanta obliczeń nie wymaga się dokładnej
wiedzy o technicznej stronie maszynowego języka.
Projektant musi przede wszystkim sformułować algorytm rozwiąza-
nia swojego zadania. Choć to pojęcie dla informatyki zupełnie podstawowe,
to nie będziemy się silić na jego formalne definiowanie już w tym miejscu.
Cała pierwsza część niniejszej książki służy właśnie temu, byśmy coraz le-
piej rozumieli istotę tego pojęcia i właściwości, jakich od algorytmów ocze-
kujemy. Niech na razie wystarczy, jeśli powiemy, że algorytm jest pewnym
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Od zadania obliczeniowego do jego wykonania przez komputer 13

przepisem postępowania, mówiącym, jak sam projektant rozwiązałby krok


po kroku swoje zadanie obliczeniowe.
Algorytm ma więc postać sekwencji złożonej z pewnych instrukcji,
ale nie muszą to być instrukcje maszynowe. Wystarczy na razie, by były
zrozumiałe i wykonalne dla człowieka. Można wyobrażać sobie, że projek-
tant, opisując algorytm, stara się objaśnić pewną procedurę postępowania
komuś innemu, na przykład młodszemu, mniej doświadczonemu koledze.
Algorytm powinien być sformułowany na tyle dokładnie, by ów kolega,
nawet jeśli jest niezbyt rozgarnięty – był później w stanie wykonać dane
obliczenie zupełnie samodzielnie.
Kiedy algorytm jest już gotowy, należałoby przetłumaczyć go na język
maszynowy. Nie będzie jednak tego robił osobiście ani sam projektant, ani
jego mniej bystry kolega. Zajmie się tym gotowy, wcześniej – i przez kogo
innego – przygotowany program, zwany translatorem, czyli tłumaczem.
Skąd jednak taki translator się wziął? Zapewne stąd, że już dawniej
inny projektant (a nawet prawdopodobnie cały ich zespół) postawił przed
sobą jako zadanie obliczeniowe – zbudowanie właśnie takiego programu
tłumaczącego. Z pewnością podzielono wówczas to zadanie na mniejsze
podzadania, opracowano odpowiednie algorytmy, przygotowano pomocni-
cze narzędzia, ułatwiające produkowanie maszynowego kodu itd. Od spe-
cjalistów od budowy translatorów możemy już wymagać znajomości zasad
budowy i działania komputera, przyjmijmy więc – zgodnie z rzeczywisto-
ścią – że dzięki ich umiejętnościom i pracy takie programy tłumaczące są
już gotowe i czekają na wykorzystanie.
Powiedzieliśmy, że algorytm naszego zadania obliczeniowego jest
początkowo sformułowany tak, by był zrozumiały dla człowieka. Translator
jest jednak programem, więc nie możemy od niego oczekiwać nawet śladu
ludzkiej domyślności ani tolerancji dla jakichkolwiek niejednoznaczności,
przejęzyczeń lub niedopowiedzeń. Dlatego przed poddaniem go tłumacze-
niu (translacji), algorytm wraz z danymi, które przetwarza, musi być zapi-
sany w specjalnym, dobrze zdefiniowanym języku, możliwie podobnym do
ludzkiego (w praktyce najczęściej angielskiego), lecz dopuszczającym sto-
sowanie wyłącznie pewnych jednoznacznych, standardowych słów, zwro-
tów i oznaczeń. Translator i tak ich nie zrozumie, ale ponieważ stanowią
one pewne przewidywalne grupy znaków – będzie w stanie je rozpoznawać
na podstawie ich budowy i stopniowo produkować odpowiadające im kolej-
ne fragmenty programu maszynowego.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

14 1. O czym będzie traktować ta książka

Taki sformalizowany język do zapisywania algorytmów (oraz danych,


które te algorytmy przetwarzają) – to oczywiście język programowania.
Algorytm zapisany (wraz z jego danymi) w języku programowania staje
się programem. Dla translatora jest on programem źródłowym (mówi się
też: kodem źródłowym, ang. source program, source code). Na jego podsta-
wie translator tworzy program maszynowy (ang. machine code) i zapisuje
go w pamięci komputera.
Program maszynowy należy jeszcze wywołać (lub: uruchomić), to
znaczy wskazać miejsce w pamięci, w którym znajduje się początek progra-
mu i spowodować, by komputer zaczął wykonywać kolejne instrukcje, aż
do natrafienia na instrukcję końcową, nakazującą mu zatrzymanie się.
Bardzo ważną zaletą komputera jest jego kompletna bezmyślność.
Niezależnie od technicznej i logicznej złożoności, szybkości itd. – jest on
równie inteligentny jak młotek lub cep. Cierpliwie i zupełnie bezmyślnie
wykona wszystko, co mu w programie nakazano. Nie poprawi żadnego
naszego najbardziej oczywistego błędu i nie domyśli się niczego, co jest
w programie opuszczone lub niedopowiedziane.
Jest to wielka zaleta, ponieważ dzięki temu komputer bezlitośnie we-
ryfikuje niewiedzę lub brak dyscypliny umysłowej u projektanta. Jeśli pro-
gram jest oparty na złych założeniach, jeśli zawiera logiczne błędy lub me-
rytoryczne luki – to komputer wyprodukuje prawdopodobnie bezsensowny
wynik albo się zawiesi. Ale z drugiej strony, jeśli w końcu wykonanie pro-
gramu daje rezultaty oczekiwane i sensowne – to znaczy, że jego projektant
naprawdę zrozumiał problem i potrafił go rozwiązać naprawdę dobrze.
Dobrze przygotowany i poprawny program jest natychmiast gotowy
do użycia przez miliony ludzi. Trudno znaleźć podobną sytuację w jakiejś
innej dziedzinie techniki. Jest to bowiem tak, jak gdyby zaraz po zbudowa-
niu i przetestowaniu prototypu – powiedzmy – samochodu, tysiące użyt-
kowników mogły go sobie w ciągu kilku minut skopiować, a potem od razu
wsiąść do niego i jechać. Taka sztuczka z samochodami się nie uda, a z pro-
gramami – przynajmniej w zasadzie – tak.
Te dwa ostatnio opisane fakty tłumaczą – jak się wydaje – obserwo-
wane od kilku dziesięcioleci nagłe przyspieszenie rozwoju w wielu dziedzi-
nach wiedzy. Dzięki posiadaniu narzędzia do bezwzględnej i szybkiej wery-
fikacji różnych hipotez, pomysłów i metod ludzie zaczynają naprawdę i co-
raz dokładniej rozumieć coraz więcej procesów i zjawisk, a także potrafią tę
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Od zadania obliczeniowego do jego wykonania przez komputer 15

wiedzę wykorzystać konstruktywnie w praktyce. Łatwość rozpowszechnia-


nia powoduje, że te nowe wyniki są szybko i coraz szerzej dostępne.
Z kolei, wśród wciąż rosnącego grona użytkowników znajdą się za-
wsze tacy, którym przyjdzie do głowy, że w danej metodzie można coś
ulepszyć, uogólnić albo zmodyfikować tak, by można było ją zastosować
w innej, niespodziewanej dziedzinie. Prowadzi to do powstawania nowych
algorytmów i nowego oprogramowania. Z zasady wymaga to też coraz lep-
szych komputerów: szybszych, o większej pamięci i bardziej rozbudowa-
nym oprogramowaniu systemowym. Kiedy i one są już dostępne – staje
się możliwe wypróbowywanie nowych, jeszcze bardziej wyrafinowanych
algorytmów… i koło się zamyka.
Te procesy się wzajemnie nakręcają i wspomagają, powodując za-
równo ogromny przyrost zasobów ludzkiej wiedzy, jak doskonalenie sa-
mych narzędzi do przetwarzania informacji. Niektórzy nazywają te zjawi-
ska rewolucją informacyjną przełomu XX i XXI wieku.
Doprawdy warto dowiedzieć się czegoś więcej o tych procesach i zja-
wiskach. Temu właśnie jest poświęcona niniejsza książka.
Powiedzmy jednak od razu, że będziemy się zajmowali pojęciami
i zasadami, charakterystycznymi dla dzisiejszej informatyki oraz kompute-
rów takich, jak dzisiaj rozumie się to słowo. Nie będą więc przedmiotem
naszej uwagi baśniowe stwory, które straszą nas z kart powieści science fic-
tion i filmów o podobnej tematyce. Takie, które – wyposażone w nadludzką
inteligencję – rozwiążą kiedyś za nas wszystkie problemy, a potem zawiążą
spisek i bezlitośnie zapanują nad nami i całym światem.
To, że takie pomysły i obawy lęgną się w wyobraźni osób, które
o komputerach bardzo mało wiedzą – jest w pewnym sensie zrozumiałe.
Już dzisiaj jesteśmy uzależnieni od otaczających nas systemów komputero-
wych. Bez internetowej wyszukiwarki trudno dziś sobie wyobrazić życie.
Awaria systemu w sklepie, urzędzie czy banku oznacza lokalną katastrofę.
Każda transakcja dokonana kartą płatniczą czy rozmowa telefoniczna reje-
struje naszą obecność w pewnym miejscu, ujawnia nasze upodobania i krąg
znajomości. Portale społecznościowe mogą być źródłem tak dokładnych in-
formacji o nas, że wielu upatruje w nich zagrożeń dla naszej wolności czy
prywatności.
To wszystko prawda, ale z drugiej strony – dlaczego nikt się nie boi,
że pewnego dnia zbuntują się przeciwko ludziom telewizory? Że zawiążą
konspirację i będą manipulowały całą ludzkością, dostarczając fałszywych
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

16 1. O czym będzie traktować ta książka

wiadomości, judząc jednych ludzi przeciwko innym i wywołując konflikty


w sobie tylko znanym celu?
Spisku telewizorów się nie boimy, bo lepiej je znamy. Wiemy, że
wprawdzie mogą służyć (i niestety częstokroć służą) za narzędzie manipu-
lacji, ale są właśnie co najwyżej narzędziem w rękach pewnych osób, grup
czy organizacji.
Również i z tego powodu powinniśmy więcej wiedzieć o algorytmach,
technice cyfrowej, komputerach. Jeżeli będziemy je lepiej znali – przesta-
niemy się obawiać ich samych, a lepiej zrozumiemy ważne cywilizacyjne
procesy, które dzieją się na naszych oczach. Będziemy też w stanie dostrzec
ewentualne zagrożenia tam, gdzie one się rzeczywiście mogą kryć: w lu-
dziach, którzy – być może – zechcą posłużyć się systemami komputerowy-
mi do celów, jakie nam nie odpowiadają.
Oczywiście, teoretycznie rzecz biorąc, nie można wykluczyć, że poja-
wią się kiedyś maszyny obdarzone świadomością i wolą, zdolne do twórcze-
go realizowania swoich zamysłów i dążeń. Jeśli jednak to kiedyś – nie daj
Boże – nastąpi, to nie będą to komputery. Będą one działały na innych zasa-
dach, będą się inaczej nazywały i ktoś inny napisze o nich książkę. My zaś
będziemy się trzymali dzisiejszej, naukowej i technicznej rzeczywistości.

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

18 1. O czym będzie traktować ta książka

Rozdział 6 rozpoczniemy od wątpliwości: czy jedynym sposobem


na rozwiązywanie naszych problemów obliczeniowych jest wykorzystanie
algorytmów, programów i komputera? Oczywiście, nie. Algorytmy i kom-
putery kojarzą się z cyfrowymi metodami przetwarzania danych, tymcza-
sem wiele z zadań można rozwiązać, używając także innych, analogowych
sposobów zapisywania, przesyłania i przetwarzania informacji. Będzie to
dobry pretekst do podania kilku niebanalnych przykładów oraz do tego, by
zastanowić się nad istotą obu tych podejść i dzielącymi je różnicami.
Jednocześnie widzimy, że już od dłuższego czasu to, co było analo-
gowe, jest wypierane przez cyfrowe: cyfrowe nagrania, cyfrowe filmy, fo-
tografia, telefonia, telewizja dominują na rynku. Ale właściwie dlaczego?
Postaramy się wyrobić sobie pogląd i na ten temat.
Nie jest jednak tak, że urządzenia cyfrowe, algorytmy i komputerowe
oprogramowanie eliminują z życia wszelkie analogowe formy prezentowa-
nia informacji. Nie da się ich wyeliminować, ponieważ zewsząd jesteśmy
otoczeni sygnałami, które są ze swej natury analogowe. Docierają do nas
z otoczenia analogowe dźwięki, obrazy i radiowe sygnały z kosmosu. Sami
zresztą wytwarzamy analogowe sygnały elektryczne: ich źródłem jest dzia-
łanie naszego mózgu, serca i innych mięśni. Technika cyfrowa nie tylko ich
nie ignoruje, ale przeciwnie: dostarcza niebywale sprawnych narzędzi do
ich rejestrowania i przetwarzania.
Tej tematyce poświęcony jest rozdział 7. Poznamy w nim najbardziej
elementarne pojęcia i metody stosowane w cyfrowym przetwarzaniu sygna-
łów. Warto się z nimi zaznajomić nie tylko dlatego, że to po prostu ciekawe,
ale też dlatego, że algorytmy z dziedziny cyfrowego przetwarzania sygna-
łów należą do wykorzystywanych w praktyce najczęściej. W chwili, gdy
to czytamy – dosłownie setki milionów cyfrowych urządzeń przetwarzają
analogowe sygnały. Każdy telefon komórkowy, odtwarzacz CD czy DVD,
cyfrowy telewizor czy komputer, którego właściciel zabawia się oglądaniem
filmów lub rozmową za pośrednictwem Skype’a – zajmuje się tym przez
cały czas.
Po tej wycieczce w rejony analogowe powrócimy na teren czysto cy-
frowy, charakterystyczny dla współczesnej informatyki i pozostaniemy na
nim aż do końca książki.
Najpierw, w rozdziale 8, omówimy abstrakcyjną maszynę Turinga:
zaskakująco prosty pomysł, pokazujący, na czym w istocie polega działanie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

O czym opowiemy 19

każdego urządzenia, zdolnego do algorytmicznego wykonywania sekwen-


cyjnych obliczeń.
Współczesny komputer może wprawiać laika w podziw swą szyb-
kością, miliardami komórek pamięci, graficzną formą komunikacji z użyt-
kownikiem – ale okazuje się, że z teoretycznego punktu widzenia to tylko
techniczne błyskotki. Są bowiem podstawy, by sądzić, że żaden komputer
nie jest w stanie skutecznie wykonać żadnego obliczenia, które nie byłoby
wykonalne dla takiej – zdawałoby się – prościutkiej maszyny Turinga.
Maszyna Turinga pokazuje jak na dłoni, że zarówno od niej, jak i od
komputera nie można oczekiwać żadnego samodzielnego, twórczego działa-
nia: żadnej intuicji, pomysłu, nieoczekiwanego skojarzenia, wynalazczości.
Ten wniosek może budzić mieszane uczucia. Jedni będą rozczarowani, że
komputery są aż tak pozbawione inteligencji i niczego za nas nie wymyślą.
Drudzy poczują dumę, że mając do dyspozycji tak prymitywne urządzenia,
potrafimy aż tak wiele za ich pomocą zrobić. Umiemy nawet przy ich wyko-
rzystaniu naśladować pewne inteligentne zachowania, co stanowi przedmiot
badań nad sztuczną inteligencją.
Rozdział 9 jest z kolei poświęcony podstawowym pojęciom z dzie-
dziny lingwistyki matematycznej. A cóż to takiego jest? Czy lingwistyka
może mieć w ogóle coś wspólnego z matematyką? Czy język programo-
wania jest rzeczywiście językiem, takim jak polski czy angielski? Czy ma
– na przykład – swoją gramatykę? Okazuje się, że można podać formalną,
matematyczną definicję zarówno języka, jak i gramatyki, a potem wyko-
rzystywać pojęcia z dziedziny lingwistyki matematycznej tak do analizy
języków naturalnych, jak do tworzenia sztucznych języków służących do
komunikacji z maszynami.
W rozdziale 10 zajmiemy się automatami skończonymi. To kolejny
teoretyczny pomysł, który ma bardzo ważne i bardzo praktyczne zastosowa-
nie w wielu gałęziach informatyki. Przedstawimy tam kilka podstawowych
pojęć, na których opiera się teoria automatów skończonych. Można je po-
tem w zupełnie bezpośredni sposób wykorzystać np. przy badaniu popraw-
ności pewnych konstrukcji językowych w językach programowania i przy
projektowaniu cyfrowego sprzętu.
Na rozdziale 10 kończy się pierwsza część książki, poświęcona kilku
podstawowym dla informatyki teoretycznym pojęciom i stosowanym w in-
formatyce sposobom postępowania. W dalszej części będziemy zmierzać do
tego, by zrozumieć, na jakiej zasadzie działa typowy komputer. Choć przed
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

20 1. O czym będzie traktować ta książka

chwilą bardzo nisko oceniliśmy jego inteligencję – warto zrozumieć, jak


z bezmyślnych, technicznych podzespołów i elementów można skonstru-
ować zadziwiająco sprawne i pomysłowe urządzenie służące do wykonywa-
nia algorytmicznych obliczeń.
Co więcej, nie jest to jakieś tam urządzenie, jakich wiele w otaczają-
cym nas świecie. Przeciwnie, komputery należą do tych kilku technicznych
rozwiązań, które w wyjątkowy sposób wpłynęły na kształt i losy naszej cy-
wilizacji. Ich rozwój wiąże się bardzo silnie z historią i polityką ostatnich
kilkudziesięciu lat. Także i teraz mocno wpływają one na wiele dziedzin
naszego życia. O tym również opowiemy w dalszej części książki.
Rozpoczniemy od rozdziału 11, który pomoże czytelnikowi oswoić
się z dwójkowym systemem reprezentowania wewnątrz komputera wszel-
kich danych oraz maszynowych instrukcji. Najpierw podamy kilka sposo-
bów dwójkowego (binarnego) zapisywania liczb oraz reguł wykonywania
na nich podstawowych operacji arytmetycznych. Potem omówimy techniki
dwójkowego reprezentowania informacji innych niż liczby, a mianowicie
– ciągów znaków i symboli pochodzących z różnych alfabetów. Oczywiście,
w naszym kręgu kulturowym posługujemy się głównie (choć nie jedynie)
alfabetem łacińskim, ale dowiemy się także, w jaki sposób koduje się dwój-
kowo inne zestawy umownych znaków, w tym także symbole pochodzące
z alfabetów używanych przez narody dla nas egzotyczne, a nawet – symbole
pisma klinowego czy hieroglificznego pisma staroegipskiego.
Rozdział 12 poświęcimy podstawowym pojęciom i metodom syntezy
logicznej. Pod tym terminem rozumie się składanie złożonych układów cy-
frowych z pewnych podstawowych cegiełek, zwanych bramkami logicz-
nymi i przerzutnikami. Tak budowane są wszelkie cyfrowe urządzenia, od
prostych sterowników do superkomputerów.
Ciekawe jest to, że stosowane przy tym metody wywodzą się z pod-
stawowych praw logiki, znanych jeszcze od czasów Arystotelesa. Kiedy
i jaką drogą koncepcje wielkiego antycznego filozofa dotarły do rąk dzi-
siejszych inżynierów? Dowiemy się o tym, poznamy podstawowe regu-
ły algebry Boole’a, a także sami spróbujemy przy ich użyciu zaprojektować
kilka prostych, przykładowych sieci logicznych. W rozdziale 13 rozsze-
rzymy nasz wykład o układy sekwencyjne, to znaczy takie, które potrafią
pamiętać swój stan i swoje dalsze działania uzależniać od tego, co działo
się w poprzedzających chwilach. Przekonamy się tam, do czego może się
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

O czym opowiemy 21

przydać znajomość podstaw teorii automatów, którym był poświęcony roz-


dział 10.
Wraz z odkryciem (w końcu lat trzydziestych XX wieku) związku
między prawidłami logiki a technicznymi układami dwustanowymi rozwój
techniki przetwarzania informacji gwałtownie przyspieszył. Dlatego, choć
już kilka wieków temu usiłowano budować różne maszyny do celów ob-
liczeniowych – era współczesnych komputerów rozpoczęła się na dobre
w okresie II wojny światowej. Jak wyglądały i do czego były używane daw-
ne maszyny obliczeniowe i pierwsze „mózgi elektronowe”? Dowiemy się
o tym (i nie tylko o tym) z rozdziału 14.
Dalszy rozwój informatyki splótł się bardzo silnie z dramatycznymi
wydarzeniami politycznymi, które działy się już w okresie powojennym.
Początki zimnej wojny, rywalizacja w zdobywaniu kosmosu, szpiego-
stwo technologiczne, wyścig zbrojeń między największymi mocarstwami
– wszystko to w znacznym stopniu przenosiło się na grunt techniki oblicze-
niowej. Nie wszyscy wiedzą, jak mocno (i dlaczego) opanowanie produkcji
układów scalonych wpłynęło na polityczne losy świata. Choć oczywiście
był to tylko jeden z wielu czynników wpływających na ówczesną rzeczywi-
stość – jednak im właśnie zawdzięczamy w dużym stopniu przewagę tech-
nologiczną krajów Zachodu, która w końcu przeważyła na ich korzyść szalę
wieloletniej rywalizacji z systemem komunistycznym. O tym wszystkim
opowiem w rozdziale 14.
Dotrzemy wreszcie do zasad działania komputera. Rozdział 15 wpro-
wadzi nas w samą istotę pomysłu, który zapoczątkował przełom w dzie-
dzinie budowy cyfrowych maszyn do celów przetwarzania informacji. To
koncepcja komputera z pamiętanym programem, którą przypisuje się wy-
bitnemu matematykowi, Johnowi von Neumannowi. Warto ją dobrze zrozu-
mieć, gdyż z jej wykorzystaniem działają do dzisiaj wszystkie programowa-
ne komputery: od małych smartfonów i laptopów aż do superkomputerów.
Oczywiście, nie są tak prymitywne jak kiedyś, są tysiące razy szybsze, mają
bogate systemowe i aplikacyjne oprogramowanie i tak dalej – ale opierają
się na wciąż tych samych kilku podstawowych zasadach sformułowanych
przez von Neumanna.
Dalsze trzy rozdziały (16, 17 i 18) mówią więcej o budowie i dzia-
łaniu współczesnych komputerów. Do celów wykładu posłużymy się tam
uproszczonym modelem komputera, który ma to, co komputer mieć powi-
nien: pamięć operacyjną, procesor, magistralę systemową, urządzenia ze-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

22 1. O czym będzie traktować ta książka

wnętrzne, system przerwań i tak dalej, ale wszystko w możliwie najprost-


szej postaci. To upodabnia ten przykładowy model do minikomputerów pro-
dukowanych na początku lat siedemdziesiątych XX wieku. Powie ktoś, że
to starzyzna, ale będzie miał tylko częściowo rację. Dzięki swej prostocie
taki model może być ilustracją prowadzonego w tej książce wykładu, po-
święconego przecież podstawowym i względnie utrwalonym zasadom, a nie
zaawansowanym technicznym szczegółom. Późniejsze komputery nadają
się do tego celu znacznie gorzej, gdyż są już zbytnio złożone.
Istotnie, w połowie lat siedemdziesiątych XX wieku weszły w uży-
cie układy scalone o wielkiej skali integracji (LSI – ang. Large Scale
Integration). Ogromne możliwości, jakie dały one projektantom komputero-
wego sprzętu, spowodowały, że organizacja komputera szybko skompliko-
wała się i wzbogaciła o rozmaite ulepszenia, nowe podzespoły i jednostki.
W rozdziale 19 powiemy więcej o tych ulepszeniach i o tym, jak zmodyfi-
kowały one budowę dzisiejszych komputerów. Przekonamy się też, że dla
ich zrozumienia warto było rozpocząć od poznania dawniejszych, bardziej
prostych rozwiązań.
Na koniec, w rozdziale 20, pożegnamy się, wyrażając ubolewanie, że
nie udało się w tej książce zmieścić tak wielu jeszcze innych, pasjonujących
tematów współczesnej informatyki. To jednak tylko Wstęp do… i to raczej
o popularyzatorskim charakterze. Ktoś, kogo ta tematyka wciągnie – może
zechce już samodzielnie skorzystać z innych, bardziej specjalistycznych
źródeł.
Tak wygląda w skrócie treść niniejszej książki. Czytelnik został uprze-
dzony i ostrzeżony. Kto uzna, że może być to dla niego ciekawe – niech się
czuje serdecznie zaproszony na następne stronice.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

2. O algorytmach i złożoności
obliczeniowej na kilku łatwych
przykładach

Co to właściwie jest „obliczenie”?


W języku polskim sam termin obliczenie kojarzy się natychmiast z liczbami
i z liczeniem: na przykład z dodawaniem, odejmowaniem, mnożeniem, dzie-
leniem jakichś liczb. Oczywiście, jest bardzo wiele zadań, które w gruncie
rzeczy polegają właśnie na tym (mówimy wtedy o obliczeniach numerycz-
nych), jednak generalnie, pojęcie obliczenia (ang. computation) należy ro-
zumieć znacznie szerzej.
Każde obliczenie jest prowadzone na zbiorze pewnych danych, a jego
celem jest wyprodukowanie informacji, która przed wykonaniem tego obli-
czenia nie była znana. Danymi mogą być oczywiście liczby, ale mogą nimi
być także ciągi znaków konwencjonalnego alfabetu (na przykład nazwiska
czy adresy) i inne obiekty, takie jak na przykład odpowiednio zapisane gra-
fy, figury geometryczne, obrazy, a nawet dźwięki. Zostawmy na razie pro-
blemy związane z tym, że należy je uporządkować w odpowiednie struktury
danych (np. listę nazwisk, tablicę liczb, drzewo decyzji) i przedstawić (za-
kodować) tak, by można było je w przyszłości przetwarzać przy użyciu
komputera. W tej chwili ważne jest przede wszystkim to, by uświadomić
sobie, że dane mogą mieć bardzo różną naturę, podobnie jak zadania obli-
czeniowe, polegające na operowaniu tymi danymi.
Tak więc proste zadanie obliczeniowe może polegać na przykład na
tym, by:
– obliczyć sumę wydatków z całego miesiąca, spisanych na załączonej li-
ście,
– odpowiedzieć (tak lub nie) na pytanie, czy osoba o podanym tu nazwisku
jest na liście mieszkańców osiedla, a jeżeli tak – to podać jej adres,
– ułożyć daną listę nazwisk w kolejności alfabetycznej,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

24 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

– obliczyć wysokość podatku na podstawie danych zawartych w formularzu


PIT 37,
– narysować czerwony trójkącik o zadanych bokach,
– obliczyć pole obszaru ograniczonego osią x, zadaną krzywą y = f(x) oraz
prostymi x = 0 i x = 50,
– znaleźć w Panu Tadeuszu Adama Mickiewicza wers, w którym znajdują
się słowa „dzięcielina pała”,
– mając daną mapę drogową, znaleźć najkrótszą drogę między miastami A
i B,
– odpowiedzieć (tak lub nie) na pytanie, czy graf A ma taką samą budowę, jak
graf B,
– ...
Taką listę można by ciągnąć dowolnie długo. Wszystko to są oblicze-
nia, w omówionym wyżej uogólnionym sensie. Są wśród nich obliczenia
numeryczne, prowadzone rzeczywiście na liczbach oraz symboliczne (na
ciągach symboli, takich jak nazwiska lub inne pisane teksty). Zależnie od
kontekstu możemy też mówić o innych rodzajach obliczeń (np. graficznych,
prowadzonych na figurach geometrycznych), ale zazwyczaj są to w gruncie
rzeczy i tak obliczenia numeryczne lub symboliczne (albo mieszane).
Niektóre z obliczeń zmierzają do rozstrzygnięcia pewnej wątpliwości:
celem zadania jest wtedy jedynie udzielenie odpowiedzi „tak” albo „nie”.
Inne zadania polegają na wyliczeniu pewnej wartości liczbowej, nowej za-
wartości pewnej listy, zbioru współrzędnych punktów na ekranie (tworzą-
cych przykładowy trójkącik) itd.
Warto zdać sobie sprawę, że wymienione wyżej proste zadania ob-
liczeniowe są pewnymi uproszczonymi modelami prawdziwych, znacznie
bardziej złożonych problemów. Ktoś, kto wie, jak narysować na ekranie
czerwony trójkącik – będzie wkrótce umiał narysować i sto tysięcy róż-
nobarwnych trójkątów. Potem – zastanowi się, jak z takich trójkątów zło-
żyć obraz trójwymiarowej bryły, takiej jak na przykład postać człowieka.
Nieuchronnie przyjdzie mu też do głowy, że dobrze byłoby taki obraz ob-
racać i animować... Rzeczywiście, cała współczesna grafika komputerowa
rozpoczęła się kiedyś od problemu narysowania jednego trójkącika.
Podobnie dzisiejsze wyszukiwarki internetowe wzięły się stąd, że
w przeszłości ktoś postawił sobie pytanie: jak wyszukać w pewnym tekście
zadane słowo lub zwrot? My zapytaliśmy o Pana Tadeusza i dzięcielinę, ale
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Weź, Jasiu, kajecik i notuj 25

przecież równie dobrze może chodzić o dowolne teksty i dowolne zwroty.


Kiedy już było wiadomo, że takie zadanie daje się algorytmicznie rozwiązać
– można było pokusić się o jego ulepszenie, wreszcie – o rozszerzenie na
wyszukiwanie w całej światowej sieci. Nie było to łatwe, ale dziś wiemy, że
wykonalne. Co więcej, trudno nam dziś wyobrazić sobie życie bez sprawnej
internetowej wyszukiwarki.
Proste zadania, polegające na wyszukiwaniu nazwiska na liście, po-
rządkowaniu samej listy, obliczaniu sumy wydatków czy wysokości podatku
– dały z kolei początek koncepcji baz danych i technice operowania nimi.
Bez tego nie obejdzie się dziś żadna firma handlowa, bank, przedsiębiorstwo
produkcyjne czy organ administracji. Podobnie obliczanie pola pod zadaną
krzywą y = f(x) – to uproszczony model numerycznego całkowania, operacji
niezbędnej przy rozwiązywaniu układów równań różniczkowych. Równania
te z kolei służą do opisywania dynamiki różnych zjawisk i znajdują zastoso-
wanie w symulacji zjawisk mechanicznych, aerodynamicznych, meteorolo-
gicznych, astronomicznych itd., a więc również np. w astronautyce i w sys-
temach przewidywania pogody. Takie przykłady można by mnożyć.
W dalszej części książki poznamy sposoby rozwiązywania niektórych
spośród takich prostych zadań obliczeniowych. Będą one świadomie for-
mułowane tak, by ujmowały samą istotę problemu, bez technicznych szcze-
gółów. Choć mogą się wydawać naiwne lub wręcz dziecinnie proste, nie
powinniśmy ich lekceważyć. Należy im się uwaga i szacunek, bo są one jak
niepozorne ziarenka, z których mogą później wyrosnąć ogromne drzewa.

Weź, Jasiu, kajecik i notuj


Poszedł Jaś do sklepu, kupił zeszyty za 5 zł, długopis za 2 zł i ołówek za
1 zł. Ile złotych wydał w sumie? Z takim problemem, który bez wątpienia
można nazwać zadaniem obliczeniowym, zetknął się każdy z nas na samym
początku edukacji. Jak objaśnimy sposób rozwiązania tego zadania komuś,
kto już umie dodać do siebie dwie liczby całkowite, ale przed aż tak skom-
plikowanym problemem staje po raz pierwszy? Prawdopodobnie w pierw-
szym odruchu powiemy tak:
1. Dodaj 5 i 2, zapisz wynik.
2. Do tego wyniku dodaj 1, zapisz nowy wynik.
3. Ten nowy wynik jest rozwiązaniem zadania.
4. I koniec.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

26 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

Weź, Jasiu, kajecik i notuj 27

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

28 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

Gdyby N = 2, zawartość nawiasu powtórzyłaby się, a każde dalsze zwięk-


szenie N o 1 dorzuca jeszcze jeden obieg pętli, czyli 5 instrukcji itd. Tak
więc liczba instrukcji potrzebnych do zsumowania listy N liczb naszą meto-
dą wynosi 5 + 5N.
Ten ciąg kilku prostych instrukcji, bez żadnej modyfikacji, poradzi
sobie równie dobrze z listą trzech wydatków Jasia, jak z trzystu tysiącami
wydatków dużej firmy: oczywiście, trzeba będzie wtedy obiec pętlę trzysta
tysięcy razy. Obejmuje on również przypadek, kiedy lista wydatków jest
pusta.
Wygodnym sposobem przedstawiania algorytmu jest sieć działań
(ang. flow diagram). Na rysunku 2.1 pokazana jest sieć działań, odpowia-
dająca prostemu algorytmowi sumowania listy liczb, który zapisaliśmy wy-
żej w formie tekstowej. Choć poszczególne kroki są nieco inaczej zapisane
i nie są ponumerowane, to jednak stosowana tu symbolika jest chyba dość
oczywista. Poszczególne bloki odpowiadają oddzielnym krokom algoryt-
mu, zaś strzałki wskazują ich kolejność. Bloki o kształcie rombu oznaczają
badanie warunku, który jest napisany w ich środku i podjęcie odpowiedniej
decyzji. W naszym przypadku badany warunek ma postać pytania: „czy

Rys. 2.1. Sieć działań dla sumowania listy liczb


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Weź, Jasiu, kajecik i notuj 29

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.

1 Matematyków doprowadza to do palpitacji serca, ponieważ w matematyce znak


równości ma dobrze określone znaczenie, a napisanie, że i = i + 1 prowadzi na-
tychmiast do wniosku, że 0 = 1.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

30 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

Tak czy inaczej, musi być dobrze określone, co zrobić po wykonaniu


każdej instrukcji.
Trzecia własność, której oczekujemy od algorytmu, jest bardzo waż-
na, choć może nam w pierwszej chwili nie przyjść do głowy. Zauważyliśmy
już, że algorytm w postaci napisanej – to nie jest to samo, co ciąg instrukcji
wykonywanych w czasie realizacji tegoż algorytmu. Liczba kroków potrzeb-
nych do wykonania algorytmu zależy nie tylko od konstrukcji samego algo-
rytmu, lecz również od ilości (a często również od wartości) danych, któ-
re przetwarza. Oczekujemy jednak, że liczba kroków wykonania, choć być
może bardzo wielka, będzie skończona, to znaczy, że wykonanie algorytmu
kiedyś się zakończy. W przeciwnym przypadku nasz przepis na rozwiązanie
jest bezużyteczny i nie zasługuje na miano prawdziwego algorytmu.
Ujmijmy to inaczej: każdy algorytm ma początek i co najmniej jed-
no miejsce (a być może kilka miejsc, to całkiem dopuszczalne), w którym
napisano „koniec”, „stop” lub „zatrzymaj się”. Chcemy mieć gwarancję, że
skoro rozpoczniemy wykonywanie algorytmu od miejsca „początek”, to po
skończonej liczbie kroków dotrzemy do jednego z miejsc z napisem „ko-
niec”. Jeśli tak jest, to informatyk powie (wprawdzie niezbyt zręcznie po
polsku), że ten algorytm „ma własność stopu”.
Nasz algorytm sumowania listy liczb tę własność stopu ma. „Idzie” on
stopniowo po liście, za każdym obiegiem pętli przesuwa się na niej o jedną
pozycję i – jeśli tylko sama lista jest skończona – na pewno dotrze do koń-
ca, poda wynik i zatrzyma się. Ale nie zawsze musi tak być. Wkrótce po-
znamy pewien niby-algorytm, który wygląda na pozór całkiem przyzwoicie,
w praktyce się zatrzymuje, ale nikt dotąd nie potrafił udowodnić, że zawsze
musi się kiedyś zatrzymać, czyli – że ma własność stopu. Warto uświadomić
sobie, że własność stopu jest niezbędna.

Przeszukiwanie listy nieuporządkowanej


i uporządkowanej
W powyższym przykładzie sporządziliśmy listę liczb po to, by obliczyć ich
sumę. Jednak jeśli taką listę już mamy (a wiemy, że może być bardzo długa),
to może się zdarzyć, że zechcemy ją wykorzystać w inny sposób. Możemy
na przykład być ciekawi, czy na tej liście jest pewna liczba, o konkretnej
zadanej wartości K. Jeśli tak, to chcielibyśmy wiedzieć, na której pozycji
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Przeszukiwanie listy nieuporządkowanej i uporządkowanej 31

listy się ona znajduje. Spróbujmy skonstruować algorytm, który zaspokoiłby


naszą ciekawość.
Oczywiście, listę trzeba przeszukać, porównując jej kolejne pozy-
cje z zadanym kluczem K. Kluczem jest pewna zadana, konkretna wartość,
taka jak na przykład 3. Wynik działania algorytmu może być dwojaki. Jeśli
stwierdzi, że poszukiwana liczba rzeczywiście jest na liście – powinien po-
dać numer pozycji, na której tę liczbę (zgodną z kluczem) znalazł, a na-
stępnie zatrzymać się. Jeżeli tej liczby nie ma – powinien napisać „nie ma”
i również zakończyć działanie.
Schemat algorytmu przeszukiwania listy jest pokazany na rysun-
ku 2.2. Jest on równie mało skomplikowany, jak algorytm sumowania listy
liczb. Podobnie jak tamten zawiera pętlę, ale tym razem warunki wyjścia
z tej pętli są dwa i dwa są też sposoby zatrzymania się. Jedną z operacji
warunkowych jest sprawdzanie, czy dojechaliśmy już do końca listy, drugą

Rys. 2.2. Przeszukiwanie listy nieuporządkowanej


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

32 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

– badanie zgodności z kluczem K. Podaną sieć działań Czytelnik przeanali-


zuje bez trudności sam.
Zauważmy, że algorytm przeszukiwania różni się od algorytmu su-
mowania pewną istotną właściwością. W przypadku sumowania liczba
obiegów pętli była równa liczbie pozycji na liście. Teraz – zależy ona od
przypadku, a ściślej – od zawartości i ułożenia liczb na liście. Jeżeli szczę-
śliwym zbiegiem okoliczności poszukiwana liczba jest na liście pierw-
sza, to algorytm znajdzie ją i zatrzyma się niemal natychmiast. Jednak
w najgorszym przypadku, kiedy poszukiwana liczba jest na liście ostatnia
albo nie ma jej wcale – algorytm, aby się zatrzymać, będzie musiał od-
wiedzić wszystkie pozycję na liście. Jeżeli lista liczy N pozycji, to trze-
ba będzie pracowicie obiec pętlę N razy. Dzieje się tak, ponieważ lista
jest nieuporządkowana. Spisywaliśmy ją, nie zwracając uwagi na wartości
liczb, w takiej kolejności, jak nam one przychodziły do głowy albo – w ja-
kiej Jaś dokonywał zakupów.
Co by było jednak, gdyby lista była uporządkowana? Gdyby ktoś za-
dał sobie wcześniej trud takiego ułożenia znajdujących się na niej liczb,
by ich wartości narastały (lub przynajmniej nie malały) wraz z kolejnym
numerem na liście? Dla przykładu, wyobraźmy sobie następującą nieupo-
rządkowaną listę zawierającą 10 liczb całkowitych:

4; 34; 2; 1; 17; 3; 4; 234; 33, 7; #.

Ta sama lista uporządkowana w kolejności niemalejącej wyglądałaby tak:

1; 2; 3; 4; 4; 7; 17; 33; 34; 234; #.

Czynność porządkowania listy nieuporządkowanej nazywa się sortowa-


niem. Sortowanie jest samo w sobie ciekawym zadaniem obliczeniowym
i aż się prosi, by opracować dla niego odpowiedni algorytm. Zajmiemy się
tym później. Ale czy przeszukiwanie listy uporządkowanej różni się w istot-
ny sposób od przeszukiwania jej nieuporządkowanej wersji?
Nie musi, ale może. Proszę zauważyć, że algorytm z rysunku 2.2 po-
radzi sobie z dowolnym porządkiem listy, a więc również ze szczególnym
przypadkiem: listą uporządkowaną. Tyle że nie wykorzysta bardzo ważnej
informacji o tym, że lista jest już posortowana. Jeżeli natomiast wiemy, że
na pewno mamy do czynienia z listą uporządkowaną, to możemy skonstru-
ować algorytm przeszukiwania znacznie sprytniej, mianowicie wykorzystu-
jąc pomysł podziału listy na połowy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Przeszukiwanie listy nieuporządkowanej i uporządkowanej 33

Pomysł polega na tym, by – znając całkowitą długość uporządkowa-


nej listy – zlokalizować jej środek i porównać z kluczem liczbę, która się
tam znajduje. Jeżeli ta liczba jest szczęśliwym trafem zgodna z kluczem – to
przeszukiwanie można już zakończyć. Jeśli jest większa od klucza, to zna-
czy, że trzeba szukać dalej, ale nie warto przeszukiwać całej prawej połowy
listy. Znajdują się tam przecież liczby jeszcze większe (a przynajmniej nie
mniejsze) niż ta środkowa i na pewno tam zgodności z kluczem nie stwier-
dzimy. Warto więc zająć się tylko lewą połową listy. Podobnie jeśli owa
środkowa liczba jest mniejsza od klucza, to warto zająć się jedynie prawą
połową listy, zawierającą właśnie liczby coraz większe, bo tylko tam można
spodziewać się ewentualnej zgodności z kluczem.
W każdym przypadku trzeba potraktować wybraną połowę jak nową
listę, znaleźć znowu jej środek, porównać z kluczem, na podobnej zasadzie
zdecydować, w której jej połowie warto kontynuować poszukiwanie zgod-
ności i tak dalej. Za każdym kolejnym badaniem odrzucamy więc połowę
liczb, a nowa badana lista jest dwukrotnie krótsza niż w poprzednim kroku2.
Algorytm bardzo szybko dochodzi do badania listy o długości 1 (czyli po
prostu zawierającej jedną liczbę), której już podzielić się nie da. To jest
ostatnie miejsce, w którym może się ewentualnie ukrywać poszukiwana
liczba. Jeżeli ona tam istotnie jest – to już ją mamy, a jeżeli tam jej nie ma
– to nie ma jej nigdzie, bo liczba o jedną pozycję w lewo jest już taka sama
lub mniejsza od poszukiwanej, a ta o jedną pozycję w prawo – taka sama
lub większa.
Taki algorytm jest bardzo sprawny i bardzo dobrze radzi sobie ze
zwiększaniem długości uporządkowanej listy. Jeżeli na przykład przeszuku-
jemy uporządkowaną listę o długości N = 4 (tzn. zawierającej cztery liczby),
to w najgorszym razie już po dwóch podziałach dochodzimy do ostateczne-
go, decydującego badania listy o długości 1. Istotnie, w pierwszym kroku
wybieramy jedną z połówek (o długości 2), w drugim kroku dzielimy ją
na dwie połówki (każda o długości 1) i ostateczna decyzja jest już bliska.
Przeszukiwanie uporządkowanej listy dwukrotnie dłuższej (N = 8) jest tyl-
ko o jeden podział dłuższe. Rzeczywiście, już pierwszy podział sprowadza
nas do listy o długości N = 4 – i dalej algorytm działa tak jak poprzednio.

2 Ściślej mówiąc, w przybliżeniu dwukrotnie, bo listy o nieparzystej długości nie


da się podzielić na dokładne połowy. To techniczny szczegół niewart analizowa-
nia, ale niech Czytelnik spróbuje go rozwiązać sam.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

34 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

Uogólniając, powiemy, że do przeszukania uporządkowanej listy o długości


N = 2p potrzeba w najgorszym razie p podziałów, albo – od drugiej strony
– że do przeszukania listy o długości N potrzeba w najgorszym razie około
log2N podziałów.
Okazuje się więc, że do przeszukiwania listy uporządkowanej może-
my zastosować dwa algorytmy: jeden stary, taki sam, jak dla listy nieupo-
rządkowanej, drugi – nowy, z podziałem na połowy. Czujemy, że ten drugi
jest znacznie sprawniejszy, bardziej pomysłowy i lepiej się nadaje do prze-
twarzania długich list. Czy to poczucie można jakoś formalnie zapisać?
A może dałoby się ten lepszy algorytm wykorzystać także w przeszu-
kiwaniu list nieuporządkowanych? Przecież listę nieuporządkowaną można
by najpierw uporządkować (posortować), a potem zastosować nasz nowy
algorytm. Czy to by się w sumie opłacało? Takie pytania prowadzą nas na
trop pojęcia złożoności obliczeniowej algorytmów.
Zanim powiemy o tym więcej, zauważmy, że sam problem przeszuki-
wania listy jest uproszczonym modelem operacji zupełnie podstawowej dla
wszelkich baz danych. W powyższych przykładach poszukiwaliśmy liczby,
zadanej jako klucz przeszukiwania. Jednak równie dobrze kluczem przeszu-
kiwania mógłby być ciąg znaków, oznaczający np. nazwisko klienta w bazie
danych klientów banku, abonenta w książce telefonicznej, umowny kod ja-
kiegoś wyrobu w ewidencji magazynowej.
Wszelkie tego rodzaju spisy, ewidencje, kartoteki, bazy danych itd.
– czy to w postaci elektronicznej, czy papierowej – są zazwyczaj złożone
z indywidualnych pozycji, zwanych zapisami lub rekordami (ang. records),
a te z kolei składają się z pól (ang. fields). Dla przykładu, wyobraźmy so-
bie książkę adresową naszych znajomych. Przyjmijmy, że każdej osobie
odpowiada jeden zapis, a kolejnymi polami w każdym zapisie niech będą,
powiedzmy, imię, nazwisko, numer telefonu, miejscowość, kod pocztowy,
ulica, numer domu, numer mieszkania. Taki spis możemy wyobrażać sobie
jako wypełnioną tabelę, której wierszami są poszczególne zapisy, a kolumny
mają w nagłówku nazwy poszczególnych pól.
Jeżeli chcemy w tej tabeli odnaleźć numer telefonu pana Abackiego
– wskazujemy kolumnę nazwisk i ustalamy, że kluczem przeszukiwania ma
być „Abacki”. Opisany wyżej algorytm przeszukiwania przebiegnie tę ko-
lumnę (tak jak poprzednio listę) i albo odpowie, że pola o takiej zawartości
nie ma w spisie, albo wypisze numer pozycji (zapisu), odpowiadającej tej
właśnie osobie. Wtedy – znając już numer zapisu (rekordu) – odczytamy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Pojęcie złożoności obliczeniowej (czasowej). Notacja O(...) 35

cały zlokalizowany zapis (czyli wiersz tabeli) i pobierzemy stamtąd numer


telefonu lub inną potrzebną informację, skojarzoną w tym wierszu z poszu-
kiwanym nazwiskiem.
Oczywiście, kiedy indziej możemy chcieć znać nazwisko osoby, któ-
rej numer telefonu pojawił się nam na wyświetlaczu aparatu telefonicznego.
Wtedy przeszukiwaną listą będzie zawartość kolumny z nagłówkiem „nu-
mer telefonu”, kluczem przeszukiwania – ów numer odczytany z wyświe-
tlacza itd. Tak więc przeszukiwanie zazwyczaj poprzedza inne czynności
wykonywane na znalezionym zapisie: odczytanie, edycję, modyfikację itd.
Są to zadania typowe dla wszelkich operacji w bazach danych.

Pojęcie złożoności obliczeniowej (czasowej).


Notacja O(...)
Powiedzieliśmy wyżej, że algorytm powinien radzić sobie ze strukturami
danych o różnej wielkości, choć sam algorytm w postaci „napisanej” – nie
zmienia się wcale. W omawianych przykładach sumowana lista zakupów
mogła mieć dowolną (ale skończoną) długość N, podobnie – dowolną dłu-
gość mogły mieć przeglądane listy itd. To bardzo pożądana cecha dobrze
skonstruowanego algorytmu.
Oczywiście, musimy liczyć się z tym, że im ów parametr N jest więk-
szy, tym do wykonania algorytmu będzie potrzebna większa pamięć, a samo
wykonanie będzie trwało dłużej. Własnością, która charakteryzuje zależność
między rozmiarem danych a tymi dwoma podstawowymi zasobami (czasem
i pamięcią) potrzebnymi do wykonania zadania, jest złożoność obliczeniowa
algorytmu (ang. computational complexity).
W teorii złożoności obliczeniowej mówi się o złożoności czasowej
(ang. time complexity) i pamięciowej (ang. space complexity). My ograni-
czymy się jedynie do podstawowej intuicji dotyczącej pierwszej z nich, czy-
li złożoności czasowej.
Ocena złożoności obliczeniowej czasowej danego algorytmu nie pole-
ga na dokładnym wyliczeniu, ile konkretnie czasu (mierzonego w fizycznych
jednostkach, np. w sekundach) zajmie jego wykonanie. Taka dokładna war-
tość zależy przecież od wielu czynników, o których na etapie tworzenia algo-
rytmu w ogóle nie mamy jeszcze pojęcia: od wyboru języka programowania,
sposobu zapisania algorytmu w postaci programu, od sprawności translatora,
wreszcie od technicznych parametrów komputera, który ten algorytm będzie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

36 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

później wykonywał. W chwili, kiedy opracowujemy algorytm – mamy prawo


o tym jeszcze nic nie wiedzieć. Możemy jednak ocenić, jaki jest ogólny cha-
rakter wzrostu czasu wykonania algorytmu, gdy rozmiar danych rośnie.
Analizując wykonanie algorytmu sumowania N liczb, zauważyliśmy,
że pewne fragmenty algorytmu są w każdym jego przebiegu wykonywane
tylko raz, a inne (wchodzące w skład pętli) – są wykonywane tyle razy, ile
jest liczb na liście, czyli N-krotnie. Stwierdziliśmy, że liczba instrukcji wy-
konanych w trakcie jednego przebiegu algorytmu wynosi 5N + 5. W tym
przypadku czas wykonania algorytmu jest – nie wchodząc w szczegóły –
mniej więcej proporcjonalny do N, powiemy więc, że złożoność obliczenio-
wa tego algorytmu jest rzędu N, inaczej – że złożoność ta jest O(N). W tym
oznaczeniu „O” to nie jest zero, lecz litera „O” od słowa order, oznaczające-
go w języku angielskim właśnie „porządek” lub „rząd” (w takim znaczeniu,
jak np. „rząd wielkości”).
Proszę zauważyć, że gdyby dokładna zależność czasu wykonania algo-
rytmu od N miała postać (100N + 1500) sekund albo (1556N + 39 876) mikro-
sekund – to w obu przypadkach rząd złożoności byłby taki sam, mianowicie
O(N). Nie są bowiem ważne konkretne wartości liczbowe współczynników
tego wyrażenia: decydujący jest fakt, że sam charakter wzrostu jest liniowy:
jeśli pominąć część stałą – proporcjonalny do N.
Drugi z omawianych algorytmów, ten dotyczący przeszukiwania li-
sty nieuporządkowanej, ma również złożoność O(N). Istotnie, w najgorszym
przypadku każdą z N pozycji na liście należy jednokrotnie odwiedzić, by
stwierdzić, czy nie jest to właśnie ta poszukiwana liczba. Analizowanie wy-
konania algorytmu w najlepszym przypadku byłoby niecelowe: jeśli szczę-
śliwym trafem poszukiwana liczba znajduje się na samym początku listy
– algorytm wykryje ją już w pierwszym kroku, niezależnie od N. Żadnej
zależności od N nie dałoby się wtedy stwierdzić.
Z kolei algorytm przeszukiwania listy uporządkowanej (ten wykorzy-
stujący pomysł wielokrotnego podziału na połowy) ma złożoność O(log N).
Istotnie, wykazaliśmy, że do przeszukania listy o długości N potrzeba (znów
w najgorszym razie) około log2N kroków. Podobnie jak poprzednio, nie jest
ważne, czy jest to logarytm o podstawie 2, e, czy 10, czy czas wykona-
nia będzie równy (1330 + 26 log2N) sekund, czy (25 + 2log10 N) sekund:
o rzędzie złożoności decyduje to, że ogólny charakter wzrostu (w zależności
od N) jest logarytmiczny.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Pojęcie złożoności obliczeniowej (czasowej). Notacja O(...) 37

Rozpatrzmy jeszcze inny przykład. Wyobraźmy sobie, że mamy dwa


grafy: G1 i G2, jak na rysunku 2.3. Chcemy je porównać, by stwierdzić,
czy są one identyczne. No, całkiem identyczne to one nie są, bo w jed-
nym węzły oznaczone są cyframi (1, 2, …, 6), a w drugim – literami (a, b,
…, f ). Powiedzmy jednak, że znamy zasadę numeracji węzłów w obu gra-
fach i wiemy, iż węzłowi 1 w grafie G1 odpowiada węzeł a w G2, węzłowi
2 w G1 – b w G2 itd., tak jak w tabeli 2.1.

Rys. 2.3. Czy grafy G1 i G2 mają tę samą budowę?

Tab. 2.1. Odwzorowanie węzłów grafów G1 i G2

W grafie G1 W grafie G2
1 a
2 b
3 c
4 d
5 e
6 f

Czy przy tak zadanej odpowiedniości węzłów oba grafy są izomorficzne,


to znaczy, czy są „tak samo zbudowane”? Chodzi o to, by upewnić się,
czy jeżeli w grafie G1 istnieje krawędź (strzałka) od węzła np. 1 do 2, to
w grafie G2 również odpowiednie węzły (np. a i b) są połączone strzałką.
I odwrotnie, jeśli w grafie G1 takiej krawędzi nie ma – to nie ma jej też
w grafie G2.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

38 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

W pierwszej chwili pytanie może się wydać wręcz banalne. Przecież


jeden rzut oka na rysunek 2.3 pozwala natychmiast stwierdzić, że te dwa
grafy istotnie są izomorficzne. Tak, ale nie dajmy się zwieść: rzut oka – to
nie jest algorytmiczne wykonanie zadania. „Rzucając okiem” na rysunek,
wykorzystujemy nasz biologiczny, niezwykle sprawny aparat wzrokowy
wyspecjalizowany w rozpoznawaniu kształtów. Angażuje on skomplikowa-
ny układ optyczny w naszym oku, tysiące zakończeń nerwów wzrokowych
w siatkówce oka, tory nerwowe prowadzące do obu półkul mózgowych
i wreszcie ośrodki w korze mózgowej, odpowiedzialne za rozpoznawanie,
zapamiętywanie i kojarzenie obrazów.
Podobnym aparatem – choć różniącym się w szczegółach anatomicz-
nych i funkcjonalnych – dysponują liczne gatunki zwierząt, nie tylko kręgow-
ce, lecz także owady i mięczaki, takie jak ośmiornice czy mątwy. Sprawność
tego aparatu często decyduje o przeżyciu: trzeba bowiem umieć dosłownie
w mgnieniu oka ocenić widziany obraz i zdecydować, czy ja mogę to zjeść,
czy też to może zjeść mnie. Kto tego nie potrafił robić dostatecznie sprawnie
– już albo zginął z głodu, albo sam został zjedzony.
Patrząc na grafy G1 i G2, mimowolnie wykorzystujemy więc to,
co odziedziczyliśmy po naszych odległych ewolucyjnych przodkach, tych
właśnie, którym udało się przeżyć. Jednocześnie nie ma argumentów, które
pozwalałyby przypuszczać, że cały ten biologiczny mechanizm działa we-
dług jakiegoś algorytmu, zbudowanego tak, jak my rozumiemy to słowo.
Zresztą, pierwsze wrażenie wzrokowe nie wystarczy. Popatrzmy na
rysunek 2.4. Grafy G1 i G3 wcale nie wyglądają na podobne, a przy takiej
samej odpowiedniości węzłów, jak w tabeli 2.1, są w istocie izomorficz-
ne, jedynie inaczej narysowane. Izomorfizm – to nie to samo co wygląd,
dlatego formułując algorytm rozstrzygania o izomorfizmie dwóch grafów,
musimy postąpić bardziej systematycznie.
Po pierwsze, powinniśmy oba grafy przedstawić w postaci uporząd-
kowanych struktur danych, nadających się do algorytmicznego przetwarza-
nia. W poprzednich przykładach strukturami danych były listy, tu wygodne
byłyby tablice. Przyjmijmy więc, że każdy z grafów opisany jest kwadrato-
wą tabelką, jak w tabeli 2.2.
Wiersze i kolumny każdej tabelki są opisane nazwami węzłów odpowied-
niego grafu, w takiej kolejności, jak na to wskazuje tabelka odwzorowa-
nia węzłów. W środku tabelki, w każdym okienku znajdującym się w i-tym
wierszu i j-tej kolumnie napiszmy znaczek np. „x” (albo 1), jeżeli w grafie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Pojęcie złożoności obliczeniowej (czasowej). Notacja O(...) 39

Rys. 2.4. Czy grafy G1 i G3 są izomorficzne?

Tab. 2.2. Tablice sąsiedztwa grafów G1 oraz G3


do do
G1 G3
1 2 3 4 5 6 a b c d e f
1 x x a x x
2 b
3 x x c x x
od od
4 x d x
5 x e x
6 x x f x x

istnieje krawędź prowadząca od węzła i do węzła j. Jeżeli takiej krawędzi


nie ma – pozostawmy to okienko puste (albo napiszmy tam zero). Takie
tablice (zwane tablicami lub macierzami sąsiedztwa, ang. adjacency matrix)
dokładnie opisują budowę grafu.
Nie będziemy tu szczegółowo opisywali sieci działań algorytmu
stwierdzania izomorfizmu grafów G1 i G3. Wystarczy powiedzieć, że powi-
nien on porównać ze sobą wszystkie pary odpowiadających sobie okienek
w obu tabelach sąsiedztwa. Jeżeli zawartość okienka (1, 1) tabeli grafu G1
jest identyczna, jak okienka (a, a) tabeli G3 (tzn. oba zawierają znaczek „x”
albo oba są puste) – to należy podobnie sprawdzić okienka (1, 2) oraz (a, b).
Jeżeli i te zawartości są identyczne – należy sprawdzić parę okienek (1, 3)
oraz (a, c) i tak dalej. W ten sposób, wiersz po wierszu (lub kolumna po
kolumnie) trzeba przebiec całą tabelę. Stwierdzenie niezgodności w której-
kolwiek parze komórek natychmiast kończy działanie algorytmu i powoduje
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

40 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

ogłoszenie werdyktu negatywnego: nie, te grafy izomorficzne nie są. Jeżeli


natomiast zgodność utrzyma się do samego końca porównywania par komó-
rek z obu tabel – decyzja jest pozytywna.
Łatwo ustalić, jaka jest złożoność obliczeniowa opisanego algorytmu.
Tabele sąsiedztwa są kwadratowe, dlatego (gdy N jest liczbą węzłów obu
grafów) zawierają po N 2 komórek każda. Tyle też porównań potrzeba do
zakończenia algorytmu. Sam algorytm może oczywiście mieć jeszcze część
stałą, niezależną od N, a także jakieś części wprost proporcjonalne do N,
więc generalnie zależność czasu wykonania algorytmu od liczby N może być
wielomianem drugiego stopnia, o ogólnej postaci T = aN 2 + bN + c. Jednak
podobnie jak poprzednio, dla ustalenia rzędu złożoności obliczeniowej nie są
ważne wartości współczynników a, b i c ani obecność części proporcjonalnej
do N, a jedynie fakt, że dominującym składnikiem wielomianu jest ten, który
zawiera N 2. Dlatego złożoność obliczeniowa jest w tym przypadku O(N 2).
Sytuacja byłaby znacznie trudniejsza, gdybyśmy nie znali wzajemnej
odpowiedniości między węzłami obu grafów, która w powyższym przykła-
dzie była po prostu z góry zadana w postaci tabeli 2.1. Algorytm rozwią-
zania tak rozszerzonego zadania musiałby wypróbować wiele możliwych
wzajemnych odwzorowań zbiorów węzłów. Jak wiele? Wyobraźmy sobie
tabelkę taką jak tabela 2.1, ale o na razie niewypełnionej prawej kolumnie.
Aby tę kolumnę wypełnić, musimy w nią wpisać pionowo sześć liter (od a
do f, w dowolnej kolejności). Możemy to zrobić na 6! = 1 · 2 · 3· 4 · 5 · 6 =
= 720 sposobów. Tak więc, chcąc odpowiedzieć na pytanie o izomorfizm
dwóch grafów o N węzłach i nieznanym sposobie wzajemnego odwzorowa-
nia węzłów – musimy wypróbować N! tabelek odpowiedniości, a dla każdej
z nich utworzyć parę tablic sąsiedztwa i przeprowadzić N 2 porównań.
W teorii złożoności obliczeniowej zadania, wymagające tego typu
algorytmów, są nazywane problemami klasy NP. Co ten skrót znaczy
i jakie (doprawdy paskudne) właściwości mają znane obecnie algorytmy
dla problemów NP – powiemy w następnym rozdziale.
Na razie powróćmy do pytania, jak można formalnie porównywać ze
sobą rzędy złożoności obliczeniowej, a dzięki temu orzekać, czy pewien algo-
rytm A jest „lepszy” – w sensie złożoności obliczeniowej – od algorytmu B.
Problem ten jest ważny, ponieważ algorytmy mogą znacznie różnić
się między sobą pod względem złożoności obliczeniowej. Sami poznali-
śmy do tej pory przykłady algorytmów o złożoności O(N), O(N 2), O(logN).
Nietrudno jest wyobrazić sobie, że istnieją także algorytmy o złożoności
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Pojęcie złożoności obliczeniowej (czasowej). Notacja O(...) 41

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

42 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

To rozumowanie można łatwo uogólnić. Powiemy, że rząd złożoności


O1 jest mniejszy od rzędu O2, jeżeli (niezależnie od natury urządzeń wyko-
nujących te algorytmy) istnieje taka graniczna wartość N*, że dla wszystkich
N > N* fizyczny czas wykonania algorytmu o złożoności O1 jest krótszy niż
algorytmu o złożoności O2 (choć dla N < N* warunek niekoniecznie musi
być spełniony). W świetle takiej definicji można więc legalnie napisać, że
O(logN) < O(N) < O(N2) albo że O(N5) < O(2N) itd.

Problem Collatza i badanie własności stopu


Zajmijmy się teraz zadaniem obliczeniowym, które nie ma na celu oblicze-
nia czegoś szczególnie użytecznego, a jedynie służy do zilustrowania innej,
niepokojącej właściwości programów, które przecież są – a raczej powin-
ny być – algorytmami, tyle że zapisanymi w pewnym ustalonym języku.
Zadanie to jest znane pod nazwą problemu Collatza.
Wyobraźmy sobie mianowicie program, którego jedyną daną wej-
ściową (zadawaną przez użytkownika) jest liczba naturalna X. Jeżeli licz-
ba ta jest równa 1, to program zatrzymuje się. Jeżeli X jest większe od 1
(a mniejsze być nie może, skoro jest liczbą naturalną) – to program bada,
czy X jest liczbą parzystą. Jeżeli tak, to program zastępuje dotychczasowe
X ułamkiem . Jeżeli natomiast X jest liczbą nieparzystą – program zastę-
puje dotychczasowe X przez (3X + 1). W obu przypadkach program wraca
do badania, czy obecnie X jest już równe 1. Jeśli tak – zatrzymuje się, jeśli
nie – znowu bada parzystość liczby, zastępuje X nową wartością (zgodnie
z podanymi regułami) i tak dalej. Opisane zasady można łatwo przedstawić
w postaci sieci działań lub zapisać w postaci programu.
Dla zilustrowania zasady działania tego programu przyjmijmy, że za-
daliśmy na początku wartość X = 9. Kolejne wartości X produkowane w ko-
lejnych obiegach pętli będą następujące:

9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1

i w tym momencie program zatrzyma się. Takich eksperymentów możemy


przeprowadzić więcej, dla różnych wartości początkowych X i stwierdzimy
zapewne (jak wielu, którzy to robili przed nami), że w praktyce ten pro-
gram w końcu osiąga 1 i zatrzymuje się. Jednak liczba kroków (obiegów
pętli) potrzebna do zatrzymania się jest zupełnie nieprzewidywalna. Może
się więc zdarzyć, że przyjąwszy na początku jakąś wartość X, spędzimy (my
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Problem Collatza i badanie własności stopu 43

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

44 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

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

Problem Collatza i badanie własności stopu 45

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

46 2. O algorytmach i złożoności obliczeniowej na kilku łatwych przykładach

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

48 3. Trudne problemy, które wybuchają

Rys. 3.1. Jaką drogą ma pojechać komiwojażer?

G – Gdańsk, P – Poznań, R – Rzeszów). Linie (krawędzie grafu) symbolizu-


ją drogi między nimi. Załóżmy przy tym, że graf jest pełny i nieskierowany,
to znaczy, że – po pierwsze – między każdą parą miast istnieje oddzielna
droga i – po drugie – że drogi te nie są jednokierunkowe. Odległości między
miastami można znaleźć w atlasie samochodowym albo skorzystać (jak my
tu zrobiliśmy) z jednego z serwisów internetowych, oferujących takie wyli-
czenia. Wypisaliśmy je obok odpowiednich linii.
Po zgromadzeniu niezbędnych danych samo planowanie trasy nie wy-
gląda na zadanie szczególnie trudne. Trzeba wziąć kartkę papieru, długopis,
ewentualnie kalkulator, wypisać wszystkie możliwe trasy, a następnie obli-
czyć ich długości i wybrać najkrótszą z nich. Można to zrobić w systema-
tyczny sposób. Jak? Trzeba posłużyć się tzw. drzewem decyzyjnym, takim
jak na rysunku 3.2. Jego interpretacja jest prosta, należy jedynie pamiętać,
że w informatyce drzewa mają zazwyczaj korzeń u góry, a rosną liśćmi do
dołu.
W naszym przypadku wiemy, że wyruszamy z Warszawy. Warszawa
(W) jest więc korzeniem drzewa decyzyjnego. Wybierając pierwsze miasto,
które odwiedzimy (a więc pierwszy odcinek drogi), mamy do wyboru trzy
możliwe decyzje: może być to Gdańsk, Poznań albo Rzeszów. Dlatego od
korzenia w dół rosną trzy gałęzie, zakończone węzłami G, P i R. Ich kolej-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Komiwojażer ma problem 49

Rys. 3.2. Drzewo decyzyjne komiwojażera dla N = 3

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

50 3. Trudne problemy, które wybuchają

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

„drugich” możliwości, każda z drugich na (N – 2) trzecich i tak dalej. W re-


zultacie liczba liści drzewa (a więc też i cykli do porównania) jest równa

N · (N – 1) · (N – 2) · ... · 3 · 2 · 1 = N!

Znamy to wyrażenie z działu matematyki, zwanego kombinatoryką.


To silnia liczby N.
Dla rozwiązania zadania, dla każdego z N! wariantów trasy trzeba ob-
liczyć jej długość, która sama jest sumą N odcinków. Inaczej mówiąc – ob-
liczenie długości jednej drogi ma złożoność O(N), jednak takich obliczeń
trzeba wykonać N! Dlatego złożoność obliczeniowa opisanego algorytmu
rozwiązania problemu komiwojażera jest O(N ⋅ N!). W praktyce, zdecydo-
wanie dominujący wpływ na ilość niezbędnych obliczeń ma czynnik N!,
dlatego uznaje się, że dla problemu komiwojażera złożoność obliczeniowa
jest po prostu O(N!).
To się bardzo łatwo pisze: N z wykrzyknikiem, łatwo czyta się: „N sil-
nia”, ale warto sobie uświadomić, jaki obliczeniowy koszmar ten niewinny
symbol oznacza. Widzieliśmy, że jeśli komiwojażer ma odwiedzić 3 miasta,
to możliwych cykli Hamiltona jest zaledwie 6 (bo 3! = 1 ⋅ 2 ⋅ 3 = 6). Jeżeli
miast jest 4, to dróg do porównania jest 4! = 1 ⋅ 2 ⋅ 3 ⋅ 4 = 3! ⋅ 4 = 24. Jeśli
5 – to jeszcze pięć razy więcej, czyli 120. Jeśli 6 – to 720: sześć razy więcej
niż w przypadku pięciu miast. Sytuacja robi się niepokojąca: tego się już nie
da tak łatwo policzyć z użyciem kartki papieru i kalkulatora.
Puśćmy wodze fantazji: dajmy komiwojażerowi komputer, który po-
trafi wyliczyć i porównać w ciągu jednej sekundy milion dróg. Mniejsza
o szczegóły: chodzi tu tylko o poglądowe pokazanie wpływu czynnika N!,
więc załóżmy dla uproszczenia, że czas wyliczania długości jednej drogi nie
zależy od N. Takiemu komputerowi znalezienie najkrótszej trasy dla N = 3
zajęłoby jedynie 6 mikrosekund, czyli sześć milionowych części sekundy.
Niestety, nawet komputer też nie na wiele się przyda, jeśli komiwojażer pla-
nuje odwiedzenie 15 czy 20 miast.
W tabeli 3.1 podano, ile czasu taki hipotetyczny komputer (wylicza-
jący i porównujący milion dróg na sekundę) rozwiązywałby problem ko-
miwojażera dla różnych (i wcale nie tak wielkich) wartości N. Rzut oka na
prawą kolumnę robi wrażenie. Może ktoś nie uwierzy, ale proszę sprawdzić:
rok ma 31 536 000 sekund, więc nasza maszyna porównałaby między sobą
w tym czasie 31 536 000 000 000 dróg. Jednak znalezienie najkrótszej spo-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

52 3. Trudne problemy, które wybuchają

Tab. 3.1. Czas rozwiązywania problemu komiwojażera przez hipotetyczny komputer


N Czas
3 0,000006 s
4 0,000024 s
9 0,36 s
10 3,6 s
12 8 min
14 > 1 doba
15 > 2 tygodnie
17 ~ 11 lat
18 ~ 200 lat
20 >77 000 lat

śród dróg pomiędzy zaledwie dwudziestoma miastami wymagałoby ponad


77 000 lat takich obliczeń!
Poznaliśmy w ten sposób wyjątkowo podstępne zjawisko, które na-
zywa się wykładniczą eksplozją problemu (ang. exponential explosion).
Początkowo nic nie zapowiadało kłopotów: łatwo udało się nam sformuło-
wać algorytm rozwiązania zadania, skutecznie go wykonaliśmy dla przypad-
ku trzech miast i ucieszyliśmy się, że udało się zaoszczędzić ponad 350 km
trasy. Kiedy jednak spróbowaliśmy uogólnienia – nasz pomysł dosłownie
wybuchł nam w rękach, rosnąc do potwornych rozmiarów.
To bardzo niepokojący wniosek. Z jednej strony pocieszające jest, że
algorytm istnieje, z drugiej – cóż nam z tego? Można się nim posłużyć tylko
w przypadku niektórych, małych zadań, natomiast jest zupełnie niepraktycz-
ny i nie do zaakceptowania, jeśli rozmiar grafu jest większy. Nikt nie zgodzi
się czekać latami czy tysiącami lat na rozwiązanie zadania.
Co gorsza, żaden postęp technologiczny nie obroni nas przed wredną
naturą tego zjawiska. W przeszłości bywało już tak, że jakieś obliczenia
trwały nieakceptowalnie długo, ale rozwój technologii budowy kompute-
rów, wykorzystanie systemów wieloprocesorowych itd. pozwoliły na spro-
wadzenie tych obliczeń do rozsądnych ram czasowych. Tu – nie może to
mieć miejsca. Nawet gdybyśmy posłużyli się superkomputerem, który byłby
w stanie wyliczyć i porównać nie milion, lecz dziesięć miliardów dróg na
sekundę – to i tak rozwiązanie problemu dla N = 20 zajęłoby 7,7 roku, czyli
prawie osiem lat. A dla N = 21? Dwadzieścia jeden razy więcej, czyli około
stu sześćdziesięciu lat. A dla N = 22? Jeszcze dwadzieścia dwa razy więcej:
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dlaczego NP? 53

prawie trzy tysiące trzysta lat. Fakt, że przy założonej dwukierunkowości


dróg różnych tras jest naprawdę dwukrotnie mniej – też przynosi niewielką
pociechę. Połowa N! – to i tak dostatecznie dużo.
Niestety, porzućcie wszelką nadzieję: wykładniczej eksplozji nie do-
goni nikt.
Są jeszcze inne złe wiadomości. Problem komiwojażera nie jest wy-
jątkiem. Problemów, dla których algorytmy istnieją, lecz eksplodują wy-
kładniczo, jest bardzo wiele. Jak już wspomniano, szczególnie ważną klasą
takich problemów są problemy NP.

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

54 3. Trudne problemy, które wybuchają

Skrót NP łączy dwa angielskie terminy: Nondeterministic (niedetermi-


nistyczny) oraz Polynomial (wielomianowy). Sens ich użycia dobrze widać
choćby na przykładzie problemu komiwojażera. Obliczenie długości jednej
drogi wymaga sumowania N liczb i ma złożoność O(N). Jest to złożoność
wielomianowa (ang. polynomial complexity), ponieważ zależność czasu wy-
konania od N jest wielomianem, w tym przypadku – pierwszego stopnia.
Kłopot polega jednak na tym, że takich obliczeń może być potwornie wiele.
Co gorsza, nie ma żadnych reguł, które podpowiedziałyby nam, w jakiej
kolejności należy generować i badać kolejne warianty trasy podróży. Gdy
produkujemy kolejny wariant trasy, nic nie ogranicza naszej swobody wy-
boru kolejnych (oczywiście nieodwiedzonych dotąd) odcinków drogi, inny-
mi słowy – nic nie determinuje zasad wyboru. Mówimy, że taki dowolny,
losowy wybór jest niedterministyczny.
Gdyby istniała maszyna, kierowana nadnaturalną intuicją lub popy-
chana palcem bożym, która by w każdej sytuacji wyboru podejmowała za-
wsze najlepszą decyzję – rozwiązałaby zadany jej problem NP w wielomia-
nowym czasie. Niestety, nie dysponujemy takimi cudownymi urządzenia-
mi i jesteśmy zdani na mozolne sprawdzanie ogromnej liczby możliwości.
W praktyce, zależnie od przypadku, może nam się udać znaleźć rozstrzy-
gnięcie problemu decyzyjnego (lub najkrótszą drogę w przypadku „oblicze-
niowej” wersji problemu) już po zbadaniu pierwszej losowo wybranej wer-
sji trasy, ale równie dobrze może się to zdarzyć po dalszych milionach prób
albo dopiero za ostatnim razem.
Tak więc problemy klasy NP to zadania „niedeterministycznie wielo-
mianowe”, czyli takie, dla których znamy algorytmy, składające się z bardzo
wielu podobliczeń (a każde o wielomianowej złożoności), które mogą być
wykonywane w niedeterministycznej, dowolnej kolejności. O złożoności
obliczeniowej decyduje jednak czas rozwiązania w najgorszym, najbardziej
niekorzystnym przypadku, dlatego złożoność problemów NP ma – jak się
nam dziś wydaje – charakter wykładniczy.
Podobną naturę ma algorytm orzekania o izomorfizmie dwóch gra-
fów, którym zajmowaliśmy się w poprzednim podrozdziale. Pokazaliśmy
tam, że jeżeli wzajemne odwzorowanie zbiorów węzłów grafu jest znane
(jak w tabeli 2.1), to stwierdzenie ewentualnego izomorfizmu dwóch gra-
fów o N węzłach wymaga N 2 porównań zawartości poszczególnych okienek
dwóch tablic sąsiedztwa. Jest więc obliczeniem o wielomianowej złożoności:
O(N2). Jednak, gdybyśmy takiej tabeli 2.1 nie mieli – musielibyśmy kolej-
no wypróbowywać różne wersje wzajemnego przyporządkowania węzłów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dlaczego NP? 55

Kombinatoryka podpowiada, że różnych sposobów skonstruowania tabelki


odpowiedniości jest w najgorszym przypadku również N! Dla każdej z N!
wersji trzeba oczywiście wyprodukować nowe (inaczej uporządkowane) ta-
bele sąsiedztwa, ponowić sprawdzanie zgodności N 2 par komórek itd.
W rezultacie, podobnie jak w przypadku problemu komiwojażera,
całe obliczenie składa się z ogromnej liczby prostych podobliczeń o wielo-
mianowej złożoności. Również i tu nie ma żadnej reguły co do zalecanej ko-
lejności sprawdzania różnych wersji przyporządkowania węzłów. Możemy
mieć szczęście i stwierdzić izomorfizm już po wypróbowaniu pierwszej
wersji przyporządkowania, ale równie dobrze może się zdarzyć, że uda się
to nam dopiero za ostatnim razem albo wcale. Tak więc problem orzekania
o izomorfizmie grafów jest też NP.
Płynie z tego ważna nauczka. Jeżeli przy próbie sformułowania al-
gorytmu dla jakiegoś zadania przyjdzie nam do głowy myśl, że trzeba by
po prostu sprawdzić wszystkie możliwe drogi, ułożenia, kombinacje, zależ-
ności... itp. – to uważajmy: zapewne wchodzimy na niebezpieczny grunt.
Jest bardzo prawdopodobne, że ten problem jest NP, a algorytm oparty na
takim najprostszym pomyśle zaraz nam wykładniczo eksploduje. Naiwna
wiara, że bardzo szybki komputer sobie poradzi – może się wkrótce zamie-
nić w rozczarowanie.
Ale przecież – z drugiej strony – problemy NP jakoś rozwiązywać
trzeba. Nie są one jedynie intelektualną łamigłówką dla miłośników mate-
matyki czy informatyki. To uproszczone modele problemów występujących
w praktyce. Codziennie z hurtowni i magazynów wyjeżdżają tysiące sa-
mochodów, z których każdy ma zawieźć towar do kilkudziesięciu sklepów
i wrócić do bazy. Wielkie TIR-y wyruszają na trasy, mające tysiące kilo-
metrów, odwiedzają dziesiątki miast i wracają do swoich baz. Każda taka
podróż jest w zasadzie cyklem Hamiltona wykonywanym na ulicach miast
lub drogach świata. Znajdowanie możliwie najkrótszych dróg jest dla przed-
siębiorstw transportowych istotnym problemem optymalizacji, ważnym ze
względów ekonomicznych, technicznych i ekologicznych.
Podobnie, przy produkcji podzespołów komputerowych (np. płyt
głównych, kart graficznych.) na każdej płycie trzeba precyzyjnie nawiercić
kilkaset czy nawet kilka tysięcy otworków, w które potem będą montowane
końcówki układów scalonych. Tę operację wykonuje programowany robot,
który musi odwiedzić każdy z zaplanowanych punktów, nawiercić otwór,
przesunąć się do następnego punktu, znów nawiercić otwór itd., obiec w ten
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

56 3. Trudne problemy, które wybuchają

sposób całą płytę – i wreszcie wrócić do położenia początkowego tak, by


wkrótce mógł powtórzyć całą operację dla następnego egzemplarza urzą-
dzenia. Choć odległości między odwiedzanymi punktami nie są tu mierzone
w kilometrach, tylko w milimetrach, to przed robotem (a właściwie – przed
specjalistą, który go programuje) stoi ten sam problem, co przed listono-
szem czy komiwojażerem. Znalezienie możliwie krótkiego cyklu Hamiltona
skróciłoby czas produkcji podzespołu, pozwoliło spowolnić proces zużywa-
nia się mocno eksploatowanych układów pozycjonowania robota itd.
Dlatego zarówno dla problemu komiwojażera, jak dla innych, niżej
omawianych problemów NP opracowuje się algorytmy, które może nie gwa-
rantują stuprocentowej uniwersalności i stuprocentowej optymalności wyni-
ku, lecz przynajmniej oferują rozwiązanie praktycznego problemu w moż-
liwym do zaakceptowania czasie, a także przyzwoitą jakość przybliżonego
rezultatu. O sposobach poszukiwania takich algorytmów powiemy nieco
więcej w rozdziale 4, poświęconym metodom algorytmicznym.

Plecak, układanki i różne zabawy z kredkami


Przykładem zadania NP innego typu, niż znajdowanie drogi w grafie,
jest problem plecakowy (ang. knapsack problem). Mamy oto plecak, któ-
ry w uproszczeniu możemy sobie wyobrażać jako duży prostokąt o zna-
nych wymiarach. Do tego plecaka ładujemy różne przedmioty, które (w tym
uproszczeniu) są mniejszymi prostokątami, o różnych wymiarach. Każdy
przedmiot ma też pewną wartość, niezależną od rozmiaru. Niestety, wszyst-
kie przedmioty, a jest ich N, nie mieszczą się jednocześnie w plecaku.
Zadanie polega na tym, by tak załadować plecak, aby łączna wartość zgro-
madzonych w nim rzeczy była jak największa1. Można też inaczej zdefinio-
wać cel zadania: na przykład – upakować plecak tak, żeby pozostało w nim
jak najmniej niewykorzystanego miejsca. Równoważna, decyzyjna wersja
problemu może np. polegać na rozstrzygnięciu („tak” albo „nie”), czy war-
tość przedmiotów zgromadzonych w plecaku może przekroczyć pewną za-
daną wielkość.
W każdym przypadku, pełne, naiwne rozwiązanie problemu plecako-
wego powinno polegać na rozpatrzeniu wszystkich możliwych ułożeń przed-

1 Właściwie, skoro pierwsze z przykładowych zadań nazywało się problemem ko-


miwojażera, to to zadanie powinno się nazywać problemem złodzieja.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Plecak, układanki i różne zabawy z kredkami 57

miotów w plecaku i znalezieniu wśród nich takiego, które najlepiej spełnia


warunki zadania. Skoro wszystkich możliwych – to nie dziwi nas, że takie
rozwiązanie eksploduje wykładniczo wraz z liczbą przedmiotów, czyli N.
Oczywiście, z własnego doświadczenia wiemy, jak kłopotliwym za-
daniem bywa pakowanie plecaka czy walizki przed wakacjami. Jesteśmy
często zmuszeni do bolesnych decyzji: co zabrać, a co pozostawić. Jednak
naprawdę nie o plecak tu chodzi. Planowanie wydatków rodziny czy firmy,
planowanie kampanii marketingowej, a nawet budżetu państwa itd. – jest
też odmianą problemu plecakowego. Mamy zwykle ograniczony budżet (na
ograniczony czas) i zazwyczaj tak wiele rzeczy do kupienia (lub zadań do
wykonania), że wszystkie się w tym budżecie (i w czasie) nie zmieszczą.
Budżet jest więc plecakiem, którego rozmiarami są pieniądze i czas. Jak
w nim upakować niemieszczące się zakupy lub zadania? Z których zrezy-
gnować, a które pozostawić? To praktyczne zadanie optymalizacji, dające
się przedstawić w anegdotycznej postaci zadania plecakowego.
A układanie rozkładu zajęć dla szkoły lub uczelni? Plecakiem jest tu
plan tygodnia. Musimy w nim poukładać prostokąciki różnych zajęć: lek-
cji, wykładów, laboratoriów itd. W tym przypadku – zapewne się wszystkie
zmieszczą, ale możemy zażądać innych kryteriów optymalności rozwiąza-
nia: na przykład, żeby jeden dzień w tygodniu był wolny, żeby nie było
„okienek” między zajęciami, żeby zajęcia nie trwały w żadnym dniu dłużej
niż sześć godzin itd. Muszą być przy tym spełnione oczywiste ograniczenia:
jeden nauczyciel nie może prowadzić lekcji w dwóch miejscach naraz, w tej
samej sali nie powinny się odbywać naraz dwie różne lekcje itd. Mimo tych
różnic – i w tym przypadku pełne rozwiązanie problemu polega na niedeter-
ministycznym rozpatrzeniu bardzo wielu wersji możliwych układów zajęć,
sprawdzeniu każdej z nich, czy spełnia wymienione warunki itd.
Teoretycznie – powinno się więc rozpatrzyć wszystkie możliwe konfi-
guracje zajęć, praktycznie – nie może być o tym mowy, bo problem eksplo-
duje wykładniczo. Najczęściej kończy się na przyjęciu jakiejś kompromiso-
wej wersji planu zajęć, może niedoskonałej, ale możliwej do zaakceptowa-
nia. Potem, raz opracowany plan powtarza się zazwyczaj z roku na rok, być
może z pewnymi lokalnymi aktualizacjami czy modyfikacjami, ale rzadko
kiedy projektuje się go całkiem od nowa, właśnie w obawie przed wykład-
niczą złożonością problemu. Podobnie dzieje się w przypadku rozkładów
jazdy pociągów, autobusów itd.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

58 3. Trudne problemy, które wybuchają

Zbliżony charakter mają liczne zadania, które sprowadzają się do


jakiejś układanki. Tu również nie chodzi wyłącznie o rozrywkę umysłową,
taką, jak dopasowywanie kształtów puzzli po to, by powstał ładny obra-
zek. Każda firma produkująca ubrania czy bieliznę musi zadbać o to, by
wykroje, z których ta odzież powstaje, były odpowiednio poukładane na
wstędze tkaniny. Trzeba nie tylko uwzględnić wzór, kierunek prążków itd.,
ale ułożenie powinno być też takie, by jak najmniej materiału marnowało
się na ścinki. Z takim samym problemem mają do czynienia również fir-
my produkująca detale wytłaczane z blachy, producenci mebli, galanterii
papierowej itd. Przy masowej produkcji, zmniejszenie odpadu produkcyj-
nego choćby o kilka procent oznacza wielomilionowe oszczędności. Ten
problem, znany pod nazwą problemu optymalnego rozkroju (ang. optimal
cutting problem), jest – jak można się spodziewać – NP, ponieważ i tutaj
trzeba rozpatrzyć, przynajmniej teoretycznie, wszystkie możliwe ułożenia
wycinanych detali.
Innym ciekawym zadaniem jest problem kolorowania grafu (ang.
graph coloring problem). Mamy dowolny graf (tym razem nie robimy zało-
żenia, że musi być pełny), zawierający N węzłów i zbiór krawędzi między
nimi. Dostajemy do dyspozycji K kredek, o różnych kolorach. Zadanie pole-
ga na tym, żeby albo pokolorować węzły grafu w taki sposób, by żadne dwa
węzły połączone krawędzią nie były tego samego koloru, albo też stwier-
dzić, że w przypadku zadanego grafu jest to niewykonalne.
Jest rzeczą jasną, że aby to zadanie było w ogóle ciekawe, liczba ko-
lorów K musi być większa niż jeden (co oczywiste) i mniejsza niż liczba
węzłów N (bo gdy K ≥ N, to każdy węzeł można pomalować po prostu
innym kolorem – i po kłopocie). Jeżeli jeszcze K = 2 (dwa kolory), to liczba
operacji kolorowania zależy wielomianowo od liczby węzłów i rozwiązanie
zadania otrzymuje się bardzo szybko. Jeżeli natomiast 2 < K < N, to pro-
blem jest NP, a zadanie okazuje się wcale niełatwe. Kto nie wierzy, niech
narysuje dowolny graf o kilku, choćby o siedmiu czy ośmiu węzłach i spró-
buje pokolorować go w ten sposób trzema kolorami (lub w braku kredek
– opisać, stawiając przy węzłach cyferki 1, 2, 3).
Po co jednak mielibyśmy właściwie zabawiać się kredkami? Któż
– oprócz ewentualnie przedszkolaków – zajmuje się kolorowaniem grafów?
Czy znajomość złożoności obliczeniowej tego problemu jest w ogóle do
czegoś sensownego potrzebna?
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Plecak, układanki i różne zabawy z kredkami 59

Bardzo wymowny przykład jest dosłownie w zasięgu ręki. Proszę


popatrzeć na swój telefon komórkowy, dziś mają go praktycznie wszyscy.
Po to, byśmy mogli z niego korzystać, każdy operator telefonii komórko-
wej musiał zbudować ogromną teleinformatyczną infrastrukturę, o której
złożoności, koszcie i zasadach działania większość użytkowników nie ma
pojęcia. Wszyscy jednak wiedzą, że aparat, który trzymamy w ręku, poro-
zumiewa się z całą resztą systemu drogą radiową, komunikując się z jedną
z położonych w pobliżu stacji bazowych, zwanych w skrócie BTS (bazowa
stacja nadawczo-odbiorcza, ang. Base Transceiver Station). Stacje BTS są
instalowane na dachach wysokich budynków, na szczytach wzgórz, na spe-
cjalnie wybudowanych wieżach itd. Każdy z operatorów ma na terenie kraju
kilka tysięcy takich instalacji.
Każda stacja BTS pokrywa swoim zasięgiem pewien fragment terenu.
Jego wielkość i kształt zależy od mocy nadajnika, typu i sposobu ustawie-
nia anten, miejsca umieszczenia stacji w stosunku do przeszkód terenowych
itd. Jest przy tym bardzo ważne, że każdy punkt terenu powinien koniecznie
znajdować się w zasięgu nie jednej, lecz jednocześnie kilku, a może nawet
kilkunastu stacji BTS.
Jest to niezbędne z dwóch powodów. Po pierwsze, ma to być telefo-
nia mobilna, więc kiedy abonent porusza się – powinien płynnie, bez przerw
w łączności, przechodzić z zasięgu jednej stacji do drugiej. Bez częściowe-
go nakładania się zasięgów nie byłoby to możliwe. Po drugie, jedna stacja
BTS jest w stanie obsługiwać co najwyżej kilkudziesięciu abonentów naraz.
Dlatego (zwłaszcza tam, gdzie przebywa wielu ludzi: w centrach miast, na
dworcach i lotniskach, w centrach handlowych itd.) każdy potencjalny abo-
nent musi znajdować się w granicach zasięgu jednocześnie kilku lub kilku-
nastu okolicznych stacji. Jeśli w najbliższej stacji wszystkie kanały są zajęte
– nasz aparat zwraca się do innej, jeśli i ona jest zajęta – do następnej itd.
Z tych względów nakładanie się zasięgów sąsiadujących stacji bazowych
jest koniecznie i nieuchronne.
W momencie, kiedy stacja bazowa akceptuje zgłoszenie ze strony
naszego telefonu – wyznacza mu dwie częstotliwości: jedną do łączności
w kierunku od naszego aparatu do BTS, drugą – w kierunku przeciwnym2.

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

60 3. Trudne problemy, które wybuchają

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

Plecak, układanki i różne zabawy z kredkami 61

Warto też dodać, że przydziału częstotliwości nie dokonuje się raz


na zawsze. Sieć nieustannie się zmienia. W nowych osiedlach i przy no-
wych drogach powstają nowe stacje BTS, a w stacjach już istniejących apa-
ratura nadawcza jest okresowo modernizowana. Wpływa to oczywiście na
budowę grafu ukazującego nakładanie się zasięgów poszczególnych stacji.
Ponadto, operator rutynowo sprawdza jakość łączności na poszczególnych
częstotliwościach w różnych punktach kraju, co może powodować koniecz-
ność wprowadzenia zmian itd. Każda zmiana może wymagać przynajmniej
lokalnej modyfikacji przydziału częstotliwości w ramach pewnej grupy są-
siadujących stacji bazowych. Dlatego algorytmy przydziału częstotliwości
są wykorzystywane stale, w codziennej działalności każdego operatora sieci
telefonii komórkowej.
Dziwne i nieoczywiste zależności ujawnia jeszcze inny, dobrze znany
z historii matematyki problem kolorowania map (ang. map coloring pro-
blem). Sformułowano go w pierwszej połowie XIX wieku. Technika drukar-
ska osiągnęła wówczas taki poziom, że stało się możliwe drukowanie barw-
nych map i atlasów, w szczególności – map politycznych. Liczba kolorów
była jednak ograniczona, a im była większa – tym bardziej skomplikowana
stawała się konstrukcja maszyny drukarskiej.
Każda taka mapa jest płaskim obrazkiem, pokazującym obszary i gra-
nice państw (województw, powiatów itp.). Powiedzmy, że mamy do dyspo-
zycji mapę, zawierającą N państw, oraz K farb. Czy przy ich użyciu można
pokolorować obszary poszczególnych państw tak, by barwy nie zlewały się,
to znaczy tak, by żadne dwa państwa, mające wspólną granicę (dłuższą niż
jeden punkt, bo rożkami mogą się stykać) nie miały tego samego koloru?
Każdą mapę możemy w uproszczeniu uważać za płaski graf, gdzie
granice państw są krawędziami (niekoniecznie prostymi), zbiegającymi się
w punktach, które stanowią węzły grafu. Tym razem nie kolorujemy jednak
węzłów grafu, lecz pola ograniczone krawędziami.
Podobnie jak w poprzednim problemie, mogłoby się wydawać, że za-
danie to ma sens wtedy, gdy 1 < K < N, to znaczy wtedy, gdy liczba kolo-
rów jest większa niż jeden i mniejsza niż liczba państw. Jednak już w 1853
r. brytyjski matematyk Francis Guthrie ogłosił twierdzenie o czterech kolo-
rach (ang. four color theorem), według którego już cztery barwy całkowicie
wystarczają: każdą płaską mapę, niezależnie od liczby państw i przebiegu
granic między nimi, można pokolorować za pomocą nie więcej niż czterech
barw. Właściwie, było to jedynie spostrzeżenie czy matematyczna hipoteza
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

62 3. Trudne problemy, które wybuchają

(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

Pytanie za milion dolarów: czy P = NP? 63

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.

Pytanie za milion dolarów: czy P = NP?


Jednak najdziwniejsza, niewyjaśniona dotąd tajemnica dotyczy samej na-
tury problemów NP. Nie wiadomo bowiem – bagatela – czy one w ogóle
naprawdę istnieją. Nie ma bowiem (przynajmniej na razie) żadnego formal-
nego dowodu na to, że dla problemów NP nie mogą istnieć rozwiązania
wielomianowe. Przynajmniej teoretycznie wciąż jeszcze może się okazać,
że są to zwykłe, obliczeniowo „łatwe” zadania o wielomianowej złożoności,
tylko nikt do tej pory nie wpadł na odpowiednio pomysłowy sposób ich roz-
wiązywania. Dziś znamy wprawdzie dla nich algorytmy, które wykładniczo
eksplodują, ale może już jutro ktoś ogłosi rewelacyjny sposób na algorytm
pełnego rozwiązania problemu komiwojażera, który będzie miał złożoność
wielomianową?
Co więcej, mimo tej zasadniczej wątpliwości, o problemach NP-
-zupełnych (które są pewną ważną podklasą problemów NP) wiemy też,
że mają jeszcze jedną intrygującą właściwość. Wszystkie należące do niej
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

64 3. Trudne problemy, które wybuchają

problemy są mianowicie powiązane między sobą pewnymi szczególnymi


zależnościami (zwanymi redukcjami), których natury nie będziemy tu bliżej
wyjaśniać. W każdym razie, gdyby dla tylko jednego z tych problemów uda-
ło się znaleźć rozwiązanie o wielomianowej złożoności – to (właśnie dzięki
tym zależnościom) wszystkie problemy NP-zupełne uzyskałyby od razu po-
dobnie wielomianowe rozwiązanie. I odwrotnie: gdyby dla choćby jednego
problemu udało się udowodnić, że wielomianowe rozwiązanie z pewnością
nie może istnieć – to wniosek ten dotyczyłby od razu wszystkich problemów
NP-zupełnych3.
Może jest sprawą wyłącznie naszej niewiedzy czy nieumiejętności, że
nie potrafiliśmy dotąd znaleźć takich wielomianowych rozwiązań? A może,
przeciwnie, istnieją obiektywne powody, dla których nigdy nie uda się nam
znaleźć wielomianowych rozwiązań dla problemów NP i ich poszukiwanie
jest stratą czasu?
Sytuacja przypomina w pewnym sensie tę sprzed kilku stuleci, kiedy
to liczni uczeni i wynalazcy usiłowali skonstruować perpetuum mobile: ma-
szynę, która poruszałaby się sama z siebie, wiecznie, bez dopływu energii
z zewnątrz. Wielu zmarnowało całe lata życia na daremnych usiłowaniach,
aż wreszcie udało się wykazać, że w świetle poznanych w międzyczasie praw
fizyki zadanie to jest teoretycznie i praktycznie niewykonalne. Obecnie nikt
rozsądny już takich prób nie podejmuje, dzięki czemu fizycy czy mechanicy
mogą poświęcić swoją intelektualną energię, czas i zdolności innym, lepiej
rokującym problemom. Może więc i w naszej dziedzinie uda się udowodnić,
że z pewnych, nieznanych dziś obiektywnych powodów, niedeterminizmu
świata nie da się opanować za pomocą algorytmów o wielomianowej złożo-
ności, a ich poszukiwania są daremne?
Te wątpliwości są uważane przez wielu za najważniejszy, najbardziej
palący teoretyczny problem współczesnej matematyki. Matematycy nazy-
wają go skrótowo pytaniem, czy P = NP? Rozumują bowiem tak: każde za-
danie, dla którego znamy algorytm o złożoności wielomianowej, może być
uważane za szczególny przypadek problemu NP. Istotnie, każdy algorytm
dla problemu NP, przygotowany na to, by „obsłużyć” wszystkie N!, 2N czy

3 Ciekawe, że przytoczony poprzednio problem orzekania o izomorfizmie dwóch


grafów (bez zadanej z góry odpowiedniości między węzłami) ma niejasny sta-
tus. Na pewno jest on NP, ale nikt do dziś nie potrafi powiedzieć, czy jest NP-
-zupełny.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Pytanie za milion dolarów: czy P = NP? 65

kN wielomianowych obliczeń – doskonale da sobie radę także z pojedyn-


czym obliczeniem wielomianowym. Dlatego, jeśli przez P oznaczymy zbiór
wszystkich problemów o złożoności wielomianowej, a przez NP – zbiór
wszystkich problemów NP, to P zawiera się w NP, co możemy zapisać jako
P ⊂ NP.
Z drugiej strony, gdyby okazało się, że dla wszystkich problemów NP
jednak istnieją rozwiązania o wielomianowej złożoności, znaczyłoby to, że
NP ⊆ P, więc wszystkie problemy NP należą do klasy problemów o wie-
lomianowej złożoności. Gdyby tak istotnie było, to moglibyśmy napisać,
że P = NP, ponieważ tylko w takim przypadku obie te relacje zawierania
(P ⊂ NP oraz NP ⊆ P) są równocześnie spełnione.
Czy więc P = NP, czy też P ≠ NP?
W Stanach Zjednoczonych działa zamożna prywatna instytucja, Clay
Mathematics Institute, stawiająca sobie za cel wspieranie badań w dziedzinie
matematyki. Gdy nadchodził koniec drugiego tysiąclecia naszej ery, Instytut
ten ogłosił milenijną listę siedmiu najważniejszych, nierozwiązanych dotąd
problemów współczesnej matematyki (Millenium Prize Problems) i ufundo-
wał nagrody w wysokości miliona dolarów za rozwiązanie każdego z nich.
Problem P = NP znajduje się na pierwszym miejscu listy, co stanowi pewne
potwierdzenie tego, jak wielką wagę mu się przypisuje.
Mijają kolejne lata, a nagroda wciąż czeka na odkrywcę.
Jak wynika z przeprowadzonej na ten temat ankiety, większość li-
czących się w tej dziedzinie matematyków uważa, że jednak P ≠ NP, choć
wciąż nie ma na to formalnego dowodu. Ich zdaniem nie uda się znaleźć
wielomianowych rozwiązań dla wszystkich problemów NP. Istnienie pro-
blemów NP pozostałoby w takim przypadku obiektywnym faktem, a nie
jedynie wynikiem naszej niewiedzy czy nieumiejętności.
Inni – choć jest ich mniejszość – sądzą, że jeszcze za wcześnie na
ostateczne konkluzje. Przede wszystkim – powiadają – nasza wiedza o al-
gorytmach (podobnie jak sama teoria złożoności obliczeniowej) jest jeszcze
stosunkowo młoda i w przyszłości nie można wykluczyć znacznego postę-
pu, a nawet gruntownego przełomu w tej dziedzinie. Historia matematyki
zna przecież przykłady problemów, które czekały na formalne rozwiązanie
ponad sto lat (jak np. dowód twierdzenia o czterech kolorach) czy nawet kil-
kaset lat (jak sławne twierdzenie Fermata). Może więc dopiero nasze dzie-
ci lub wnuki doczekają decydującego rozstrzygnięcia w tej sprawie? Może
wtedy okaże się właśnie, że jednak P = NP?
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

66 3. Trudne problemy, które wybuchają

Tak czy inaczej, obie wzajemnie sprzeczne możliwości pozostają na


razie nierozstrzygnięte, a problem P = NP jest otwarty4.
A może sława i milionowa nagroda za jego rozwiązanie przypadnie
w udziale właśnie Czytelnikowi lub Czytelniczce niniejszej książki?

4 Warto dodać, że jeden z milenijnych problemów (dotyczący dowodu hipotezy


Poincarégo) został stosunkowo niedawno rozwiązany. Dokonał tego rosyjski ma-
tematyk Grigorij Jakowlewicz Perelman. To bardzo dziwna i intrygująca postać,
o której właściwie niewiele wiadomo. Zrezygnował z pracy w renomowanym
moskiewskim instytucie i od kilku lat mieszka wraz z matką w przygnębiającym
sanktpetersburskim blokowisku. Nie wiadomo, z czego żyje. Unika kontaktów
ze światem. Niewiele publikuje, głównie w Internecie, ale jego rezultaty budzą
podziw światowego środowiska matematyków. W 2006 r. przyznano mu Medal
Fieldsa, najbardziej prestiżową nagrodę, o jakiej może marzyć matematyk – ale
odmówił jej przyjęcia. W roku 2010 otrzymał milion dolarów za milenijny do-
wód hipotezy Poincarégo. Jednak i tym razem ani zaszczyt, ani niemałe pienią-
dze nie wzbudziły zainteresowania Perelmana, który w ogóle nie pofatygował
się, by tę nagrodę odebrać.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

4. O metodach konstruowania
algorytmów

Skąd się biorą algorytmy?


Wiemy już, że nie ma algorytmu tworzenia algorytmów. Już w pierwszym
rozdziale zadaliśmy pytanie: skąd się one w takim razie biorą?
Odpowiedź jest wymijająca: lęgną się one gdzieś w głębinach naszego
umysłu i stamtąd wydostają się na zewnątrz. Nie wiadomo dokładnie jak, ale
też nic nie wskazuje na to, by działo się to zgodnie z jakimś algorytmem.
Potem, już bardziej świadomie, są udoskonalane, poprawiane, cyzelowane
tak długo, aż twórca uzna, że to, co zrobił, można już pokazać publiczności.
Umiejętność algorytmizacji problemów jest więc właściwością ludz-
kiego umysłu, równie fascynującą i niepojętą, jak nagła potrzeba nasmaro-
wania konturu mamuta na ścianie jaskini, zdolność skomponowania symfo-
nii lub piosenki, napisania wiersza czy udowodnienia twierdzenia matema-
tycznego. To proces twórczy, wykorzystujący (w nie całkiem uświadomiony
sposób) poprzednio poznane zjawiska i przykłady, różnice i analogie mię-
dzy nimi, a także analogie między analogiami, analogie między analogiami
analogii itd. Zaczyna się od ciekawości i chęci zrobienia czegoś, czego nikt
do tej pory nie zrobił, a wiedzie przez próby i błędy, przez okresy zniechę-
cenia i natchnienia, bardzo osobiste emocje i nagłe olśnienia, które potrafią
wyrwać ze snu w środku nocy.
Matematycy czy informatycy – w odróżnieniu od przynajmniej nie-
których poetów i pisarzy – nie lubią jednak rozczulać się nad meandrami
swoich procesów twórczych. Przeciwnie, zwykli oni na ogół przedstawiać
produkt wielu tygodni czy miesięcy rozmyślań, prób i błędów w postaci
programu lub suchej publikacji opisującej końcowy rezultat tak, jak gdyby
był on od razu zaplanowany i od początku oczywisty. Skutek jest taki, że
wielu laikom wydaje się, iż tworzenie jest wyłącznym przywilejem artystów
(cokolwiek to słowo znaczy), zaś zawód matematyka, informatyka, inżynie-
ra czy naukowca ze „ścisłej” dziedziny jest zajęciem pozbawionym emocji,
beznamiętnym i nudnym. Ale w istocie tworzenie algorytmów, programo-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

68 4. O metodach konstruowania algorytmów

wanie, formułowanie matematycznych hipotez czy dowodzenie twierdzeń


jest sztuką, nawet jeśli przedstawiciele zawodów uważanych tradycyjnie za
twórcze przyjmą taki pogląd z niedowierzaniem.
Oczywiście, nie każde samodzielne działanie zasługuje na miano
twórczego. Ktoś, kto pisze instrukcję obsługi korkociągu, niekoniecznie sta-
je się od razu pisarzem; podobnie ktoś, kto projektuje układ formularza,
posługując się arkuszem kalkulacyjnym, nie powinien się uważać za infor-
matyka. Nawet wśród informatyków, którzy z tego zawodu uczynili sobie
sposób na życie, są tacy, którzy nie projektują algorytmów całkiem samo-
dzielnie. Składają oni często swoje programy w zasadzie z gotowych części,
pochodzących z bibliotek oprogramowania, które są dostarczane przez fir-
my software’owe. Wybierają odpowiednie, gotowe programy z odpowied-
niego menu przez kliknięcie myszą, co najwyżej dorabiając potem pewne
fragmenty kodu, które sklejają wszystko w całość.
Mając dobry pomysł na grę czy gadżet multimedialny, można w ten
sposób szybko zbudować nawet złożony produkt, który ma szansę odnieść
sukces na rynku. Nic to, że otrzymane w ten sposób oprogramowanie ma nie-
przejrzystą strukturę: wystarczy nie ujawniać żenująco połatanego kodu źró-
dłowego, a sprzedawać kod wynikowy (i tak nieczytelny) w postaci gotowej
do instalacji. Nic to, że program potrzebuje ogromnej pamięci i działa bardzo
wolno, ponieważ owe gotowe algorytmy wykonują mnóstwo czynności nie-
potrzebnych w danej, konkretnej sytuacji i wzajemnie dublują swoje działa-
nia. Zapewne gdyby taki program zoptymalizować – mógłby działać sto razy
szybciej i używać pamięci tysiąc razy mniejszej (i nie jest to przesadą). Ale
to się nikomu nie opłaca. Producenci coraz większych pamięci i coraz szyb-
szych procesorów tylko zacierają ręce. Dopóki użytkownicy będą się godzili
na to, że muszą co dwa lata wymieniać swój sprzęt na nowy, szybszy, o więk-
szej pamięci – nie będzie (przynajmniej na masowym rynku) motywacji do
optymalizacji oprogramowania.
Czy to dobrze, czy źle? To znak czasów. Informatyka nie jest tu wy-
jątkiem. Większość wszelkich rynkowych produktów składa się dziś ze
znormalizowanych części. Typowy „fachowiec” w stacji obsługi samocho-
dów też nie zawsze sprawia wrażenie, jak gdyby naprawdę znał się na budo-
wie nowoczesnego silnika. Imponuje natomiast wiedzą na temat zawartości
firmowego magazynu części i podzespołów. Zamiast naprawiać – zapropo-
nuje, by najlepiej od razu wymienić pół silnika na nowy. Staroświecka inży-
nierska etyka zawodowa ery industrialnej, zakładająca dążenie do prostoty,
pomysłowości, optymalności i oszczędności – została wyparta przez specy-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Skąd się biorą algorytmy? 69

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

70 4. O metodach konstruowania algorytmów

Do niezbędnego zasobu wiedzy informatyka należą także podstawowe


algorytmy operowania tymi strukturami danych: dodawania i usuwania ele-
mentów (np. wierszy lub kolumn w tablicy, elementu na początku lub końcu
listy, nowego węzła lub krawędzi w grafie), przeszukiwania, znajdowania
elementów największych czy najmniejszych, porządkowania według ustalo-
nego kryterium, łączenia kilku struktur w jedną (np. dwóch grafów w jeden,
scalania dwóch uporządkowanych list w jedną) i tak dalej. Z tymi elemen-
tarnymi czynnościami każdy informatyk ma stale do czynienia, powinien je
więc znać, aby nie wymyślać ich za każdym razem od nowa.
Ale – wracając do analogii z komponowaniem piosenek – same umie-
jętności warsztatowe nie wystarczają. Każdy ambitniejszy muzyk musi być
także osłuchany z gatunkiem muzycznym, w ramach którego chce działać.
Powinien poznać, pamiętać i potrafić zanalizować setki utworów, po to,
by wiedzieć, jak są zbudowane, nauczyć się wyczuwać, co jest w nich do-
bre, a co złe – i ewentualnie dlaczego. Także poeta, pisarz czy dramaturg
oprócz tego, że powinien dużo wiedzieć o warsztacie literackim, powinien
być oczytany w literaturze. Choć nie gwarantuje to artystycznego sukcesu,
to przynajmniej pozwala ustrzec się odkrywania rzeczy dawno odkrytych,
popełniania nieświadomych plagiatów i kompromitujących błędów.
Informatyk powinien również studiować i znać dorobek zarówno
poprzedników, jak współczesnych mu kolegów w dziedzinie algorytmiki.
Istnieje bogata literatura na ten temat, a co pewien czas ukazują się bar-
dzo obszerne monografie, liczące czasem nawet grubo ponad tysiąc stron.
Jedne z nich zakładają posługiwanie się pewnym wybranym językiem pro-
gramowania i poświęcają więcej miejsca technice użycia tego języka, inne
– koncentrują się na prezentacji i analizie samych algorytmów oraz na ich
podstawach teoretycznych. Jednak nawet kompetentne monografie unika-
ją na ogół formułowania bezpośrednich wskazówek czy instrukcji, które
sugerowałyby sposób postępowania przy rozwiązywaniu konkretnego pro-
blemu.
Oferują czytelnikowi raczej bogaty zestaw setek krytycznie zanalizo-
wanych przykładów podejścia do problemów z różnych dziedzin: od opera-
cji sortowania do działań na grafach, przetwarzania sygnałów, kryptografii,
numerycznego rozwiązywania układów równań liniowych, optymalizacji
itp., przy założeniu, że użytkownik sam znajdzie wśród nich odpowiadające
mu rozwiązanie, użyteczną analogię lub przynajmniej inspirację do samo-
dzielnego poszukiwania własnych rozwiązań.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Metoda „dziel i zwyciężaj” 71

Niżej omówimy pokrótce kilka z najbardziej ogólnych wskazó-


wek metodologicznych: zasadę „dziel i zwyciężaj”, algorytmy zachłanne,
rekurencyjne, heurystyczne. W następnym rozdziale powiemy nieco o al-
gorytmach probabilistycznych i ewolucyjnych. Ze względu na charakter
niniejszej książki skoncentrujemy się na omówieniu jedynie tych kilku wy-
branych, podstawowych pojęć oraz związanej z nimi intuicji, w nadziei że
ułatwi to przynajmniej niektórym zainteresowanym czytelnikom poważniej-
sze studiowanie tej dziedziny.
Warto podkreślić, że wspomniane terminy nie wykluczają się wza-
jemnie ani nie wyczerpują wszystkich możliwości. Algorytmy oparte na za-
sadzie „dziel i zwyciężaj” są najczęściej rekurencyjne, istnieją heurystyczne
algorytmy probabilistyczne, algorytmy zachłanne mogą być jednocześnie
rekurencyjne i tak dalej. Ponadto, w literaturze można napotkać inne sposo-
by klasyfikacji algorytmów: np. podział na bezpośrednie i transformacyjne,
dokładne i przybliżone (aproksymacyjne).

Metoda „dziel i zwyciężaj”


Stara rzymska maksyma divide et impera, znana w Polsce jako „dziel
i rządź”, dobrze oddawała polityczną regułę, którą posługiwali się
Rzymianie, zdobywając dla swego cesarstwa kolejne ziemie. Aby droga do
podboju stanęła otworem – należało podzielić wrogie plemiona i zwyciężać
je po kolei.
Tę zasadę, w krajach anglojęzycznych tłumaczoną jako divide and
conquer (dziel i podbijaj), można zastosować również w odniesieniu do
rozwiązywania problemów, w szczególności algorytmicznych. W polskich
przekładach literatury z tej dziedziny przyjęto divide and conquer tłumaczyć
jako „dziel i zwyciężaj”. Ale chodzi o to samo, co Rzymianom: o podziele-
nie dużego zadania na mniejsze części i rozwiązywania ich stopniowo, po
kolei. Kiedy już wszystkie są rozwiązane – otrzymane w ten sposób czę-
ściowe wyniki łączy się, uzyskując rozwiązanie całego problemu.
Wskazówka jest pożyteczna, choć ogólnikowa. To, jak dany problem
podzielić na podproblemy, jak te podproblemy rozwiązywać i jak wreszcie
scalić ich wyniki w ostateczne rozwiązanie trzeba zdecydować w każdym
konkretnym przypadku. Warto jednak zdawać sobie przynajmniej sprawę,
że można przy tym zastosować drogę zstępującą (ang. top down, z góry do
dołu) lub wstępującą (ang. bottom up, od dołu do góry), jak schematycznie
pokazano na rysunku 4.1.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

72 4. O metodach konstruowania algorytmów

Rys. 4.1. Strategia zstępująca i wstępująca

Z postępowaniem drogą bottom up mamy do czynienia wtedy, gdy najpierw


rozwiązujemy poszczególne podproblemy, a potem ich wyniki – zazwyczaj
w pewnej zagregowanej postaci – wykorzystujemy do rozwiązania całego
problemu.
To dość naturalna i mało wyrafinowana zasada. Powiedzmy dla przy-
kładu, że zapisujemy sobie ważniejsze rodzinne wydatki, żeby mieć nad nimi
kontrolę. Chcielibyśmy wiedzieć, ile wydajemy w ciągu roku na – powiedzmy
– ubrania, a co więcej – ile w lecie, a ile w zimie. Naturalnym pomysłem jest,
by sumować stopniowo takie wydatki w poszczególnych miesiącach: styczniu,
lutym, marcu… i tak dalej. Suma z danego miesiąca jest pewną wielkością
zbiorczą, zagregowaną, którą wykorzystamy później, a szczegóły zakupów
w danym miesiącu można już wyrzucić (chyba że będą potrzebne też do cze-
goś innego).
Mając takie zagregowane miesięczne dane, łatwo potem policzyć, ile się
wydało w miesiącach letnich, ile w zimowych, a ile w całym roku. Można się
też posuwać dalej w górę: mając podobne zapisy z kilku lat, można zorientować
się, czy nasze roczne wydatki na ubrania rosną, czy maleją w miarę dorastania
dzieci, bez konieczności przechowywania przez kilka lat szczegółowego wyka-
zu wszystkich zakupionych rzeczy.
Strategia top down – z góry do dołu – jest mniej oczywista, ale często
jest bardzo skuteczna. Dla jej ilustracji wróćmy raz jeszcze do problemu
komiwojażera.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy zachłanne 73

Wyobraźmy sobie, że pewna firma ma w Polsce 20 sklepów i musi co


tydzień dostarczyć do nich towar z magazynu, który mieści się w Warszawie.
Gdyby kierowca firmowej ciężarówki (któremu zlecono to zadanie) przeczy-
tał tę książkę – załamałby się nerwowo, bo dowiedział się z niej przed chwilą,
że czeka go kilkadziesiąt tysięcy lat zastanawiania się nad wyborem trasy.
Ale powiedzmy, że kierowca nie jest tego świadom, natomiast
wie, że z tych dwudziestu sklepów 7 jest w Warszawie, 6 w Trójmieście,
4 w Poznaniu i jego okolicach oraz 3 na Podkarpaciu, koło Rzeszowa.
Najpierw powinien więc rozwiązać generalny problem: jak możliwie naj-
krótszą trasą, wyjeżdżając z Warszawy, objechać trzy miejsca: okolice
Gdańska, Poznania i Rzeszowa, a potem wrócić do Warszawy. My już wie-
my, jak to zrobić, a nawet taką optymalną trasę wyznaczyliśmy, korzystając
z faktu, że dla N = 3 z problemem komiwojażera można się łatwo uporać.
Najwyższy poziom zadania mamy więc rozwiązany.
Kiedy już to wiemy, schodzimy w dół, na poziom podproblemów.
W Warszawie powinniśmy znaleźć możliwie najkrótszą drogę między
7 warszawskimi sklepami (i niekoniecznie musi być to zamknięty cykl
Hamiltona), dojechawszy do Gdańska – wyznaczyć możliwie najkrótszą
drogę między tamtejszymi sześcioma sklepami i tak dalej.
Nawet, gdybyśmy przy rozwiązywaniu tych częściowych proble-
mów stosowali naiwne podejście, polegające na prostodusznym wyliczaniu
wszystkich możliwości – łączny nakład obliczeń potrzebny do takiego roz-
wiązania zadania będzie z pewnością nieporównanie mniejszy niż w przy-
padku podobnie naiwnego rozwiązywania problemu komiwojażera wprost,
od razu dla N = 20.
Oczywiście, w tym przypadku sklepy były skupione w kilku wyraź-
nych centrach. Przy innym rozłożeniu miejsc do odwiedzenia sposób wy-
różnienia obszarów, dla których formułujemy zadanie najwyższego pozio-
mu, nie musi być tak oczywiste. Niemniej, zstępująca zasada dekompozycji
zadania na prostsze jest często stosowana, i to nie tylko w odniesieniu do
problemu komiwojażera.

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

74 4. O metodach konstruowania algorytmów

Dla rozwiązania tego problemu skonstruowaliśmy drzewo decyzyjne


(rysunek 3.2), a następnie systematycznie obliczyliśmy długość wszystkich
ścieżek od korzenia do poszczególnych liści, po to, by wybrać najkrótszą
z nich. To najprostsze, naiwne rozumowanie prowadzi oczywiście do ko-
nieczności sprawdzenia N! wariantów trasy.
Zachłanny komiwojażer postąpiłby inaczej. Pomyślałby tak: wyrusza-
jąc z Warszawy, mogę pojechać najpierw do Gdańska (344 km) albo do
Poznania (319 km), albo do Rzeszowa (296 km). Ponieważ jestem zachłan-
ny, wybiorę to, co da mi najszybciej korzyść. Lepszy wróbel w ręku niż cie-
trzew na sęku. Wybiorę z tych trzech możliwości tę, która wydaje się w tej
chwili najkorzystniejsza. Co dalej – będę się martwił później. Pojadę więc
najpierw do Rzeszowa, bo tam jest najbliżej. Kiedy tam dotrę – wybiorę
znów najkrótszą z dalszych możliwych dróg itd. Tak postępując, krok po
kroku, wrócę w końcu do Warszawy.
Zachłanny algorytm podejmuje więc decyzje, które są optymalne
lokalnie, w danym kroku postępowania, choć nie gwarantuje to globalnej
optymalności. Może trzeba było w danym kroku wybrać decyzję nieco
gorszą, bo za chwilę by się to lepiej opłaciło? Algorytm zachłanny takich
wątpliwości nie ma. Niemniej, w wielu przypadkach postępowanie zachłan-
ne może dać nawet sensowny rezultat. Łatwo policzyć, że w naszym przy-
kładzie cykl otrzymany metodą zachłanną (Warszawa – Rzeszów – Poznań
– Gdańsk– Warszawa) jest dokładnie taki sam, jak najlepszy, otrzymany
z pełnego rozwiązania. Nie ma na to jednak żadnej gwarancji.
Można zauważyć także inną ciekawą właściwość. W przypadku al-
gorytmu pełnego, choć obliczenia rozpoczęliśmy, startując od Warszawy, to
kiedy już wyliczyliśmy najkrótszy cykl – punkt początkowy przestaje mieć
znaczenie. Cykl jest cyklem, a więc – podobnie jak okrąg – nie ma począt-
ku ani końca. Komiwojażer może rozpocząć podróż w dowolnym z miast,
na przykład w Rzeszowie, a optymalna, najkrótsza trasa będzie taka sama
jak wówczas, gdyby wyruszył z Warszawy i posuwał się wzdłuż wyliczo-
nego najkrótszego cyklu, drogą „Rzeszów – Poznań – Gdańsk – Warszawa
– Rzeszów” albo odwrotnie: „Rzeszów – Warszawa – Gdańsk – Poznań
– Rzeszów”. Algorytm zachłanny tej właściwości nie ma: może dawać róż-
ne rozwiązania w zależności od tego, od jakiego punktu rozpoczęliśmy ob-
liczenia. Na przykład, jeśli zaczniemy od Poznania, to algorytm zachłanny
da jako wynik trasę „Poznań – Warszawa – Rzeszów – Gdańsk – Poznań”,
o wcale nieoptymalnej długości 1642 km.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy heurystyczne 75

Zaletą podejścia zachłannego jest za to wielomianowa złożoność ob-


liczeniowa algorytmu, a zatem – w praktyce – krótki czas potrzebny do
uzyskania wyniku. Istotnie, w pierwszym kroku algorytm dokonuje N po-
równań po to, by wybrać najkrótszy z pierwszych odcinków trasy. Na nim
się koncentruje, a pozostałe możliwości odrzuca. W drugim kroku – porów-
nuje (N – 1) potencjalnych drugich odcinków i znów zachłannie wybiera
najkrótszy z nich. W trzecim potrzebuje do tego (N – 2) porównań i tak
dalej. W rezultacie, liczba operacji porównywania niezbędna do wyznacze-
nia całej drogi wynosi:

N + (N – 1) + (N – 2) + ... + 3 + 2 + 1.

Kto się chwilę zastanowi – łatwo zrozumie, że złożoność obliczeniowa jest


w tym przypadku O(N 2). Poprzednio, dla pełnego, naiwnego rozwiązania,
liczba dróg do wyliczenia i porównania wynosiła:

N · (N – 1) · (N – 2) · ... · 3 · 2 · 1 = N!

Niby trochę podobnie, a różnica kolosalna: operacje mnożenia zastąpiono


(dla algorytmu zachłannego) operacjami dodawania. Stąd jakościowa zmia-
na złożoności obliczeniowej z wykładniczej na wielomianową.
Jak widać, korzyść z zachłannego wyboru polega na tym, że decydu-
jąc się na jeden, lokalnie optymalny odcinek drogi – odrzucamy całe pod-
drzewa innych możliwości i nigdy już nie wracamy do ich analizowania.
Wada tego podejścia polega dokładnie na tym samym: globalnie optymalne
rozwiązanie może kryć się właśnie w owych odrzuconych poddrzewach de-
cyzyjnych.

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

76 4. O metodach konstruowania algorytmów

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

78 4. O metodach konstruowania algorytmów

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

Rys. 4.2. Sieć działań algorytmu scalania

się, skąd się one wzięły i kto je uporządkował. Oczekujemy, że w wyniku


scalania powstanie nowa lista LC, na której znajdą się (również w niemale-
jącym porządku) wszystkie elementy przepisane z obu list.
Algorytm wykorzystuje trzy zmienne: a, b i c, które wskazują bieżący
numer kolejnych pozycji na listach (odpowiednio) LA, LB, LC. Konwencję
odwoływania się do poszczególnych pozycji na tych listach znamy już
z rozdziału 2. Jeśli np. zmienna a będzie miała wartość 1 i w tym momencie
zapytamy o zawartość LA(a), to znaczy, że pytamy o LA(1), czyli pierwszą
pozycję na liście LA itd. Ponadto, jak poprzednio, znak „#” oznacza koniec
listy. W dwóch operacjach badania warunku znak „&” oznacza iloczyn lo-
giczny (koniunkcję) warunków: na przykład warunek (L(a) = #) & (L(b) = #)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

80 4. O metodach konstruowania algorytmów

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ą:

Definicja funkcji LC := scal(LA, LB);


Pod spodem opiszmy występujące w algorytmie scalania argumenty
i zmienne:
– argumenty wejściowe:
• LA, LB – uporządkowane niemalejąco listy liczb całkowitych;
– wynik:
• LC – uporządkowana niemalejąco lista liczb całkowitych;
– zmienne lokalne:
• a, b, c – liczby całkowite.
Dalej umieśćmy tzw. ciało tej funkcji (ang. function body), czyli szcze-
gółowy, krok po kroku, opis algorytmu scalania, sporządzony według sieci
działań z rysunku 4.2.
Dla porządku, zakończmy definicję wyraźnym napisem:

Koniec definicji funkcji LC := scal(LA, LB);


Tę kartkę odłóżmy do podręcznej teczki z napisem „Biblioteka funk-
cji”. Będą się tam gromadziły inne, podobne opisy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy rekurencyjne 81

Od tej chwili wolno nam w jakimś innym algorytmie, wśród innych


instrukcji, napisać np.:
– ...
– X := scal(Y, Z);
– ...
Ktoś, kto będzie ten algorytm wykonywał, zorientuje się, że jest to wy-
wołanie funkcji nazywającej się scal. Sięgnie wówczas do biblioteki, odszuka
kartkę z definicją funkcji scal i znajdzie tam wszelkie informacje potrzebne
dla dalszego postępowania.
Przede wszystkim zobaczy, że w definicji funkcji LC := scal(LA, LB)
argumentami operacji są – formalnie rzecz biorąc – LC, LA oraz LB. Pisząc
X := scal(Y, Z), chcemy jednak, by tym razem algorytm wykonał się przy
założeniu, że LC – to X, LA – to Y, LB – to Z. Argumenty, zapisane w de-
finicji funkcji – to argumenty formalne, natomiast te, które są wymienione
w wywołaniu funkcji – to argumenty aktualne. Argumenty aktualne mogą
być w każdym wywołaniu funkcji inne.
Weźmy więc nową kartkę, którą nazwiemy roboczo, powiedzmy, „kart-
ką wykonania funkcji”. Umieścimy tam przede wszystkim opis obowiązują-
cego w tej chwili przyporządkowania argumentów aktualnych – argumentom
formalnym, na przykład taki:
– X w miejsce LC,
– Y w miejsce LA,
– Z w miejsce LB.
Niżej będziemy notowali bieżące, kolejne wartości zmiennych a, b
i c, zawartość powstającej stopniowo listy LC (czyli X) oraz numer kolejnej
instrukcji z algorytmu scalania. Teraz możemy już przystąpić do wykonania
algorytmu, stanowiącego ciało funkcji.
Gdy dotrzemy do końca algorytmu, będziemy mieli w rezultacie za-
wartość nowej, scalonej listy X. Z listą X możemy wrócić do programu,
który wywoływał funkcję scal i kontynuować jego wykonanie. Mówi się, że
funkcja X := scal(Y, Z) zwraca listę X. „Kartkę wykonania funkcji” możemy
natomiast zmiąć i wyrzucić do śmieci: przestała być już potrzebna.
Tak więc funkcję wykonuje się jak gdyby na boku, na oddzielnej
kartce, po czym powraca się (wraz ze zwróconym wynikiem) do głównego
algorytmu, który tę funkcję wywołał. Ma to bardzo ważną i godną zapamię-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

82 4. O metodach konstruowania algorytmów

tania konsekwencję: zmienne, używane w algorytmie scalania, występują


jedynie na owej ubocznej karcie i nie mylą się ze zmiennymi algorytmu
głównego.
W algorytmie scalania występują na przykład zmienne a, b i c, pełnią-
ce tu funkcję bieżących indeksów, numerujących poszczególne pozycje na
trzech listach. Są to zmienne lokalne funkcji scal. Tymczasem w algorytmie
(programie), który wywoływał funkcję scal, też mogą występować zmienne
o nazwach a, b, c, ale służące jakimś zupełnie innym celom. Zasada wyko-
nywania funkcji „na boku” sprawia, że nie ma tu żadnego nieporozumienia.
Zmiana wartości lokalnej zmiennej np. a na pomocniczej karcie wykonania
nie powoduje zmiany wartości zmiennej a z programu głównego: to są po
prostu dwie różne zmienne. Lokalne zmienne giną wraz z zakończeniem
wykonania funkcji. Zresztą, najczęściej (choćby wtedy, gdy korzystamy
z funkcji zdefiniowanej wcześniej przez kogoś innego) nawet nie musimy
wiedzieć, jakich zmiennych lokalnych używa się w danej funkcji. Dzięki
omówionej zasadzie lokalności zmiennych funkcji – nie musimy się o nie
martwić. Wystarczy, że wiemy, jakie argumenty wejściowe powinniśmy do-
starczyć (i w jakiej kolejności) i jaki wynik zwróci nam wywołana funkcja.
Po tych wyjaśnieniach mechanizm rekurencyjnego sortowania ze sca-
laniem nie powinien sprawić większych trudności. Operację tę zdefiniuje-
my również jako funkcję y := sortuj(x), postępując dokładnie według wyżej
opisanych zasad, tak jak to robiliśmy dla funkcji scal:

Definicja funkcji y := sortuj(x);


Formalne argumenty funkcji:
– x – wejściowa lista liczb całkowitych.
Wynik:
– y – uporządkowana niemalejąco lista x.
Zmienne lokalne:
– L1, L2, L3, L4 – listy liczb całkowitych,
– d – liczba naturalna.
Ciało funkcji:
1. Początek
2. Oblicz d = długość listy x;
3. Jeśli d < 2, to y := x; Koniec;
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy rekurencyjne 83

4. Podziel listę x na dwie (najlepiej możliwie równe) części: L1 oraz L2;


5. L3 := sortuj(L1);
6. L4 := sortuj(L2);
7. y := scal(L3, L4);
8. Koniec

Koniec definicji funkcji y := sortuj(x);


Ciało funkcji sortuj stanowi – jak widać – rekurencyjny algorytm,
który daje się zapisać w zaledwie ośmiu wierszach pseudokodu1, a sprawnie
uporządkuje listę o dowolnej długości. W wierszach 5 i 6 funkcja sortuj
istotnie wywołuje samą siebie. My jednak już znamy cały sekret rekurencji:
algorytm jest niby ten sam, ale wykonuje się – za każdym wywołaniem – dla
innych argumentów aktualnych i posługuje się innymi zmiennymi lokalnymi.
Żeby zobaczyć, jak to działa w praktyce, wykonajmy ten algorytm
ręcznie. Załóżmy dla przykładu, że chcemy przy jego użyciu posortować
listę L o następującej zawartości:

L = 23; 4; 33; 2; #

chcąc jednocześnie, by wynik nazywał się nowaL. Wywołujemy więc funk-


cję sortowania, pisząc instrukcję:

nowaL := sortuj(L);

Uruchamiamy w ten sposób proces, zilustrowany schematycznie na rysun-


ku 4.3.
Zgodnie z omówioną wyżej zasadą, bierzemy z biblioteki kartę z de-
finicją funkcji sortuj i zakładamy oddzielną kartkę wykonania funkcji. Tym
razem różnych wykonań będzie więcej, niech więc nazywa się ona Kartka
1. Na niej ustalamy przede wszystkim argumenty aktualne:
– L w miejsce x,
– nowaL w miejsce y.
Przygotowujemy też miejsce na lokalne zmienne: d, L1, L2, L3 oraz

1 Pseudokodem nazywamy taki niby-program, w którym obok skrótowych wyra-


żeń charakterystycznych dla formalnego języka programowania występują jesz-
cze – dla wygody wyjaśniania – nieformalne objaśnienia w „ludzkim” języku.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

84 4. O metodach konstruowania algorytmów

Rys. 4.3. Wykonanie funkcji sortuj


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy rekurencyjne 85

L4 (wiemy o nich z definicji funkcji) i przystępujemy do wykonywania ko-


lejnych instrukcji z ciała funkcji.
W instrukcji z wiersza 2 obliczamy długość listy L. W tym przypadku
d = 4. Instrukcje z wiersza 3 wykonują się jedynie warunkowo, pod wa-
runkiem że d < 2. Ponieważ teraz tak nie jest, ignorujemy wiersz 3 i prze-
chodzimy do wiersza 4. Zgodnie z jego treścią dzielimy listę L na dwie
części, najlepiej – o równych lub zbliżonych długościach. Zapisujemy więc
na Kartce 1:
– L1 = 23; 4; #,
– L2 = 33; 2; #
i przechodzimy do wiersza 5.
Tu funkcja wywołuje samą siebie. Jest to jednak nowe wywołanie,
a więc z innymi argumentami aktualnymi. Musimy postąpić dokładnie tak,
jak poprzednio: wziąć nową kartkę (Kartka 2), napisać na niej nowe przy-
porządkowanie parametrów formalnych i aktualnych:
– L1 zamiast x,
– L3 zamiast y.
przygotować miejsce na zmienne lokalne, a potem przystąpić do wykony-
wania ciała funkcji znów od początku, dla nowych argumentów aktualnych
i nowych zmiennych lokalnych.
Co jednak zrobić z Kartką 1? Wrócimy do niej później, dopiero wte-
dy, gdy nowo wywołana funkcja zwróci wyliczoną (na Kartce 2) wartość
L3. Zaznaczmy na Kartce 1, od jakiej instrukcji powinniśmy po powrocie
na nią kontynuować wykonanie algorytmu. Tą następną instrukcją powin-
na być oczywiście instrukcja 6. Odłóżmy Kartkę 1 na razie na lewą stronę
stołu.
Na Kartce 2 znów obliczamy długość d listy x, ale tym razem x – jest
to L1, a pamiętajmy, że również samo d – to inna zmienna niż d na Kartce
1. Teraz d = 2, a więc ponownie instrukcje z wiersza 3 nie wykonują się.
Instrukcja 4 każe znów podzielić listę x na dwie listy:
– L1 = 23; #,
– L2 = 4; #.
Oczywiście, znów pamiętamy, że lokalna zmienna L1 na Kartce 2 to
też zupełnie inna zmienna niż L1 na Kartce 1 itd.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

86 4. O metodach konstruowania algorytmów

My jesteśmy wciąż na Kartce 2. Skoro wykonaliśmy instrukcję 4,


czas na instrukcję 5. Ale ta znów wywołuje funkcję sortuj, tyle że z no-
wym argumentem aktualnym. Nie ma rady: na Kartce 2 zapisujemy, że po
powrocie trzeba będzie kontynuować od instrukcji 6 i odkładamy Kartkę 2
na lewą stronę, na leżącą tam już Kartkę 1. Zauważmy, że w ten sposób za-
czyna się tam tworzyć stos kartek, oczekujących na możliwość kontynuacji
wykonania.
Przygotowujemy nową Kartkę 3 i mając ją przed sobą, rozpoczynamy
wykonanie algorytmu jeszcze raz od początku. Argumentem wejściowym x
jest teraz lista

L1 = 23; #.

Jej długość d = 1, dlatego tym razem warunkowe instrukcje z wiersza 3 na-


reszcie wykonują się. Funkcja zwraca więc wartość y (czyli L3) i kończy dzia-
łanie. Pamiętając tę zwróconą wartość (L3 = 23; #), możemy już zniszczyć
Kartkę 3 i wziąć ze stosu po lewej stronie stołu tę, która leży na wierzchu.
Oczywiście, jest to Kartka 2.
Na Kartce 2 zapisujemy zwróconą przez instrukcję 5 wartość:

L3 = 23; #

i kontynuujemy wykonanie algorytmu według tejże Kartki 2. Zapisaliśmy


na niej poprzednio, że po powrocie trzeba się zająć instrukcją 6. To jednak
jest znów wywołanie funkcji sortuj. Bierzemy więc nową kartkę (tym ra-
zem jest to Kartkę 4) i zabieramy się do ponownego wykonania funkcji. Na
Kartce 2 zapisujemy natomiast, że po wykonaniu tego wywołania trzeba
będzie wrócić do instrukcji 7, po czym odkładamy Kartkę 2 z powrotem na
stos.
Wykonanie algorytmu na Kartce 4 zwraca Kartce 2 jednoelementową
listę o zawartości:

L4 = 4; #.

Samą Kartkę 4 możemy już wyrzucić, bo dojechaliśmy do końca tego wy-


konania. Bierzemy ze stosu kartkę, która leży na wierzchu. Oczywiście, jest
to przed chwilą odłożona Kartka 2. Powrót do Kartki 2 powoduje konty-
nuację rekurencyjnego algorytmu, od instrukcji 7. Jest to operacja scala-
nia. Operacja scal jest też funkcją, więc dla jej wykonania musimy utwo-
rzyć oddzielną, nową kartkę (Kartka 5), odkładając znów chwilowo na stos
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy rekurencyjne 87

Kartkę 2. Uwaga: Dla uproszczenia, Kartka 5 (wykonanie funkcji scal)


w ogóle nie jest pokazana na rysunku 4.3.
Scalanie dotyczy tym razem dwóch jednoelementowych list:
– L3 = 23; #
– L4 = 4; #.
W wyniku powstanie nowa (już uporządkowana!) lista

y = 4; 23; #,

którą Kartka 5 zwraca Kartce 2, a sama znika. Możemy teraz wrócić do


Kartki 2, ale nie na długo, bo już następna instrukcja oznacza koniec tego
wykonania. Kartka 2 zwraca więc listę y Kartce 1, jako jej, Kartki 1, zmien-
ną L3, a sama znika. Wracamy do Kartki 1, która do tej pory oczekiwała na
swoją kolej na dnie stosu z lewej strony. Teraz już nie ma żadnych kartek,
które by ją przykrywały.
Nie będziemy nużyć czytelnika rozwlekłym opisywaniem reszty tego
procesu. Rysunek 4.3 powinien wystarczyć do jego zrozumienia. W każdym
razie czynności, które omówiliśmy, powtarzają się regularnie, aż do osią-
gnięcia ostatecznego rezultatu, czyli uporządkowanej listy:

nowaL = 2; 4; 23; 33; #.

W języku informatyków mówi się, że mieliśmy tu do czynienia


z zagnieżdżonym wykonaniem funkcji (ang. nesting, nested execution).
Wewnątrz funkcji wykonuje się inna lub ta sama funkcja, wewnątrz tej funk-
cji – jeszcze jedna funkcja itd. Przypomina to rosyjską zabawkę matrioszkę:
w malowanej drewnianej babie kryje się mniejsza baba, w tej babie – następ-
na, w niej kolejna, jeszcze mniejsza i tak dalej. Informatyk powiedziałby, że
owe toczone baby są zagnieżdżone jedna w drugiej.
Choć w przykładowym wykonaniu rekurencyjnego algorytmu po-
sługiwaliśmy się kartkami i ołówkiem, to przećwiczona przez nas zasada
zagnieżdżonego wykonywania funkcji dość dokładnie odpowiada temu, co
dzieje się w rzeczywistym systemie komputerowym.
Programista, pisząc swój nowy program, posługuje się zazwyczaj
specjalistycznym oprogramowaniem, które pomaga mu w pracy. W jego
skład wchodzi między innymi edytor danego języka programowania, je-
go translator (a więc program tłumaczący kod źródłowy na język maszy-
nowy) oraz biblioteka gotowych funkcji, których oczywiście nie trzeba już
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

88 4. O metodach konstruowania algorytmów

opracowywać od nowa. Każdy język programowania oferuje też możliwość


samodzielnego definiowania i dołączania do biblioteki nowych, własnych
funkcji. Ustaleniu podlega – podobnie jak u nas – nazwa funkcji, kolej-
ność i znaczenie argumentów formalnych, zmienne lokalne, ciało funkcji
itd. Pisząc swój program, możemy ich później używać tak, jak gdyby były
pojedynczymi instrukcjami, na równi z innymi funkcjami, już znajdującymi
się w bibliotece.
Gdy w tekście programu (tj. w kodzie źródłowym) pojawia się wy-
wołanie funkcji – translator przetwarzający kod źródłowy wstawia (w ko-
dzie wynikowym) pewną standardową sekwencję instrukcji maszynowych,
która inicjuje proces wywołania funkcji. W ramach tej sekwencji wykonuje
się wszystko to, co my robiliśmy, manipulując kolejnymi kartkami. Funkcja
otrzymuje czasowo do dyspozycji własny obszar pamięci (jak gdyby naszą
kartę wykonania), rezerwuje się tam miejsce na jej zmienne lokalne itd.,
a informacje potrzebne do późniejszego wznowienia programu, który funk-
cję wywołał, wędrują na stos, zupełnie jak nasze kartki, które odkładali-
śmy na lewą stronę stołu. W podobny sposób, po zakończeniu wykonania
ciała funkcji, wraca się do poprzedniego poziomu zagnieżdżenia, odczytu-
jąc ze stosu to, co jest niezbędne do wznowienia poprzedniego programu.
Oczywiście, użytkownik nie musi się sam o to wszystko martwić2.
Tak więc rekurencja i mechanizm zagnieżdżonego wykonywania
funkcji „rozmnażają” prosty, kilkuwierszowy kod, powodując jego wielo-
krotne wykonanie dla coraz to innych argumentów aktualnych. Liczba in-
strukcji wykonanych oczywiście znacznie przekracza liczbę instrukcji na-
pisanych w kodzie źródłowym. I o to właśnie powinno chodzić dobremu
programiście.
Dzięki temu, że ręcznie przećwiczyliśmy proces wykonania tego
rekurencyjnego algorytmu sortowania, będziemy mogli ocenić jego zło-
żoność obliczeniową. Jest ona bardzo korzystna, mianowicie O(N · logN).
Posługując się rysunkiem 4.3, można to uzasadnić, wprawdzie bardzo nie-
formalnie, ale w sposób dość dobrze przemawiający do intuicji.
Jeżeli zmrużymy oczy i popatrzymy na rysunek 4.3 z pewnej odległo-
ści, ignorując szczegóły, to dostrzeżemy obrócony trójkąt, ułożony z prosto-

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

kątów, symbolizujących poszczególne wykonania funkcji. Jego wierzchoł-


kiem jest położona z lewej strony Kartka 1, a podstawą – pionowa kolumna
złożona z kartek 3, 4, 7 oraz 8. Nietrudno sobie wyobrazić, że gdyby wej-
ściowa lista L zawierała nie N = 4, lecz N = 8 liczb, to ten trójkąt musieliby-
śmy powiększyć o jeszcze jedną kolumnę, dorysowaną z prawej strony, za-
wierającą oczywiście osiem prostokątów. Tak więc dwukrotne powiększenie
długości listy wejściowej zwiększa również dwukrotnie podstawę trójkąta,
natomiast odległość od podstawy do wierzchołka (czyli wysokość trójkąta,
a tu – szerokość rysunku) wzrasta o jeden poziom zagnieżdżenia. Innymi
słowy, podstawa trójkąta rośnie proporcjonalnie do N, jego wysokość – pro-
porcjonalnie do log2N, a więc jego pole – proporcjonalnie do N · log2N.
Jeżeli przyjmiemy (a nie jest to odległe od prawdy), że liczba instrukcji
wykonywanych w czasie rekurencyjnego sortowania jest z grubsza propor-
cjonalna do pola takiego trójkąta – to stwierdzimy, że złożoność tego algo-
rytmu jest właśnie O(N · logN).
Tu jednak ostrzeżenie. Prostota rekurencyjnego algorytmu może być
złudna. Akurat ten algorytm sortowania ma złożoność O(N · logN), ale jakiś
inny rekurencyjny algorytm może w trakcie wykonania eksplodować wy-
kładniczo, choć wcale tego nie będzie widać po pozornie prostym tekście
opisującego go programu.
Dobrym, dość znanym przykładem może być rekurencyjny algorytm
rozwiązania łamigłówki znanej pod nazwa wieże Hanoi (ang. towers of
Hanoi). Nie będziemy go tu szerzej omawiać, bo jest on często przytacza-
ny jako przykład rekurencji i łatwo go odnaleźć zarówno w literaturze, jak
i w Internecie. W tym przypadku kilkuwierszowy, nie dłuższy od naszego
sortowania, niewinnie wyglądający rekurencyjny algorytm ma złożoność
O(2N), a więc eksploduje wykładniczo.
Anegdota, która dała nazwę problemowi, odwołuje się do rzekomej
dalekowschodniej legendy, którą jedni lokują w Wietnamie, inni – w Indiach
(wieże Brahmy), a która naprawdę jest, jak się zdaje, wymysłem pewnego
dziewiętnastowiecznego francuskiego matematyka. Zgodnie z nią Bóg, stwo-
rzywszy świat, powierzył wybranym kapłanom (czy też mnichom) wieżyczkę,
ułożoną z 64 szczerozłotych (jakże by inaczej!) krążków. Przypomina ona dzie-
cinną zabawkę: podstawkę z drążkiem, na który nanizane są kolorowe krążki,
najpierw duży, potem coraz mniejsze i mniejsze, a każdy z otworem w środku.
Bawiąc się nią, małe dzieci uczą się porównywać i układać krążki we właści-
wej kolejności. Zadanie mnichów jest nieco trudniejsze: muszą oni przełożyć
wszystkie krążki z pierwotnego stosu na inny stos (nazwijmy go docelowym),
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

90 4. O metodach konstruowania algorytmów

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

Metody Monte Carlo


Ta grupa algorytmów wykorzystuje pewne pojęcia i techniki znane z ra-
chunku prawdopodobieństwa i statystyki matematycznej. Pomysł pochodzi
od Stanisława Ulama, jednego z największych polskich matematyków XX
wieku. Był on za życia stosunkowo mało w Polsce znany, gdyż przez więk-
szość życia działał w USA, między innymi w gronie najznakomitszych fi-
zyków i matematyków, którzy w latach II wojny światowej pracowali w Los
Alamos nad zbudowaniem bomby atomowej, a potem termojądrowej.
Nazwa tego rodzaju algorytmów nie jest przypadkowa: odnosi się
przecież do miasta sławnego z losowych gier hazardowych, które są – moż-
na powiedzieć – praktycznym ucieleśnieniem rachunku prawdopodobień-
stwa.
Istotę podejścia można łatwo pokazać na następującym przykładzie.
Powiedzmy oto, że mamy zarejestrowany wykres, obrazujący, jak się zmie-
niała prędkość samochodu w trakcie podróży (rysunek 5.1a). Chcielibyśmy
na jego podstawie obliczyć, ile kilometrów ten samochód przejechał pomię-
dzy, powiedzmy, dziesiątą a sześćdziesiątą minutą jazdy.

Rys. 5.1. Obliczanie całki oznaczonej metodą prostokątów


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

92 5. Algorytmy probabilistyczne i ewolucyjne

Szkolna wiedza o zależności pomiędzy prędkością, drogą i czasem


podpowie nam, że poszukiwana wielkość jest równa powierzchni pola ob-
szaru, ograniczonego od góry wykresem prędkości, od dołu – poziomą osią
czasu t, a z lewej i z prawej strony – odpowiednio pionowymi liniami pro-
stymi t1 = 10 minut oraz t2 = 60 minut. To pole musimy więc obliczyć.
Osoby, którym nie jest obcy rachunek całkowy, powiedzą, że nasze
zadanie polega na obliczeniu całki oznaczonej z funkcji ciągłej v(t) w gra-
nicach od t1 do t2. Oznacza to dokładnie to samo, co stwierdziliśmy w po-
przednim akapicie.
Obliczanie wartości całek oznaczonych ma bardzo praktyczny cel
w wielu dziedzinach techniki i nauki. O prędkości i drodze już powiedzie-
liśmy; podobnie byłoby, gdyby zadana funkcja y(t) oznaczała np. zmiany
(w czasie) poboru mocy przez przedsiębiorstwo. Wtedy całka oznaczona tej
funkcji w przedziale np. od pierwszego do ostatniego dnia stycznia (a więc
również odpowiednie pole „pod wykresem” funkcji) byłaby równa ener-
gii, za której pobór w styczniu przedsiębiorstwo musi zapłacić elektrow-
ni. Analogicznie, gdyby zadana funkcja opisywała nie zmienność w czasie,
lecz przestrzenny kształt złożonej powierzchni (np. kopuły nad budowlą), to
wielowymiarowa całka oznaczona (z odpowiednimi granicami przedziałów)
odpowiadałaby objętości bryły zawartej pod tą kopułą, itd.
Klasyczne rozwiązanie problemu obliczania całek oznaczonych, zna-
ne z tradycyjnej analizy matematycznej (wiodące przez wyznaczanie całki
nieoznaczonej) pochodzi z epoki, w której o informatyce nikomu się nie
śniło. Zostawmy je na boku. Nam wystarczy zdroworozsądkowe, też dobrze
znane, numeryczne rozwiązanie problemu. Polega ono na tym, by w celu
obliczenia pola podzielić interesujący nas przedział czasu na wąskie odcinki
o długości ∆ (rysunek 5.1b), a następnie stopniowo, w pętli, obliczać i su-
mować pola wąskich prostokącików (albo – jeszcze lepiej – trapezików),
tworzących cały obszar. Łatwo ten pomysł przekształcić w prawdziwy (ale
jeszcze nie probabilistyczny) algorytm. Im te elementarne prostokąty (trape-
zy) będą węższe – tym dokładniej ich suma będzie przybliżać rzeczywistą
wielkość pola, ale jednocześnie będzie ich więcej. Więcej też będzie opera-
cji potrzebnych do uzyskania rezultatu.
Rozwiązanie probabilistyczne opiera się na innej zasadzie. Wyobraźmy
sobie, że zamknęliśmy interesujący nas fragment wykresu prędkości w pro-
stokącie o znanych wymiarach: o szerokości równej T = t2 – t1 i wyso-
kości równej maksymalnej wartości funkcji w tym przedziale (Vmax, jak
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Metody Monte Carlo 93

na rysunku 5.2a). Wylosujmy teraz całkowicie przypadkowo jeden punkt


z tego prostokąta, tak jak gdybyśmy wbili weń na ślepo igłę. Ten punkt albo
znajdzie się nad krzywą, albo trafi w interesujące nas pole. Powtórzmy takie
losowanie np. 1000 razy, zliczając, ile razy wylosowany punkt znalazł się
pod krzywą, to znaczy w granicach pola. Jeśli zdarzyło się to, na przykład,
673 razy na 1000 losowań, oznacza to, że statystycznie rzecz biorąc, po-
szukiwana powierzchnia pola jest w przybliżeniu równa 673/1000 = 0,673,
czyli 67,3% powierzchni całego prostokąta, a ta przecież jest łatwa do obli-
czenia. I już. Żadne obliczanie i sumowanie pól elementarnych prostokątów
czy trapezów nie jest potrzebne.

Rys. 5.2. Zasada obliczania całki oznaczonej metodą Monte Carlo

Ż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

94 5. Algorytmy probabilistyczne i ewolucyjne

– z pionowego przedziału wartości funkcji) określą nam współrzędne jed-


nego losowego punktu na powierzchni prostokąta. Następnie, dla pierwszej
z tych losowych liczb (oznaczmy ją ti), która w naszym przykładzie jest po-
ziomą współrzędną punktu, należy obliczyć wartość zadanej funkcji (czyli
v(ti)) i porównać ją (jak na rysunku 5.2b) z drugą losową wartością (vi),
która oznacza współrzędną tego losowego punktu w pionie.
Jeżeli v(ti) ≥ vi, to punkt leży we wnętrzu lub na granicy poszukiwa-
nego pola, jeżeli jest inaczej – leży powyżej. Oczywiście, trzeba teraz skon-
struować pętlę, obieganą odpowiednią liczbę (np. N = 1000) razy, powtarzać
w niej losowanie punktu, porównywanie jego położenia z krzywą, jedno-
cześnie zliczając punkty, które trafiły pod krzywą. Na koniec – wystarczy
obliczyć odpowiedni ułamek (jak owo 0,673 w powyższym przykładzie)
i pomnożyć przezeń powierzchnię prostokąta.
Ale jak sprawić, by komputer produkował na życzenie losowe licz-
by z zadanego przedziału? Okazuje się, że nie jest to wcale takie proste.
Komputer jest skonstruowany po to, by mechanicznie i niezawodnie wy-
konywał operacje arytmetyczne i logiczne, a ich rezultaty mają właśnie nie
mieć przypadkowego charakteru. Operacja „2 + 2” musi dawać w wyniku
dokładnie 4, a nie 4 lub 5 z prawdopodobieństwem 1/2, albo liczbę „w oko-
licach 4”. Czy takie sztywne, deterministyczne urządzenie w ogóle można
zmusić do jakiegokolwiek losowego zachowania?
Jeden z najstarszych pomysłów na produkowanie przez komputer
liczb losowych (ściślej – tzw. liczb pseudolosowych, o czym niżej) pocho-
dzi od Johna von Neumanna, tego samego, który uchodzi za intelektualnego
ojca wszystkich współczesnych komputerów. Metoda była zaproponowana
jeszcze w roku 1946, a jej nazwa: „środek z kwadratu” (ang. middle squ-
are), dobrze oddaje istotę pomysłu.
Weźmy mianowicie dowolną liczbę naturalną, powiedzmy, że sze-
ściocyfrową. Posłuży ona do zainicjowania procesu generowania ciągu
(pseudo)przypadkowych liczb i jest nazywana „ziarnem” (ang. seed) tego
procesu. Niech to będzie – dla przykładu – liczba 487 643. Podnieśmy ją do
kwadratu: otrzymamy liczbę dwunastocyfrową (tu: 237 795 695 449).
Weźmy teraz jej sześć środkowych cyfr (795 695) i powiedzmy: to
jest właśnie poszukiwana liczba losowa. Jeśli trzeba ponowić losowanie –
podniesiemy 795 695 do kwadratu (otrzymamy 633 130 533 025), weźmie-
my sześć jej środkowych cyfr i powiemy: oto nowa liczba losowa: 130 533.
Za następnym razem wykonamy 130 5332 = 017 038 864 089 (proszę zwró-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Metody Monte Carlo 95

cić uwagę na zero, uzupełniające długość wyniku do umówionych 12 cyfr),


a kolejną liczbą losową będzie 038 864 i tak dalej.
Łatwo tę zasadę przekształcić w program, zwany generatorem liczb
pseudolosowych (ang. pseudorandom number generator), który na żądanie
wyprodukuje kolejną liczbę naturalną, w tym przypadku – z przedziału od
0 do 999 999, o przypadkowej (jak się wydaje) wartości. Kiedy zaprzesta-
niemy generowania, ostatnia wyprodukowana liczba zostanie zapamiętana
i – w momencie ponownego uruchomienia generatora – będzie użyta jako
domyślne „ziarno”, jeżeli nie będzie się nam chciało wymyślać i jawnie za-
dawać „ziarna” od nowa.
W konkretnym zastosowaniu każdą z takich liczb należy oczywiście
przeskalować zgodnie z warunkami zadania: jeśli na przykład chcemy mieć
liczby losowe z przedziału od 0 do 99 (niekoniecznie całkowite), a generator
dostarcza liczb sześciocyfrowych (od 0 do 999 999) – to każdą z nich trzeba
podzielić przez 10 000. Wspomniane wyżej liczby miałyby wtedy wartość
odpowiednio: 79,5695; 13,0533; 3,8864 i tak dalej. Podobnie, gdybyśmy
chcieli symulować wynik rzutu monetą – moglibyśmy ustalić, że jeśli licz-
ba losowa z naszego sześciocyfrowego generatora jest mniejsza od 500 000
– to orzeł, w przeciwnym razie – reszka, itd.
Jeśli popatrzymy na długi ciąg składający się z kilku tysięcy wyge-
nerowanych w ten sposób liczb – nie dostrzeżemy między nimi żadnego
związku, żadnej reguły uzależniającej kolejne wartości od porządku ich na-
stępowania po sobie. Nie mają też one tendencji do grupowania się i roz-
kładają się stosunkowo równomiernie po całym przedziale od 0 do 999 999.
Wyglądają więc na przyzwoite, przypadkowe liczby. Jednak – z drugiej
strony – ta przypadkowość jest w istocie udawana. Liczby są produkowane
zgodnie ze stałą, deterministyczną zasadą i jeżeli jutro powtórzymy proces
generowania, zaczynając od tego samego ziarna – otrzymamy dokładnie taki
sam ciąg liczb, jak poprzednio. Dlatego tak produkowane liczby nazywamy
pseudolosowymi.
Opisana metoda („środek z kwadratu”) ma swoje wady, wymagające
pewnych ulepszeń, choćby to, że po pseudoprzypadkowym wygenerowaniu
liczby 0 generator ugrzązłby w zerze na zawsze, gdyż potem już zawsze
02 = 0. To jest jeszcze łatwe do ominięcia, ale co gorsza taki generator ma
również pewien cykl, po którym liczby nieuchronnie się powtarzają. W na-
szym przykładzie liczby pseudolosowe były sześciocyfrowe, więc najdalej po
milionie losowań musiałaby się pojawić liczba, która już kiedyś była – i od
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

96 5. Algorytmy probabilistyczne i ewolucyjne

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

Symulacja losowych zjawisk zachodzących w czasie 97

Gdyby jednak była to funkcja np. trzech zmiennych, a warunki za-


dania nakazywały podzielić przedział zmienności każdej z nich na 1000
elementarnych przedziałów – to podobne zdroworozsądkowe numeryczne
całkowanie wymagałoby obliczenia i zsumowania 1000 ⋅ 1000 ⋅ 1000 =
= 1 000 000 000 elementarnych czterowymiarowych brył. W takim przypad-
ku analogiczny algorytm probabilistyczny mógłby dostarczyć realistycznego
oszacowania wartości tej całki już przy znacznie mniejszej liczbie losowań.
Taka argumentacja leży u podstaw całej klasy metod rozwiązywania
bardzo różnorodnych zadań w nauce i technice za pomocą algorytmów pro-
babilistycznych. Tak można rozwiązywać liczne problemy wielowymiaro-
wego całkowania, optymalizacji, symulacji złożonych zjawisk fizycznych
i tak dalej.

Symulacja losowych zjawisk zachodzących w czasie


Istnieją również takie problemy, których w ogóle nie można rozwiązać ina-
czej, niż przy użyciu metod probabilistycznych, a to dlatego, że dotyczą
zjawisk ze swej natury losowych i to o takich właściwościach, że analitycz-
ne rozwiązania nie są znane. Wtedy praktycznie jedyną drogą do poznania
interesujących nas zależności jest symulacja takiego zjawiska lub procesu,
połączona ze zbieraniem odpowiednich danych i analizowanie ich przy uży-
ciu technik znanych ze statystyki.
Dla wsparcia intuicji, wyobraźmy sobie gabinet lekarski, do którego
zgłaszają się pacjenci. Czas pomiędzy pojawianiem się kolejnych pacjentów
w drzwiach przychodni jest zmienną losową, której rozkład jest możliwy
do oszacowania na podstawie obserwacji. Terminy te znaczą tyle, że (je-
śli tylko pacjenci nie umówili się między sobą) czas pomiędzy kolejnymi
pacjentami nie jest stały, lecz przyjmuje różne, przypadkowe wartości, po
drugie – że potrafimy powiedzieć, jakie wartości tego czasu pojawiają się
częściej, a jakie rzadziej.
Jeżeli pacjent stwierdzi, że poczekalnia jest pusta, a lekarz nie przyj-
muje innego chorego – wchodzi prosto do gabinetu. W przeciwnym razie
– zajmuje miejsce w poczekalni. Tworzy się w ten sposób kolejka. Lekarz,
żegnając poprzedniego pacjenta, zaprasza następnego z kolejki. Pacjent
przebywa u lekarza przez pewien czas, który nazwiemy czasem obsługi.
Niech nas nie interesuje, dlaczego jeden pacjent przebywa u lekarza dłużej,
a inny – krócej: notujmy tylko wartości czasu, przez jaki byli przez lekarza
obsługiwani kolejni pacjenci. Stwierdzimy z pewnością, że czas obsługi
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

98 5. Algorytmy probabilistyczne i ewolucyjne

jest również zmienną losową o pewnym rozkładzie, oczywiście innym niż


czas między zgłoszeniami. Co więcej, jeśli zajrzymy do przychodni kilka
razy dziś, jutro czy pojutrze – zobaczymy, że raz w kolejce czekają trzy oso-
by, kiedy indziej – pięć, a kiedy indziej – nikt. Długość kolejki jest więc też
zmienną losową (tym razem – przyjmującą nieujemne wartości całkowite:
3, 5, 0, ... itd.), a jej rozkład (mówiący, jakie wartości występują częściej,
a jakie rzadziej) zależy od rozkładu czasu między zgłoszeniami i rozkładu
czasu obsługi.
Opisana przychodnia lekarska jest przykładem elementarnego sys-
temu obsługi (ang. service system), jak na rysunku 5.3. Składa się on z urzą-
dzenia obsługującego – serwera (ang. server, w tym przypadku – gabinet
lekarski) oraz kolejki (ang. queue), a jego zadaniem jest obsługa losowego
strumienia klientów (ang. clients, customers). Wewnątrz serwera może pra-
cować jeden mechanizm obsługi (u nas – lekarz) lub kilka. Podobny cha-
rakter ma urząd pocztowy, sklep, apteka, bank, okienko w urzędzie gminy,
warsztat naprawy samochodów czy wreszcie internetowy serwer, w którego
kolejce stoją komunikaty czy polecenia realizacji usług.
Już w przypadku takiego pojedynczego systemu obsługi można wy-
obrazić sobie wiele wariantów jego działania. Niekiedy czas obsługi jest
sumą kilku (również losowych) czasów, tak jak gdyby we wnętrzu serwera
działało kilka mechanizmów obsługi, ale nie niezależnych, tylko działają-
cych kolejno po sobie. Samych serwerów może być kilka, a praca może
być zorganizowana tak, że każdy serwer ma swoją oddzielną kolejkę, albo
tak, że jest jedna, wspólna kolejka do zespołu kilku serwerów (np. do kilku
równorzędnych okienek w banku czy urzędzie pocztowym).

Rys. 5.3. Najprostszy system obsługi


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Symulacja losowych zjawisk zachodzących w czasie 99

Czasami jest kilka kolejek do jednego serwera: na przykład – u lekarza


– jedna kolejka zwykłych pacjentów, druga – tzw. nagłych przypadków, wy-
magających obsłużenia w pierwszej kolejności. Podobnie, w systemie kom-
puterowym może być kilka kolejek o różnym priorytecie dostępu do serwera.
Jaką wtedy przyjąć strategię szeregowania zadań, to znaczy, według jakiego
kryterium wybierać z kolejki następnego klienta do obsłużenia? Czy poja-
wienie się klienta o wyższym priorytecie powinno powodować zawieszenie
obsługi i wyproszenie obecnie obsługiwanego klienta z powrotem do kolejki?
Tego typu pytań (i odpowiedzi na nie) można sformułować wiele.
Co więcej, elementarne systemy obsługi często łączą się w sieci ob-
sługi, inaczej sieci kolejkowe (ang. queueing networks) składające się z wie-
lu systemów obsługi oraz reguł poruszania się klientów w takiej sieci.
Pozostając przy naszym medycznym przykładzie, wyobraźmy sobie
całą poradnię medyczną (rysunek 5.4), w której są gabinety dwóch spe-
cjalności (powiedzmy, że internistyczny i kardiologiczny), jest gabinet za-
biegowy oraz apteka. Każda z tych jednostek, traktowana oddzielnie, jest
systemem obsługi, z charakterystycznym dla siebie rozkładem czasu obsłu-
gi, swoim serwerem (serwerami), kolejkami itd. Czas pomiędzy kolejnymi
zgłoszeniami pacjentów do całej przychodni jest zmienną losową T1. Czas
przebywania u internisty (również losowy) jest oznaczony jako T2, W ga-

Rys. 5.4. Przykładowa sieć obsługi


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

100 5. Algorytmy probabilistyczne i ewolucyjne

binecie zabiegowym działają dwie panie pielęgniarki, obsługujące wspólną


kolejkę, a losowy czas jednego zabiegu jest T3. Nie będziemy kontynuowali
tego opisu, rysunek jest dostatecznie jasny.
Obserwacja działania przychodni może nam dostarczyć jeszcze innych,
dodatkowych danych, również o probabilistycznym charakterze, opisujących
reguły ruchu klientów w sieci. Dla przykładu, może się okazać, że:
– nowi pacjenci udają się w 80% przypadków do internisty, a w 20% – do
kardiologa,
– każdy pacjent opuszczający gabinet internisty w 40% przypadków udaje
się do domu, w 40% przypadków – do apteki, a w 20% – do gabinetu
zabiegowego,
– połowa pacjentów obsłużonych w gabinecie zabiegowym wraca do inter-
nisty, a pozostała połowa – idzie do domu,
– 75% pacjentów kardiologa idzie do apteki, gdzie są obsługiwani tylko
przy jednym okienku,
– 5% pacjentów drugiego okienka apteki wraca raz jeszcze do kardiologa,
…i tak dalej. Łatwo te proste zależności przedstawić tak jak na rysunku 5.4,
albo w postaci odpowiedniej tablicy prawdopodobieństw.
Ale mogą być też reguły znacznie bardziej skomplikowane. Pacjenci
mogą się dzielić na różne klasy (na przykład na mężczyzn i kobiety lub na
dzieci i dorosłych), o różnych zasadach przemieszczania się w sieci. Decyzje
o zajęciu miejsca w kolejce mogą zależeć od aktualnej, chwilowej długości
kolejki lub od łącznego czasu, jaki pacjent spędził już w poradni itd. Takich
możliwości jest bardzo wiele.
Nie trzeba wielkiej wyobraźni, by uświadomić sobie, że w podob-
ny sposób – jako sieć obsługi – można opisać system obiegu dokumentów
w dużej firmie, procedurę załatwiania reklamacji konsumenckiej czy po-
dania o kredyt, wielostanowiskową samochodową stację serwisową, proces
produkcji złożonych wyrobów, system transportowy, fragment sieci kompu-
terowej, a w gruncie rzeczy – każde niebanalne przedsiębiorstwo, składają-
ce się z mniejszych jednostek i obsługujące strumień masowych, losowych
zgłoszeń.
Każde takie przedsiębiorstwo, jeśli szanuje swoją pracę i budżet, a tak-
że swoich klientów, powinno poddać swoją działalność analizie. Uzyskane
z niej dane mogą pomóc w poprawieniu jakości obsługi i sytuacji ekono-
micznej firmy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Symulacja losowych zjawisk zachodzących w czasie 101

Można łatwo wymienić ogromną liczbę pytań, na które taka analiza


powinna odpowiedzieć. Dla przykładu: jaka jest przepustowość tej sieci ob-
sługi, to znaczy ilu klientów jest ona w stanie obsłużyć w jednostce czasu
(np. na godzinę, na dobę, na miesiąc). Ilu (średnio) pacjentów przebywa
jednocześnie w lokalu przychodni? Jak długi czas (średnio) spędza pacjent
w przychodni? Jaka jest średnia długość kolejki u internisty (kardiologa,
w zabiegowym itd.)? Jaki jest średni czas oczekiwania w kolejce i samej
wizyty u każdego z lekarzy łącznie? Jaki procent czasu pracy każdy z pra-
cowników (lekarze, pielęgniarki, aptekarze) spędza na rzeczywistej obsłu-
dze pacjentów, a jaki – na oczekiwaniu na pacjenta? Czy wszyscy są mniej
więcej sprawiedliwie obciążeni? Czy nie ma tu wąskich gardeł, to znaczy
miejsc, w których kolejki (i czas w nich oczekiwania) są wyraźnie dłuższe
niż gdzie indziej?
Co więcej, często chcielibyśmy wiedzieć, jaki efekt odniosą ewen-
tualne zmiany w organizacji przedsiębiorstwa. Mając ograniczone środki,
możemy stanąć przed dylematem: czy zatrudnić drugiego internistę, a za
to zwolnić jedną pielęgniarkę? Jak by to wpłynęło na długość kolejek, czas
oczekiwania, przepustowość itd.? A co by się stało, gdyby wskutek awarii
w sąsiedniej przychodni (albo epidemii grypy) liczba klientów napływają-
cych do nas w ciągu doby zwiększyła się dwukrotnie?
Działaniem tego typu obiektów zajmuje się dział matematyki, zwa-
ny teorią masowej obsługi lub teorią kolejek (ang. queueing theory). Dla
dość szerokiej klasy systemów i sieci obsługi dostarcza on gotowych wzo-
rów, pozwalających na obliczenie np. średniej długości poszczególnych ko-
lejek, rozkładu i wartości średniej całkowitego czasu przebywania klienta
w systemie i całej sieci obsługi, liczby klientów obsługiwanych w jednostce
czasu. Oczywiście, te wartości są uzależnione nie tylko od struktury sieci
i prawdopodobieństw wyboru drogi pomiędzy jej węzłami, lecz również od
rozkładów wszystkich losowych zmiennych, takich jak czas między zgło-
szeniami, czasy obsługi w poszczególnych serwerach itp.
Niemniej, aby można było posłużyć się takimi gotowymi wzorami,
analizowana sieć obsługi musi czynić zadość pewnym ograniczającym wa-
runkom. Aby nie odrywać się od głównego nurtu wykładu, nie będziemy
ich tu dokładniej omawiać. Jednak w praktyce zdarza się bardzo często, że
te warunki nie są spełnione – a więc nie istnieją dla tych przypadków goto-
we rozwiązania analityczne. W takiej sytuacji jedynym praktycznym sposo-
bem postępowania jest stworzenie symulacyjnego modelu sieci kolejkowej,
a następnie jego symulacja, połączona ze zbieraniem odpowiednich staty-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

102 5. Algorytmy probabilistyczne i ewolucyjne

styk. Interesujące nas zależności dopiero wyłonią się z tych statystycznych


danych.
Model symulacyjny tworzą struktury danych oraz opisy algorytmów
postępowania. Programowe struktury danych (np. tablice, listy, grafy) repre-
zentują w modelu jego „nieruchome” elementy: kolejki, serwery, połączenia
między nimi itd., a także obiekty „ruchome” (takie jak klienci, pacjenci, do-
kumenty, przetwarzane części…), które przemieszczają się pomiędzy nimi.
Każde pojawienie się nowego „ruchomego” obiektu jest nazywane zda-
rzeniem. Podobnie, zdarzeniami są też wszelkie przejścia owych ruchomych
obiektów pomiędzy kolejkami, serwerami etc. Algorytmy, wchodzące w skład
modelu, opisują natomiast, co należy zrobić w momencie wystąpienia każde-
go ze zdarzeń: na przykład dokąd skierować pacjenta (o określonych właści-
wościach) w momencie jego pojawienia się w drzwiach przychodni, a dokąd
w chwili, gdy opuszcza on dany serwer itd. Niektóre ze zdarzeń pociągają za
sobą wygenerowanie losowego czasu, np. wejście pacjenta do serwera powo-
duje wyprodukowanie losowej wartości czasu, przez jaki będzie on w tym
serwerze przebywał. Oczywiście, zakończenie tego czasu będzie w przyszło-
ści oznaczało nowe zdarzenie, które też będzie wymagało obsługi.
Oprócz tak sporządzonego modelu potrzebny jest również symulator
– program, który uczyni z modelu praktyczny użytek. Symulator generuje
nowych klientów i potem przestawia ich pomiędzy elementami modelu
zgodnie z algorytmami obsługi zdarzeń. Naśladuje to rzeczywisty ruch
klientów w sieci, ale dzieje się to znacznie szybciej niż w rzeczywisto-
ści. Jednocześnie, symulator zapamiętuje dane, które też zostały wcześniej
zdefiniowane w modelu, na przykład czas, jaki upłynął pomiędzy wej-
ściem do określonej kolejki a wyjściem z jej serwera, czas między poja-
wieniem się klienta w sieci i jej opuszczeniem, liczbę klientów w kolejce
itd. Później, po przeprowadzeniu symulacji wielu godzin (a może nawet
miesięcy) pracy sieci tak zebrane dane posłużą do wyliczania wartości
średnich i rozkładów tych losowych zmiennych, o których chcieliśmy się
dowiedzieć.
Takiemu badaniu symulacyjnemu poddano na przykład wyżej przy-
toczony model przychodni medycznej. Okazało się, że wąskim gardłem
jest internista, przed którego gabinetem tworzy się coraz dłuższa kolejka.
Sytuację uzdrawia dodanie drugiego internisty o identycznym rozkładzie
czasu obsługi i ze wspólną kolejką do obu internistów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 103

Symulator można by oczywiście samodzielnie zaprojektować i wyko-


nać, choć to dość złożona sprawa. Na szczęście, są już od dawna dostępne na
rynku gotowe symulatory, a nawet całe systemy oprogramowania do celów
symulacji. Umożliwiają one składanie bardzo złożonych modeli symulacyj-
nych z gotowych elementów, definiowanie ich liczbowych parametrów, opi-
sywanie algorytmów obsługi zdarzeń oraz danych wyjściowych, które mają
być zbierane w czasie symulacji. Sam symulator, wchodzący w skład takiego
oprogramowania, oprócz swojej podstawowej funkcji, jaką jest naśladowanie
działania sieci połączone ze zbieraniem danych, prowadzi także z zasady ani-
mację, pokazując na ekranie ruch klientów w symulowanej sieci.
Tego typu symulatory, nazywane symulatorami zdarzeń dyskretnych
(ang. discrete event simulators), znajdują zastosowanie w wielu dziedzi-
nach. Dla porządku należy dodać, że oprócz nich istnieją także inne symu-
latory, działające na innej zasadzie, służące do symulacji zjawisk ciągłych,
występujących np. w fizyce, mechanice, meteorologii.

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

104 5. Algorytmy probabilistyczne i ewolucyjne

takich wartości parametrów równania, by odpowiadająca mu linia była moż-


liwie dobrze dopasowana do układu punktów.
W kategoriach nieco bardziej ogólnych możemy powiedzieć, że po-
stanowiliśmy rozwiązać zadanie optymalizacji: chcemy znaleźć możliwie
najlepszy (a więc właśnie optymalny) układ wartości wielu parametrów.
Takie problemy występują we wszystkich dziedzinach nauki, techniki, eko-
nomii, nic więc dziwnego, że istnieje duża liczba metod i algorytmów ich
rozwiązywania, nieodwołujących się wcale do obliczeń ewolucyjnych. My
skoncentrujemy się jedynie na pokazaniu, jak można sobie z problemami
optymalizacji radzić, stosując właśnie algorytm ewolucyjny.
Żeby problem nie był banalny, załóżmy, że poszukiwana krzywa ma
być sumą trzech sinusoid i jednej stałej. Dlaczego akurat taką sumą? Jedynie
dlatego, że jest to przykład funkcji o sporej, ale niezbyt dużej liczbie pa-
rametrów, odwołujący się do szkolnej wiedzy i niewymagający szerszego,
technicznego wprowadzania w jakieś bardziej praktyczne zagadnienie opty-
malizacji.
Przypomnijmy, że pojedyncza sinusoida jest graficznym obrazem
funkcji o następującej postaci:

 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


T
przez k. Wielkość

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ć:

y ( x) = a1 ⋅ sin ( k1 ⋅ x + p1 ) + a2 ⋅ sin ( k2 ⋅ x + p2 ) + a3 ⋅ sin ( k3 ⋅ x + p3 ) + c ,


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 105

gdzie ai, ki, pi (i = 1, 2, 3) są parametrami poszczególnych sinusoid, zaś c


jest pewną stałą. Dopasowanie funkcji y(x) do zadanych punktów polega
więc na odpowiednim doborze wartości dziesięciu parametrów:
a1, k1, p1, a2, k2, p2, a3, k3, p3, c.

Najbardziej naiwne rozwiązanie problemu polegałoby na systematycznym


przejrzeniu wszystkich możliwych układów wartości dziesięciu parametrów
i znalezieniu najlepszego wśród nich. Gdybyśmy się zdecydowali na tę (po-
wiedzmy od razu: beznadziejną) metodę, moglibyśmy na przykład założyć,
że wypróbujemy 100 wartości każdego z parametrów, w odpowiednich dla
każdego z nich granicach. Łatwo policzyć, że po takiej decyzji wszystkich
możliwych konfiguracji wartości 10 parametrów jest 10010, czyli 1020 (słow-
nie: sto miliardów miliardów możliwości). Nie jest to więc ani pomysłowe,
ani zalecane rozwiązanie: szkoda życia na takie obliczenia.
Zdefiniujmy więc najpierw prostą strukturę, w której będziemy zapisy-
wali jeden komplet wartości naszych dziesięciu parametrów (rysunek 5.5).
Nazwiemy ją wektorem wartości parametrów, gdyż rzeczywiście od strony
matematycznej jest to wektor o dziesięciu współrzędnych. Komplet dzie-
sięciu wartości wpisanych w „okienka” pokazane na rysunku 5.5 w pełni
definiuje jedną konkretną funkcję y(x).

Rys. 5.5. Wektor wartości parametrów

Wyobraźmy sobie teraz, że wypełniamy taki wektor dziesięcioma zupełnie


przypadkowymi liczbami, na przykład uzyskanymi za pomocą generatora
liczb losowych. Niech tak wypełniony wektor nazywa się v1. Gdybyśmy te
przypadkowe wartości wpisali w odpowiednie miejsca w wyrażeniu opisu-
jącym założoną postać funkcji y(x), a następnie obliczyli i wykreślili prze-
bieg tej funkcji – zobaczylibyśmy najprawdopodobniej jakąś krzywą y1(x),
rozpaczliwie niepasującą do punktów, przez które ma przechodzić.
Utwórzmy na próbę jeszcze jeden, równie losowy wektor v2. Jego
zawartość będzie inna, więc podstawienie poszczególnych parametrów do
y(x) da w rezultacie inną krzywą y2(x). Może ona będzie lepiej niż y1(x)
pasować do zadanego układu punktów? Gdyby tak w istocie było, mogliby-
śmy powiedzieć, że wektor v2 jest lepszy od poprzedniego v1.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

106 5. Algorytmy probabilistyczne i ewolucyjne

Ale co to właściwie znaczy, że jedna krzywa pasuje lepiej, a inna


gorzej? Potrzebujemy jakiegoś formalnego kryterium, które pozwoliłoby
liczbowo ocenić stopień dopasowania. Dla naszego zadania kryterium takie
łatwo zdefiniujemy, popatrzywszy na rysunek 5.6.

Rys. 5.6. Wyznaczanie współczynnika dostosowania

Widzimy na nim rozrzucony na płaszczyźnie zbiór n punktów (oznaczonych


dużymi kropkami) oraz hipotetyczną krzywą yv(x), która wynikłaby z pod-
stawienia jakiegoś wektora parametrów v do wzoru na ogólną postać funk-
cji y(x). Narysowana krzywa wyraźnie „źle pasuje” do zadanych punktów.
Dla poszczególnych punktów odstępstwa mają postać pionowych odcinków,
zaznaczonych przerywaną linią. Moglibyśmy długości tych odcinków zsu-
mować i otrzymaną sumę podzielić przez liczbę punktów. Otrzymalibyśmy
wówczas średnie odstępstwo (w przeliczeniu na jeden punkt). Im ta wartość
jest mniejsza, tym lepiej dana krzywa pasuje do układu punktów. W ideal-
nym przypadku (jeśli jest on w ogóle możliwy do osiągnięcia) powinna być
wręcz równa zeru.
Taka miara dostosowania nie jest jednak dobra. Chodzi o to, że dla
niektórych punktów odchylenia bywają „w górę”, dla innych „w dół’, są
zatem dodatnie i ujemne. Gdyby je zwyczajnie zsumować, to odstępstwa
dodatnie i ujemne niwelowałyby się, a wynik mógłby być bliski zeru nawet
dla krzywych znacznie odbiegających od ideału dostosowania. Zastosujemy
więc sztuczkę często wykorzystywaną przy analizie błędów, odchyleń od
średniej itd., a mianowicie przed sumowaniem długość każdego odcinka
podniesiemy do kwadratu. Zagwarantuje to, że wszystkie składniki sumy
będą nieujemne.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 107

W rezultacie przyjmijmy, że miarą dostosowania (ang. fitness) wek-


tora v będzie suma kwadratów pionowych odległości. Innymi słowy, zdefi-
niujmy współczynnik ϕ(v) określony następującym wzorem:

n
∑ ( yi − yv ( xi ))2
i =1
ϕ (v ) = ,
n

gdzie n jest liczbą punktów, (xi, yi) są współrzędnymi i-tego punktu (i = 1, 2,


…, n), natomiast yv(xi) – wartością, jaką funkcja yv(x) przyjmuje dla x = xi.
Na rysunku 5.6 pokazano interpretację tych oznaczeń dla jednego z punk-
tów i wydaje się, że dalszych objaśnień nie trzeba.
Teraz już wiemy, czego poszukujemy: takiego wektora dziesięciu pa-
rametrów, dla którego współczynnik dostosowania ϕ(v) ma możliwie małą
wartość. On da krzywej yv(x) możliwie najlepsze dopasowanie jej kształtu
do zadanego układu punktów.
Zasada algorytmu ewolucyjnego zakłada, że każdy wektor, taki jak
na rysunku 5.5, wypełniony konkretnymi wartościami parametrów jest jed-
nym osobnikiem, a każdy z parametrów jest jego jednym genem. U nas
osobniki mają więc po dziesięć genów. Tworzy się następnie całą popula-
cję takich osobników, liczącą, powiedzmy, kilkaset lub kilka tysięcy takich
indywiduów.
Populacja podlega cyklicznemu procesowi ewolucji, a każdy obieg
cyklu oznacza jedno pokolenie. Zasada polega na tym, że w każdym pokole-
niu gorzej dostosowane osobniki zginą, a te, które pozostaną – wyprodukują
potomstwo. Powstanie w ten sposób nowa populacja, w której proces wy-
mierania i rozmnażania powtórzy się. Takie cykliczne następstwo pokoleń
będzie trwać aż do momentu, kiedy uznamy, że osiągnięty rezultat już nas
zadowala.
Opiszemy niżej bardzo prostą wersję takiego algorytmu, który jednak
został rzeczywiście zrealizowany dla potrzeb niniejszego wykładu, a niżej
zobaczymy prawdziwe wyniki jednego z jego wykonań.
Przebieg tego algorytmu jest schematycznie pokazany na rysunku 5.7.
Najpierw tworzy się populację początkową. Niech u nas liczy ona np.
200 osobników. Wszystkie geny u każdego z osobników populacji począt-
kowej wypełnijmy całkowicie przypadkowymi wartościami, uzyskanymi
z generatora liczb pseudolosowych.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

108 5. Algorytmy probabilistyczne i ewolucyjne

Rys. 5.7. Schemat algorytmu ewolucyjnego

Następne kroki algorytmu polegają na obliczeniu dla każdego z 200 osob-


ników wartości jego współczynnika dostosowania (czyli ϕ(v)), a potem
uporządkowaniu (sortowaniu) osobników w kolejności od najmniejszych
do największych wartości ϕ(v). W ten sposób otrzymamy listę wszystkich
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 109

200 osobników, uporządkowaną tak, że osobnik (wektor v) najlepiej dosto-


sowany (czyli o najmniejszej wartości ϕ(v)) będzie na pierwszym miejscu,
a najgorzej dostosowany (o największej wartości ϕ(v)) znajdzie się na sza-
rym końcu, na pozycji 200.
W tym miejscu jest czas na to, by zdecydować, czy kontynuować
proces ewolucji. Można sobie wyobrazić wiele możliwych warunków za-
kończenia, na przykład taki, że najlepszy z osobników ma już zadowalają-
cy poziom dostosowania albo że upłynęła już założona liczba pokoleń itd.
W każdym razie, jeśli taki warunek zakończenia jest spełniony – następuje
edycja wyników: pokazanie najlepszego zestawu parametrów, sporządzanie
odpowiednich wykresów itd.
Znacznie ciekawsze rzeczy dzieją się jednak wtedy, kiedy ewolucja
ma przebiegać dalej. Następuje wtedy bolesny, ale nieuchronny krok: trze-
ba uśmiercić gorszą część populacji. Przyjmijmy, że u nas zagładzie ulega
gorzej dostosowana połowa populacji, a więc te osobniki, które na uporząd-
kowanej przed chwilą liście mają numery od v101 do v200. Zostają one zli-
kwidowane, a przy życiu pozostaje tylko pierwsza setka osobników.
Osobniki od v1 do v100, a więc te, którym udało się przeżyć – zabierają
się teraz ochoczo do produkowania potomstwa, czyli do rozmnażania się.
W procesie rozmnażania biorą udział dwa mechanizmy: krzyżowania
i mutacji. Krzyżowanie rozpoczyna się od tego, że osobniki, które przeżyły
– dobierają się w pary. Proces ten zasymulujemy losowaniem pięćdziesię-
ciu par liczb losowych, naturalnych, z przedziału [1, 100]. Losowanie jest
przeprowadzone bez zwracania, to znaczy tak, że raz wylosowana liczba nie
bierze udziału w następnych losowaniach. W ten sposób otrzymujemy pięć-
dziesiąt różnych par. Każda para będzie za chwilę miała dwoje dzieci.
Powiedzmy dla przykładu, że jedna z takich par zawiera liczby 45
oraz 68. Znaczy to, że osobniki (wektory) v45 i v68 na liście populacji będą
za chwilę rodzicami. Dla takiej pary losowo wybierzemy pięć numerów ge-
nów (a więc parametrów poszukiwanej krzywej). Oczywiście, będą to loso-
wane (bez zwracania) liczby naturalne z przedziału [1, 10]. Przypuśćmy,
że otrzymaliśmy w ten sposób liczby 1, 3, 4, 7, 8. Teraz następuje właści-
we krzyżowanie, którego zasada jest pokazana na rysunku 5.8. Pierwsze
dziecko – to nowy wektor, który otrzymuje od „ojca” (z wektora v45) jego
geny o numerach 1, 3, 4, 7, 8, podczas gdy pozostałe geny (2, 5, 6, 9, 10) są
kopiowane od „matki”, to znaczy z wektora v68. Drugie dziecko, odwrotnie,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

110 5. Algorytmy probabilistyczne i ewolucyjne

Rys. 5.8. Zasada krzyżowania

otrzymuje geny 1, 3, 4, 7, 8 od matki (wektora v68), zaś geny 2, 5, 6, 9, 10


– od ojca (wektora v45).
Wykonanie opisanej procedury krzyżowania dla wszystkich 50 par
rodziców prowadzi do utworzenia 100 dzieci. Każde z nich jest trochę po-
dobne do taty, trochę do mamy, ale jest też w istotny sposób od każdego
z nich (i od rodzeństwa) różne.
Dodatkowe zmiany wprowadza jeszcze proces mutacji. Polega ona na
losowej zmianie pewnej (niezbyt wielkiej) liczby losowo wybranych genów
w grupie nowo urodzonych dzieci.
W opisywanej wersji algorytmu przyjęto, że mutacja wybranego
genu polega na zwiększeniu jego wartości o 30% lub zmniejszeniu o 30%
1
(co jest losowane z prawdopodobieństwem 2 ), a następnie (znów z prawdo-
1
podobieństwem 2 ) zmianie znaku otrzymanej liczby (z dodatniej na ujemną
lub odwrotnie) albo zachowanie wartości bez zmiany znaku. Założono tak-
że, że mutacji podlega 5% genów. Ponieważ dzieci jest 100, a każde ma 10
genów, to mutacja dotyczy 50 spośród łącznej liczby 1000 genów. Dlatego
opisaną procedurę mutacji zastosowano pięćdziesięciokrotnie, za każdym
razem w stosunku do losowo wybranego dziecka i losowo wybranego jego
genu.
W końcu, dla każdego z dzieci oblicza się współczynnik dostosowa-
nia ϕ(v) i cała grupa 100 dzieci dołącza do rodziców. Powstaje znów popu-
lacja składająca się, jak poprzednio, z 200 osobników. To nowe pokolenie
wektorów v. Algorytm wraca (w pętli) do porządkowania wszystkich osob-
ników od nowa. Część spośród nowo urodzonych dzieci zakwalifikuje się
do pierwszej połowy, część dotychczasowych rodziców spadnie do gorszej
setki. Znowu gorsza połowa populacji zginie. Ci, co przeżyją, połączą się
w pary i wyprodukują potomstwo, pojawią się mutacje, powstanie następne
pokolenie, ...i tak dalej, i tak dalej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 111

Z każdym pokoleniem nowa populacja dzieci stwarza szansę na poja-


wienie się osobników lepszych niż przynajmniej niektórzy z pokolenia ich
rodziców. Jeśli tak istotnie jest – takie „udane” dzieci pozostaną wśród ży-
wych, wypychając z lepszej połowy część swoich poprzedników. Będą mia-
ły wtedy okazję przekazania swych genów następcom. Jeśli natomiast wśród
dzieci nie ma indywiduów lepszych niż te, które już obecnie są w lepszej
połowie – to znikną one bezpotomnie z ewolucyjnej gry. Dzięki temu każde
następne pokolenie jest przynajmniej nie gorsze niż poprzednie, a zazwy-
czaj choć odrobinę lepsze.
Choć cały proces ewolucji rozpoczyna się od wartości całkowicie
przypadkowych, a reguły doboru par, przekazywania genów i mutacji nie
mają nic wspólnego ani z zadanymi punktami, ani z trzema sinusami i stałą
– to rezultat robi wrażenie.
Popatrzmy na wyniki jednego z rzeczywistych wykonań opisanego
prostego algorytmu. Na rysunku 5.9 pokazane są dwie krzywe. Jedna, krop-
kowana, odpowiada najlepszemu osobnikowi po 20 zaledwie pokoleniach,
druga, ciągła – najlepszemu osobnikowi po 500 pokoleniach. Druga krzywa
jest nawet „na oko” znacznie lepiej dostosowana do układu zadanych punk-
tów. Nie ma wątpliwości, że to działa.

Rys. 5.9. Najlepsze krzywe po 20 i 500 pokoleniach ewolucji


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

112 5. Algorytmy probabilistyczne i ewolucyjne

Żeby jednak nie pozostawać tylko przy subiektywnej ocenie – w każ-


dym pokoleniu była obliczana średnia wartość współczynnika dostosowania
ϕ(v) w lepszej („przeżywającej”) połowie każdej populacji. Na rysunku 5.10
widzimy przebieg tej wartości średniej w kolejnych pokoleniach całego eks-
perymentu. Widać, że najbardziej spektakularny efekt spadku uśrednionej
wartości ϕ(v) (a więc szybka poprawa średniego dostosowania przeżywają-
cej części populacji) ma miejsce w pierwszych kilkudziesięciu pokoleniach.
Potem przebiega wolniej, jednak nawet po kilku setkach pokoleń ewolucja
wciąż powoduje przenikanie do lepszej części populacji nowych, lepszych
osobników i eliminowanie gorszych, co skutkuje stopniowym zwiększaniem
poziomu dostosowania całej populacji.

Rys. 5.10. Przebieg średniego współczynnika dostosowania

Co jednak spotkało tę populację po pięciuset pokoleniach? Na rysun-


ku 5.10 widzimy, że zaszło tam jakieś dramatyczne wydarzenie, które zakłó-
ciło dotychczasowy, spokojny proces ewolucji.
Istotnie, po 500 pokoleniach, kiedy już uzyskano dość dobry poziom
dostosowania (zilustrowany na rysunku 5.9) – przeprowadzono okrutny eks-
peryment. Polegał on na całkowitej zmianie dotychczasowego układu zada-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 113

nych punktów. Punkty na płaszczyźnie zostały zadane zupełnie od nowa.


Nagle, najlepiej dostosowane osobniki straciły grunt pod nogami: okazało
się, że nie pasują już do nowych warunków. To dla nich katastrofa. One
zapewne zginą, ale jak się zachowa cała populacja?
Przebieg wykresu średniego dostosowania pokazuje, że proces ewo-
lucji poradził sobie z kataklizmem. W pierwszej chwili następuje nagły
skok wartości współczynnika w górę: cała populacja jest zaskoczona nagłą
zmianą. Jednak mechanizm ewolucji natychmiast zaczyna działać i już po
dalszych kilkudziesięciu pokoleniach widać, że populacja znów stopniowo
dostosowuje się do zupełnie nowych warunków.
W opisywanym algorytmie notowano także dodatkowo umowny śred-
ni wiek osobników w przeżywającej połowie populacji. Każdego z osobni-
ków wyposażono w licznik, który w momencie utworzenia osobnika miał
wartość 1, a następnie był zwiększany przy każdym obiegu pętli algorytmu
o jeden dla wszystkich osobników z pierwszej setki. W ten sposób każdy
osobnik, któremu udało się przeżyć w pierwszej setce przez np. 50 pokoleń
– miał wiek równy 50 itd. Nowe potomstwo, któremu udało się dostać po
raz pierwszy do przeżywającej połowy populacji – wchodziło tam z wie-
kiem równym 1, wypychając stamtąd jakieś starsze osobniki. Oczywiście,
im więcej nowych osobników znalazło się w danym pokoleniu w lepszej
połowie – tym wartość średnia była niższa. Przebieg tej wartości średniej,
pokazany na rysunku 5.11, jest bardzo pouczający.
Widzimy, że po 500 pokoleniach, w momencie, gdy „warunki byto-
wania” populacji osobników nagle i radykalnie się zmieniły – średni wiek
populacji raptownie spadł prawie do zera. Znaczy to, że gwałtownie wymar-
ły prawie wszystkie dobrze dostosowane osobniki, które już od dłuższego
czasu tworzyły elitę populacji. Przeżyły natomiast osobniki młode, nowe,
też początkowo nie za dobrze dostosowane (na co wskazuje nagły skok
w górę średniej wartości współczynnika dostosowania na rysunku 5.10).
Szybko jednak poradziły one sobie z nowymi warunkami, i to nawet szyb-
ciej niż na samym początku eksperymentu, który przecież rozpoczynał się
od zupełnie losowych wartości wszystkich genów.
Czy nie nasuwa to nam skojarzenia z tym, co spotkało życie na Ziemi
około 65 milionów lat temu, kiedy w naszą planetę uderzyła asteroida, po-
wodując kataklizm na globalną skalę?
Powtórzenie eksperymentu od nowa, nawet dla tego samego układu
zadanych punktów, da efekt jakościowo podobny, ale jednak inny (jeśli,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

114 5. Algorytmy probabilistyczne i ewolucyjne

Rys. 5.11. Przebieg średniego wieku w przeżywającej części populacji

oczywiście, nie zainicjujemy generatora liczb pseudolosowych dokładnie


takim samym ziarnem). Nałożenie się tysięcy losowych decyzji prowadzi
do nieco innego układu wartości parametrów, a więc nieco innego kształtu
krzywej. Niekiedy proces optymalizacji grzęźnie na długie pokolenia w za-
ułku przestrzeni wartości parametrów, aż w końcu jakaś szczęśliwa mutacja
powoduje skok ewolucyjny, prowadzący w ciągu zaledwie kilku pokoleń do
znaczącej zmiany na pozycji lidera. Niemniej, sama metoda jest skuteczna
i uniwersalna.
Dopasowywanie kształtu krzywej do zbioru punktów na płaszczyźnie
było jedynie pretekstem do poglądowego opisania samej zasady działania
algorytmu ewolucyjnego. Podobną ewolucyjną metodą można jednak posłu-
żyć się w wielu innych przypadkach, gdzie rozwiązanie zależy od doboru
wartości bardzo licznych parametrów. Generalna zasada działania algorytmu
pozostaje dokładnie taka sama, a w każdym przypadku kluczem do sukcesu
jest odpowiednie, właściwe dla warunków zadania zdefiniowanie budowy
osobnika i jego genów, a przede wszystkim – sformułowanie kryterium do-
stosowania, takiego, jakim w powyższym przykładzie był współczynnik ϕ.
Resztę załatwi mechanizm krzyżowania i mutacji, a także bezlitosna reguła
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 115

przeżycia, dopuszczająca do rozmnażania jedynie osobniki lepiej dostoso-


wane.
Weźmy na przykład projektowanie kształtu kadłuba statku lub nad-
wozia samochodowego. Nadwozie takie składa się z wielu płaszczyzn i po-
wierzchni krzywych o różnych rozmiarach, promieniach krzywizny, kątach
nachylenia w stosunku do kierunku jazdy itp. Optymalizacja mogłaby pole-
gać na takim doborze wszystkich tych wymiarów, kątów itd., by współczyn-
nik oporu aerodynamicznego był możliwie najmniejszy, a jednocześnie – by
pojemność bryły samochodu nie była absurdalnie mała. W przypadku innej
konstrukcji mechanicznej czy budowlanej optymalizacja może dotyczyć np.
takiego doboru przekrojów zastosowanych elementów, by ciężar projekto-
wanej konstrukcji był możliwie mały, ale przy zachowanym warunku odpo-
wiedniej wytrzymałości itd.
Zastosowania nie ograniczają się oczywiście do projektowania inży-
nierskiego. Dla przykładu, przy planowaniu kampanii reklamowej powstaje
pytanie, jak rozdzielić przeznaczone na ten cel środki tak, by nie przekra-
czając budżetu, dotrzeć do jak największej liczby potencjalnych klientów.
Różne media (telewizja naziemna, kablowa, radio, uliczne billboardy, prasa
codzienna, kolorowe tygodniki itp.) życzą sobie różnych cen za umieszcze-
nie w nich reklam, a każde medium dociera do innej liczby i innego kręgu
odbiorców. Przedmiotem optymalizacji byłby wtedy układ kwot, które zo-
staną przeznaczone (w ramach zadanego budżetu) na poszczególne media,
a kryterium optymalizacji – na przykład liczba potencjalnych odbiorców re-
klamy uzyskana za jednostkę kosztu.
Przykłady takich praktycznych, technicznych czy ekonomicznych za-
stosowań algorytmów ewolucyjnych można by mnożyć. Jednak sam me-
chanizm ewolucji, zasady doboru partnerów, tworzenia potomstwa, mutacji,
następstwa pokoleń – też mogą być (i rzeczywiście są) źródłem ciekawych
hipotez i badań.
W naszym prymitywnym algorytmie osobniki najlepsze w danej po-
pulacji są prawie nieśmiertelne, a w każdym razie bardzo długowieczne.
Bardzo udany „lider klasyfikacji” może pozostawać wśród przeżywającej
elity przez tysiące pokoleń, dopóty, dopóki nie pojawi się stu lepszych od
niego, tak że w kolejnym pokoleniu wypadnie wreszcie poza jej granicę.
Ale co by było, gdyby każdy z osobników, niezależnie od stopnia dostoso-
wania, musiał odejść z gry po przeżyciu zaledwie czterech czy pięciu poko-
leń? Czy w takich warunkach proces nadal będzie zbieżny, to znaczy, czy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

116 5. Algorytmy probabilistyczne i ewolucyjne

populacja będzie nadal stopniowo polepszać swoje dostosowanie, czy też


nie? Generalnie, szansa na szybką zbieżność procesu ewolucyjnego jest tym
większa, im więcej dzieci powstaje w każdym pokoleniu. Ile dzieci musia-
łoby produkować każde pokolenie, by zapewnić zbieżność procesu przy
ograniczonym czasie życia osobnika?
A sam mechanizm krzyżowania: dlaczego zakładać, że dzieci dzie-
dziczą akurat jedną, wylosowaną połowę genów od każdego z rodziców?
Może jedne dzieci powinny dziedziczyć jeden gen, inne – dwa, inne – trzy,
cztery, pięć od jednego z rodziców, a pozostałe geny od drugiego z nich?
Możliwości jest mnóstwo. A czy nie powinno być tak, że lepsze osobniki
mają więcej dzieci, a gorsze – mniej? Albo właśnie odwrotnie? A czy nie
mogłoby być tak, że im lepszy w danym pokoleniu jest dany osobnik, to
tym więcej partnerów (cały harem?) może sobie wybrać do produkowania
potomstwa? Czy algorytm działałby sprawniej, gdyby członkowie górnych
warstw elity krzyżowali się głównie między sobą, czy też powinni oni ra-
czej dobierać sobie partnerów „z dołu” populacji?
Tego typu pomysły stanowią punkt wyjścia dla bardzo interesujących
prac nad komputerową symulacją ewolucyjnego rozwoju sztucznego życia
(ang. artificial life, a-life). Jest to obecnie cała spora dziedzina badań.
Na koniec trudno uciec od kilku bardzo zasadniczych pytań, które na-
suwają się w sposób nieuchronny. Wiadomo, że od czasów Karola Darwina
trwa spór zwolenników teorii ewolucji z kreacjonistami. Ortodoksyjni kre-
acjoniści twierdzą, że świat powstał w wyniku jednorazowego aktu stwo-
rzenia, od razu taki, jakim go widzimy obecnie, i że ewolucjoniści nie tylko
się mylą (bo żadnej ewolucji nie ma), ale wręcz usiłują „pozbyć się Boga”
z ludzkiego światopoglądu, zastępując Stwórcę działaniem ślepego przypad-
ku. Czy algorytmy ewolucyjne dostarczają argumentów którejś ze stron tego
sporu?
Przede wszystkim trzeba zachować pokorę. W przyrodzie wszystkie
zjawiska są nieskończenie bardziej złożone i bogatsze niż w naszym prymi-
tywnym przykładzie. Geny istot żywych mało przypominają wektory zło-
żone z marnych kilku liczb. Liczności populacji poszczególnych gatunków
są często rzędu miliardów i nie są stałe, lecz zmieniają się z pokolenia na
pokolenie. Są gatunki, których osobniki w czasie swojego życia produkują
dziesiątki i setki tysięcy potomków. Jednocześnie istnieją mechanizmy sa-
moregulacji, utrzymujące liczność każdej populacji w pewnych granicach.
My mieliśmy do czynienia z jednym „gatunkiem” osobników, w przyrodzie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algorytmy ewolucyjne 117

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

6. Obliczenia analogowe i cyfrowe

Czy obliczenie musi się wykonywać w jakichś


krokach?
Dotychczas zakładaliśmy milcząco, że rozwiązania problemów, które przed
sobą stawialiśmy, mają polegać na wykonaniu szeregu pewnych elemen-
tarnych kroków czy działań. Poznaliśmy kilka algorytmów i omówiliśmy
kłopoty ze złożonością obliczeniową, które brały się stąd, że niekiedy liczba
tych kroków była zbyt duża. Istnienie sekwencji kroków przyjmowaliśmy
jako rzecz oczywistą. Jednak, gdy się przez chwilę zastanowić, powstają
dwie ważne wątpliwości.
Po pierwsze, czy można rozwiązać przynajmniej niektóre problemy
obliczeniowe w inny sposób, to znaczy nie uciekając się do wykonania se-
kwencji elementarnych kroków? Odpowiedź brzmi: tak, można. Bez kroków
obywają się obliczenia analogowe. Niniejszy rozdział jest właśnie głównie
temu poświęcony. Wyjaśnimy w nim zasadę obliczeń analogowych i cyfro-
wych i omówimy różnice między nimi. W następnym, siódmym rozdziale,
omówimy problemy, jakie wynikają na styku świata analogowego i cyfro-
wego, a także powiemy o zasadach cyfrowego przetwarzania analogowych
sygnałów.
To będzie odpowiedź na pierwsze pytanie.
Po drugie, jeśli już nawet zgodzimy się na krokowe wykonywanie
obliczeń, to jakie kroki wolno nam uznać za elementarne? To też wcale nie
jest jasne. Przypomnijmy sobie choćby problem komiwojażera. Czy można
algorytm jego rozwiązania przedstawić w następującej postaci:
1. Start;
2. Rozwiąż problem komiwojażera;
3. Stop;
Gdybyśmy tak sformułowali algorytm, to nie mielibyśmy szansy na
zauważenie, że jego złożoność obliczeniowa jest O(N!), że dla większych
N jest on w praktyce niewykonalny i tak dalej. Czujemy, że tak algorytmu
formułować nie wolno. Ale dlaczego nie odczuwamy podobnych wątpliwo-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Analogowe i cyfrowe techniki przetwarzania informacji 119

ści, pisząc „ i := i + 1” albo „dodaj a do b i wynik zapisz w c”. Dlaczego


„dodaj” wolno napisać, a „rozwiąż problem komiwojażera” – nie? Z jakich
więc operacji można składać wszelkie algorytmy? Czy można wskazać ze-
staw zupełnie najprostszych, naprawdę elementarnych czynności oblicze-
niowych, z których tworzyłoby się bardziej złożone operacje?
Próba odpowiedzi na to pytanie doprowadzi nas w kolejnym, ósmym
rozdziale do pięknego w swojej prostocie pomysłu: maszyny Turinga i waż-
nego naukowego rezultatu: hipotezy Churcha–Turinga.

Analogowe i cyfrowe techniki przetwarzania


informacji
Na przestrzeni ostatnich kilku tysiącleci ludzie wynaleźli ogromną liczbę
sposobów rejestrowania, przechowywania, przesyłania i przetwarzania in-
formacji. Różne pomysłowe techniki i urządzenia powstawały niezależnie
od siebie, w różnych krajach i kręgach kulturowych, a potem były ulepszane
i doskonalone przez wieki. Stąd bierze się ich wielka różnorodność, utrud-
niająca dostrzeżenie pewnych wspólnych cech czy właściwości.
Dziś jednak potrafimy powiedzieć, że wszystkie te metody i urządze-
nia dzielą się w istocie na jedynie dwie podstawowe grupy: technik analo-
gowych i cyfrowych. Pierwsze bywają też nazywane ciągłymi lub proporcjo-
nalnymi, drugie – nieciągłymi lub dyskretnymi.
Współczesna informatyka to dziś prawie wyłącznie domena tech-
nik cyfrowych. Ale nie zawsze tak było. Dziś, gdy mówimy „komputer”
– mamy z zasady na myśli maszynę ze swojej natury cyfrową. Jednak jesz-
cze czterdzieści lat temu były maszyny cyfrowe (ang. digital computers)
i maszyny analogowe (ang. analog computers). Wszyscy wiemy, że i dziś
istnieje analogowa i cyfrowa telefonia, są analogowe i cyfrowe nagrania
dźwiękowe, analogowe i cyfrowe kamery fotograficzne, jest analogowa
i cyfrowa telewizja. Mamy też wrażenie, że to, co analogowe, przechodzi
właśnie na naszych oczach do historii, wypierane stopniowo przez cyfrową
konkurencję. Na czym oba te sposoby przetwarzania polegają i skąd się bie-
rze domniemana wyższość technik cyfrowych nad analogowymi?
Techniki analogowe dotyczą rejestrowania i przetwarzania informacji
o wielkościach fizycznych, takich jak długość, masa, prędkość, ciśnienie,
temperatura itd., które mogą się zmieniać w sposób ciągły, płynny (nie zaś
skokowy). Ponieważ te fizyczne wielkości najczęściej trudno zapisać i trud-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

120 6. Obliczenia analogowe i cyfrowe

no nimi operować wprost – wyszukujemy inną fizyczną wielkość, również


ciągłą, która będzie odpowiednikiem (czyli właśnie analogiem) danej ory-
ginalnej zmiennej, z jakichś względów wygodniejszym do rejestrowania
i przetwarzania. Jednocześnie trzeba zapewnić, by analog zmieniał się pro-
porcjonalnie do oryginału: jeśli na przykład oryginalna wielkość wzrośnie
dwa razy – to jej analog powinien również dwukrotnie wzrosnąć.
Wiele przykładów podamy niżej, ale na początek jeden, oczywisty
(rysunek 6.1). Fizycznie rzecz biorąc, ludzki głos czy dźwięk instrumentu
muzycznego – to szybkie zmiany ciśnienia akustycznego. Nie rozchodzą
się one daleko: trudno byłoby je usłyszeć z odległości wielu kilometrów.
Za pomocą mikrofonu można je jednak przetworzyć na wygodny analog,
a mianowicie zmienne napięcie elektryczne, proporcjonalne (przynajmniej
w zasadzie) do natężenia dźwięku, a następnie przekazać je dalej, posłu-
gując się parą miedzianych przewodów. Jeśli się dodatkowo dysponuje
wzmacniaczem – można na bieżąco mnożyć (analogowo) chwilowe war-
tości napięcia przez stały współczynnik, a po stronie odbiorcy, za pomocą
równie proporcjonalnego odwrotnego przetwornika – głośnika – odtworzyć
słuchaczowi pierwotną, oryginalną zmienną.

Rys. 6.1. Analogowe przesyłanie dźwięku

Cyfrowe przetwarzanie informacji opiera się na zupełnie innej zasadzie. Po


pierwsze, dla danego cyfrowego sposobu reprezentowania danych musi być
wybrany i ustalony pewien alfabet. Alfabetem może być dowolny niepusty
i koniecznie skończony zbiór symboli. Przyjmujemy, że symbole alfabetu
są elementarne i niepodzielne, to znaczy, że nie składają się już z żadnych
mniejszych części.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Analogowe i cyfrowe techniki przetwarzania informacji 121

My będziemy zakładali również, że alfabet musi zawierać co najmniej


dwa symbole. Teoretycznie niektórzy rozważają również alfabety jednoelemen-
towe, ale w praktyce, w ciągu złożonym z wielokrotnie powtarzanego jedynego
symbolu jeden symbol musi i tak czymś się odróżniać od drugiego. Sprowadza
się to zwykle do ukrytego założenia, że między symbolami występuje jakaś
kreska czy przerwa, choćby bardzo krótkotrwała. Tak czy inaczej, alfabet ma
więc i tak dwa elementy: symbol i jego brak.
Wszelkie dane podlegające rejestracji, przesyłaniu, przetwarzaniu itd.
w sposób cyfrowy – mają postać skończonych ciągów (sekwencji) złożo-
nych wyłącznie z symboli przyjętego alfabetu. Dla każdego rodzaju danych
muszą być także dobrze określone reguły budowania poprawnych sekwen-
cji. Zbiór tych reguł stanowi gramatykę danego sposobu reprezentowania
informacji. Czy użycie w tym kontekście terminu „gramatyka” nie jest prze-
sadą? Nie, nie jest. W rozdziale 9 powiemy o tym znacznie więcej.
Dla poprawnie zbudowanych sekwencji możemy definiować różne al-
gorytmy ich przetwarzania. Ponieważ operują one na sekwencjach symboli
– ich wykonanie zawsze jest też sekwencją jakichś kroków, co zabiera czas.
Na tym właśnie polega jedna z podstawowych różnic między obliczeniami
analogowymi i cyfrowymi.
Dane cyfrowe mają więc charakter abstrakcyjnych ciągów umow-
nych symboli, które nie znaczą nic, dopóki nie określimy ich semantyki,
czyli mówiąc prościej – nie przypiszemy im znaczenia. Na przykład, za-
leżnie od tego, jak my tę sekwencję rozumiemy, ciąg znaków 234598755
może być liczbą, numerem telefonu albo kodem jakiegoś towaru. Komputer
i tak nie rozumie cyfrowych danych, które przetwarza. Zapisuje tylko owe
symbole, przestawia je, kompletuje z nich nowe sekwencje itd., zgodnie
z regułami, które my sami określamy, budując jego sprzęt i oprogramo-
wanie. Interpretacja zarówno danych wejściowych, jak i wyników należy
do nas.
Wszelkie ciągłe zmienne, które nadają się do analogowego przetwa-
rzania, potrafimy także przekształcić na postać cyfrową i przetwarzać je,
wykorzystując algorytmy działania na liczbach. Jednak w drugą stronę to
nie działa. Informacji tekstowej, numeru telefonu, nazwiska czy kodu towa-
ru nie przedstawimy w postaci analogowej. To sprawia, że cyfrowy kom-
puter jest narzędziem znacznie bardziej uniwersalnym niż każde urządzenie
analogowe.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

122 6. Obliczenia analogowe i cyfrowe

Na co dzień posługujemy się niezliczoną ilością cyfrowych sposobów


reprezentowania i przetwarzania informacji, a więc także niezliczoną ilością
alfabetów i gramatyk. Mimo to (a może właśnie dlatego) wokół tych kilku
podstawowych terminów narosło mnóstwo nieporozumień i niejasności.
Po pierwsze, termin „cyfrowy” nie oznacza wcale, że mamy do czy-
nienia wyłącznie z potocznie rozumianymi cyframi: 0, 1, 2, 3, 4... itd., choć
zbiór dziesięciu cyfr arabskich może być jednym z alfabetów. Podobnie, nie
chodzi wcale o cyfry 0 i 1, choć tworzą one technicznie najwygodniejszy
(bo najmniejszy z możliwych) alfabet. Słowo cyfrowy jest w tym kontekście
używane w znaczeniu nieciągły, skokowy, dyskretny, dla wyraźnego odróż-
nienia od reprezentacji analogowej, gdzie przetwarzane wielkości mogły
zmieniać się w sposób ciągły, przyjmując nieskończenie wiele możliwych
wartości, leżących nieskończenie blisko siebie. Założenie o skończonej na-
turze alfabetu i skończonych długościach sekwencji od razu taką ciągłość
wyklucza.
Po drugie, alfabet – to niekoniecznie tylko znane nam abecadło.
Jako alfabet może być przyjęty dowolny skończony, co najmniej dwuele-
mentowy zbiór elementarnych symboli. Oczywiście, szkolne abecadło też
spełnia te warunki. W języku polskim składa się ono łącznie z kilkudzie-
sięciu symboli: liter małych i wielkich, spacji i kilkunastu znaków prze-
stankowych, które poznajemy w pierwszej klasie. Nauczyliśmy się składać
z nich różne ciągi: wyrazy, zdania, wypowiedzi, wiersze, książki…, ale
także liczby, tablice liczb, instrukcje, programy komputerowe itd. Inne
narody mają swoje alfabety: cyrylicę, alfabet grecki, arabski, hebrajski,
gruziński i tak dalej.
Ale całkowicie „legalnym” alfabetem jest też na przykład zbiór trzech
świateł ulicznego sygnalizatora ruchu: czerwone, żółte i zielone. Przykładami
alfabetów są także: zbiór symboli szachowych, karcianych, symboli używa-
nych do zapisywania liczb rzymskich, stopni wojskowych, tekstów pisma
klinowego i tak dalej. Co więcej, możemy sami wymyślić własny alfabet,
który stanie się podstawą nowego sposobu zapisywania informacji, byleby
tylko zawierał on liczbę symboli skończoną i nie mniejszą niż dwa.
Nie odbiegajmy jednak od tematu. O różnych alfabetach, gramaty-
kach i cyfrowych zapisach będziemy jeszcze mówić w dalszej części książ-
ki. Wróćmy do obliczeń analogowych, którym poświęcamy uwagę jedynie
w tym rozdziale.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Domowe i szkolne przykłady obliczeń analogowych 123

Domowe i szkolne przykłady obliczeń analogowych


Obliczeniami analogowymi posługujemy się na co dzień, nie zauważając
tego zgoła, podobnie, jak molierowski pan Jourdain, który nie wiedział, że
mówi prozą.
Oto wieszając obrazek, chcemy wyznaczyć środek szerokości ściany.
Możemy to zrobić w sposób analogowy albo cyfrowy. W pierwszym przy-
padku bierzemy sznurek, rozciągamy na całą szerokość ściany, a następnie
składamy go na pół i przystępujemy do zaznaczania punktu, gdzie trzeba wbić
gwóźdź. Szerokość ściany jest tą zmienną ciągłą, której połowę chcemy wy-
znaczyć. Długość sznurka jest jej analogiem (w skali 1:1), a składanie na pół
jest analogowym dzieleniem przez dwa. Posłużyliśmy się tu analogiem, po-
nieważ samą ścianę dość trudno jest złożyć na pół.
Wykonując to samo zadanie w sposób cyfrowy, zapewne zdecydo-
walibyśmy się na przedstawienie szerokości ściany z postaci ciągu złożo-
nego z kilku cyfr. Posłużylibyśmy się miarką i – zaokrąglając do pełnych
centymetrów – zapisalibyśmy wynik, na przykład 364. Jest to poprawna,
trzyznakowa sekwencja, zgodna z regułami zapisywania liczb naturalnych.
Istnieje dla niej algorytm dzielenia (w szczególności przez 2), w wyniku
którego, po kilku krokach, otrzymamy nową sekwencję: 182. Odmierzymy
ją, zaznaczymy… i tak dalej.
Oto młoda mama chce kupić swojemu rocznemu synkowi buciki.
Obrysowuje mu stopę na papierze, kartkę chowa w portfelu i udaje się do
sklepu, pozostawiając dziecko pod opieką babci. Tam porównuje obrys nóż-
ki z kilkoma parami bucików i wybiera najodpowiedniejsze. To typowe ob-
liczenie analogowe. Oryginałem jest w tym przypadku stopa dziecka, ana-
logiem – jej obrys na papierze, współczynnik proporcjonalności – 1:1, ob-
liczenie zaś polega na wykonaniu szeregu porównań. Zgodzimy się, że taki
analog jest łatwiejszy do przenoszenia, manipulowania i porównywania niż
oryginał. W przeciwieństwie do oryginału, można go nawet przesłać pocztą
do Australii, do cioci, która zechciałaby zrobić dziecku prezent.
Oto mamy w ręce raport ekonomiczny, analizujący np. wydoby-
cie węgla w Polsce na przestrzeni ostatnich 20 lat. Jest tam zamieszczony
rysunek ze słupkami, których wysokość obrazuje ilość węgla wydobyte-
go w poszczególnych latach. To też analogowy sposób rejestracji danych.
Oryginałem jest masa wydobytego w danym roku węgla, jego analogiem
– wysokość słupka. Określony jest też z pewnością współczynnik propor-
cjonalności – np. 1 milimetr słupka odpowiada dwóm milionom ton węgla.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

124 6. Obliczenia analogowe i cyfrowe

Dzięki temu możemy łatwo (choć z ograniczoną dokładnością) zorientować


się, czy wzrosło, czy zmalało i o ile.
Oto robiąc ciasto, odważamy pół kilograma mąki na tradycyjnej szal-
kowej wadze. To również analogowy przyrząd pomiarowo-obliczeniowy.
Oblicza on różnicę masy dwóch przedmiotów i odwzorowuje ją na jej ła-
twy do obserwowania analog: kąt odchylenia w stosunku do poziomu. Na
podobnej zasadzie działa szalkowa waga dziesiętna (używana do ważenia
cięższych przedmiotów), która dodatkowo jeszcze mnoży (również analo-
gowo, dzięki zasadzie dźwigni) jedną z tych mas przez 10.
Oto zainteresowało nas, czy z Warszawy jest bliżej (w linii prostej) do
Londynu, czy do Helsinek. Moglibyśmy próbować to rozstrzygnąć „w ory-
ginale” (pieszo? wpław?), ale lepiej posłużyć się analogiem, czyli mapą
Europy (np. w skali 1:100 000) oraz cyrklem. A jaka jest różnica między
tymi odległościami? Trzeba odłożyć oba odcinki na jednej prostej, tak by
miały jeden punkt wspólny, odległość między dwoma pozostałymi końcami
ująć w cyrkiel i przenieść na miarkę, umieszczoną zazwyczaj gdzieś przy
opisie mapy. Miarka ma postać odcinka z podziałką, opisaną od razu w ki-
lometrach. Wykonujemy w ten sposób analogowe operacje odejmowania,
a dzięki znajomości skali możemy odczytać rezultat w jednostkach orygina-
łu: w kilometrach, zamiast w milimetrach.
Taka operacja na mapie jest doprawdy bardzo prosta w porównaniu
do podobnych, lecz znacznie bardziej skomplikowanych analogowych ob-
liczeń, jakie od stuleci rutynowo wykonywali nawigatorzy statków, posłu-
gując się mapami i zestawem prostych narzędzi: liniałem, trójkątem nawi-
gacyjnym, cyrklem, kątomierzem, na podstawie pomiarów pochodzących
z równie analogowych przyrządów nawigacyjnych. Każda mapa, globus,
plan miasta itd. – jest pewnym analogowym modelem.
Sami poznajemy w szkole, na lekcjach geometrii, podstawowe sztucz-
ki tego rodzaju: podział odcinka na n równych części przy użyciu linijki,
ekierki i cyrkla (twierdzenie Talesa!), kreślenie kąta prostego, linii prostopa-
dłej i równoległej do danej prostej, prostej stycznej do okręgu itd. Podobnie,
w sposób analogowy rozwiązujemy układ dwóch równań liniowych, kreśląc
na papierze milimetrowym dwie odpowiednie proste i odczytując współ-
rzędne punktu ich przecięcia.
Mówiąc o analogowych technikach obliczeniowych, nie można nie
złożyć hołdu urządzeniu, które odegrało ogromną rolę w rozwoju wszyst-
kich dziedzin techniki. Mowa tu o suwaku logarytmicznym (ang. slide rule).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Domowe i szkolne przykłady obliczeń analogowych 125

Przez około 200 lat od momentu jego wynalezienia (William Oughtred,


1632), suwak logarytmiczny był raczej techniczno-matematyczną cieka-
wostką, stopniowo ulepszaną i udoskonalaną. Jednak już od połowy XIX
wieku, a później także w XX wieku, suwak był podstawowym narzędziem
absolutnie każdego inżyniera, każdej specjalności, równie niezbędnym i co-
dziennym, jak papier i ołówek.
Głównym przeznaczeniem suwaka było wykonywanie operacji mno-
żenia i dzielenia przez dodawanie i odejmowanie odcinków. Przy mnożeniu,
wartość jednej liczby odszukiwało się na linijce, wyskalowanej nie liniowo
(tzn. nie jak w miarce krawieckiej, gdzie np. liczbie 50 odpowiada odci-
nek dwa razy dłuższy niż liczbie 25), lecz w skali logarytmicznej. Wartość
drugiej z liczb ustalało się na drugiej, też logarytmicznie wyskalowanej li-
nijce, a następnie przesuwając linijki względem siebie, dodawało się oba
odcinki. Otrzymana suma logarytmów jest, jak wiadomo, logarytmem ilo-
czynu. Wynik ten łatwo było można odczytać na tejże logarytmicznej skali.
Odpowiednio odejmując odcinki, można było otrzymać iloraz dwóch liczb,
a dzieląc 1 przez liczbę – jej odwrotność.
Typowy suwak miał kilka takich podziałek, pomysłowo wyskalowa-
nych także na odwrocie, więc można było przy jego użyciu obliczać nie
tylko iloczyny i ilorazy, lecz również kwadraty, sześciany, pierwiastki kwa-
dratowe i sześcienne, odwrotności, mantysy logarytmów dowolnych liczb,
wartości podstawowych funkcji trygonometrycznych, a także – przy pewnej
wprawie – wykonywać mnożenie i dzielenie dowolnych par tych liczb, ich
kwadratów, sześcianów itd. W zwykłym suwaku, takim jaki inżynier miał
zawsze pod ręką – dokładność tych operacji była rzędu 0,1%, a więc całko-
wicie wystarczająca w większości praktycznych obliczeń inżynierskich. Dla
potrzeb większych biur projektowych budowano też większe, stacjonarne
suwaki, o długości przekraczającej metr lub dwa, a więc bardziej dokładnie
wyskalowane i umożliwiające odczytywanie wyników z większą precyzją.
Przez ostatnie 200 lat opracowano około dwustu typów suwaków lo-
garytmicznych: prostych, okrągłych, spiralnych, dużych, małych i kieszon-
kowych, drewnianych, metalowych, bambusowych i plastikowych. Im to
w dużej mierze zawdzięczamy dziewiętnastowieczne koleje, tunele, mosty
i transatlantyki, dwudziestowieczne samochody, drapacze chmur, samoloty
(aż do prototypu myśliwca F16 włącznie), a nawet rakiety kosmiczne i loty
załogowe na Księżyc. Warto wspomnieć, że statki programu Apollo (łącznie
z tym, który w lipcu 1969 roku wylądował na powierzchni Księżyca) były
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

126 6. Obliczenia analogowe i cyfrowe

też wyposażone w analogowe suwaki obliczeniowe, na wypadek awarii po-


kładowego komputera.
W latach siedemdziesiątych XX wieku, a więc po blisko 350 latach
pięknej historii, suwak logarytmiczny zaczął powoli odchodzić w cień, stop-
niowo wypierany z uczelni, szkół i biur projektowych przez cyfrowe kalku-
latory elektroniczne, a potem przez komputery. Dziś, choć już od dobrych
trzydziestu lat nie jest używany w praktyce, ma wciąż grono miłośników i ko-
lekcjonerów, łatwych do odnalezienia w Internecie.
Obok uniwersalnych suwaków do opisanych działań arytmetycznych
i trygonometrycznych, skonstruowano dziesiątki typów specjalizowanych
suwaków (niekoniecznie logarytmicznych), takich jak np. kołowy suwak
fotograficzny (nazywany tabelą naświetleń) czy suwak ginekologiczny, po-
zwalający szybko obliczyć spodziewaną datę porodu na podstawie terminów
pewnych fizjologicznych objawów u pacjentki. Podobnych przykładów, od-
wołujących się do praktyki życia codziennego, szkoły czy biura projektowe-
go, można by podać więcej.

Analogowe urządzenia w technice


W technice pojawiają się również analogowe pomysły znacznie bardziej
wyrafinowane, zwłaszcza tam, gdzie chodzi nie o pojedynczy akt pomiaru
czy jednorazowe obliczenie czegoś, lecz o szybką rejestrację, przesyłanie
i przetwarzanie ciągłych wielkości fizycznych, które dynamicznie zmieniają
się w czasie.
O starym, poczciwym analogowym telefonie, skonstruowanym
w roku 1876 przez Alexandra Grahama Bella, już mówiliśmy i zilustrowa-
liśmy zasadę jego działania na rysunku 6.1. Innym, dobrze znanym przy-
kładem jest gramofon: analogowe urządzenie do rejestracji i odtwarzania
dźwięku. Pierwotny wynalazek Thomasa Edisona, fonograf (1877), polegał
na odwzorowaniu tak ulotnego oryginału, jakim są zmiany ciśnienia aku-
stycznego – na ich mechaniczny analog: zmiany głębokości rowka, rytego
igłą na walcu pokrytym metalową folią. W grafofonie (ang. graphophone)
Alexandra Grahama Bella (tak, tego samego, który jest wynalazcą telefo-
nu) folię zastąpiono warstwą wosku. Wreszcie Emile Berliner opatentował
„właściwy” gramofon (1887), w którym walec zastąpiono płytami (metalo-
wymi, szelakowymi, a później winylowymi). Umożliwiło to masową pro-
dukcję (tłoczenie) kopii nagrań, co w rezultacie spowodowało powstanie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Analogowe urządzenia w technice 127

całego nowego przemysłu muzycznego, z wszystkimi jego kulturowymi


konsekwencjami.
Oczywiście, w praktyce sprawa nie jest wcale tak prosta: zakładana
proporcjonalność pomiędzy oryginałem a analogiem jest jedynie pożąda-
nym ideałem (niemożliwym do osiągnięcia dla szerokiego zakresu częstot-
liwości i amplitud drgań akustycznych), w torze telefonicznym (lub torze
zapisu i odczytu) występują tłumienie sygnału, zakłócenia i szum. Niemniej,
podstawą tych wynalazków była zasada analogowego odwzorowania drgań
powietrza na drgania mechaniczne lub elektryczne.
Rola elektroniki i elektronicznych urządzeń analogowych zasługuje
w ogóle na szerszy komentarz. Ich znaczenie bierze się stąd, że wielko-
ści takie, jak napięcie elektryczne, natężenie prądu, indukcja magnetyczna,
natężenie pola magnetycznego itd. – okazały się szczególnie wygodnymi
analogami dla wielu innych ciągłych wielkości fizycznych, wygodniejszymi
niż np. analogi mechaniczne.
Zjawiska elektryczne lub elektromagnetyczne zaczęto wykorzysty-
wać do celów przesyłania informacji już w pierwszej połowie XIX wieku.
Wynalazek telegrafu (Samuel Morse, 1837), kablowe transatlantyckie połą-
czenie między Europą i Ameryką (1866), potem telefon A.G. Bella – od-
mieniły świat. W ostatnich latach XIX wieku nastąpiła era „telegrafu bez
drutu”: prototypu komunikacji radiowej1. Kilka lat później wynalazek lampy
próżniowej (triody, Lee de Forest, 1907) umożliwił konstruowanie wzmac-
niaczy sygnałów elektrycznych, a także doskonalszych nadajników i odbior-
ników fal radiowych. Wiemy, co było dalej: analogowe połączenia telefo-
niczne oplotły cały świat globalną siecią, a rozwój radiofonii i – nieco póź-
niej – telewizji przyniósł głębokie zmiany całego modelu kultury masowej.
Film dźwiękowy, rejestracja dźwięku i obrazu na taśmie magnetycznej... To
wszystko zasługa analogowej elektroniki.
Nie tylko telekomunikacja i media korzystały z osiągnięć analogowej
elektroniki. Nie mniejszą rolę dla cywilizacyjnego rozwoju odegrały analogo-
we, elektroniczne systemy pomiarowe i urządzenia automatyki przemysłowej.
Z czasem opracowano tysiące typów czujników (ang. sensors), pozwalających

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

128 6. Obliczenia analogowe i cyfrowe

na przekształcanie na odpowiedni elektryczny analog temperatury, położenia,


masy, ciśnienia, wilgotności, przepływu, prędkości, jasności światła i dziesią-
tek innych ciągłych wielkości fizycznych.
Równolegle, naukowcy i inżynierowie elektronicy opracowywali co-
raz doskonalsze układy i urządzenia do wzmacniania tych sygnałów, ich wi-
zualizacji (oscyloskop, kineskop), rejestrowania, przesyłania na odległość,
a także – dodawania, odejmowania, całkowania, różniczkowania, podnosze-
nia do kwadratu itd. elektrycznych sygnałów analogowych. Otworzyło to
drogę dla budowania bardzo złożonych urządzeń, służących do sterowania
pracą wielu obiektów przemysłowych.
Łatwość przesyłania sygnałów z czujników na odległość umożliwi-
ła przede wszystkim grupowanie i wizualizację w jednym pomieszczeniu
wszystkich zmiennych istotnych dla działania całego rozległego obiektu prze-
mysłowego: rafinerii, fabryki chemicznej czy hydroelektrowni. Operatorzy
mogli z takiej sterowni nadzorować pracę obiektu i wpływać (także na od-
ległość) na położenie zaworów, ciśnienie pary ogrzewającej chemiczny re-
aktor, prędkość podawania surowca do mieszalnika czy tlenu do paleniska
itp. Do działania w drugą stronę wykorzystywano wiele typów elementów
wykonawczych (ang. actuators), w szczególności serwomechanizmów (ang.
servomotors), które przekształcały wysyłany przez operatora sygnał elek-
tryczny na proporcjonalne do jego wartości położenie, np. na ruch obrotowy
zaworu, przesunięcie przepustnicy kontrolującej przepływ gazu.
Możliwość nadzorowania na odległość pracy urządzeń i maszyn to
tylko jedna z korzyści stosowania technik analogowych w sterowaniu in-
stalacji przemysłowych. Pomysłowe analogowe urządzenia (niekoniecznie
elektryczne) są zdolne do jeszcze bardziej wyrafinowanych działań.

Analogowe układy automatycznej regulacji


Bardzo długi rodowód ma genialny analogowy wynalazek: regulator od-
środkowy (ang. centrifugal governor), przypisywany wynalazcy maszyny
parowej, Jamesowi Wattowi. Nazwisko prawdziwego wynalazcy nie jest
znane: wiadomo, że podobny regulator (ang. governor, czyli – w dawnej
angielszczyźnie – sternik) był już wcześniej stosowany w wiatrakach, do re-
gulowania odległości między kamieniami młyńskimi. W roku 1787, a więc
sto lat przed fonografem Edisona, James Watt zastosował ten wynalazek już
w pierwszych modelach swojej maszyny parowej. Istota tego pomysłu jest
pokazana schematycznie na rysunku 6.2.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Analogowe układy automatycznej regulacji 129

Rys. 6.2. Zasada działania regulatora odśrodkowego

Prędkość obrotowa głównego wału napędowego maszyny jest zamieniana


na jej analog: położenie (w pionie) pierścienia, unoszonego lub opuszcza-
nego przez ciężarki, obracające się na pionowej osi, zatem podlegające sile
odśrodkowej. Poprzez odpowiednią dźwignię, położenie pierścienia steruje
zaworem, kontrolującym przepływ pary z kotła do cylindra maszyny.
Jeśli ciśnienie w kotle wzrasta, to pary napływa więcej, tłok w cy-
lindrze porusza się szybciej, szybciej też zaczyna się obracać wał maszyny,
a wraz z nim – pionowa oś z ciężarkami. Wówczas (dzięki sile odśrodko-
wej) ciężarki unoszą się, pociągając za sobą pierścień, a to (dzięki dźwigni
łączącej pierścień z zaworem wlotowym) powoduje zmniejszenie napływu
pary. Silnik więc zwalnia, lecz wtedy ciężarki opadają, zawór uchyla się,
pary napływa więcej, silnik przyspiesza, ale nie za bardzo, gdyż ciężarki się
podnoszą... itd.
W ten sposób regulator odśrodkowy utrzymuje stosunkowo stałą pręd-
kość obrotową wału maszyny, uniezależniając ją w znacznym stopniu nie
tylko od wahań ciśnienia pary w kotle, ale również od wahań obciążenia.
Wyobraźmy sobie, że nasz silnik parowy napędza parowóz, który cią-
gnie wagony po górzystym terenie. Kiedy pociąg zaczyna jechać pod górę
– nieco zwalnia, gdyż praca, którą ma do wykonania, wzrasta. Wał napę-
dowy obraca się nieco wolniej, ciężarki opadają, zatem zawór otwiera się
szerzej, do cylindra napływa więcej pary, wał obraca się nieco szybciej...
– właśnie po to, by prędkość obrotowa nie spadała, lecz utrzymała się na
poprzednim poziomie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

130 6. Obliczenia analogowe i cyfrowe

Mówiąc dzisiejszym językiem, odśrodkowy governor Watta jest przy-


kładem mechanicznej, analogowej realizacji ujemnego sprzężenia zwrotne-
go (ang. negative feedback). Zasada sprzężenia zwrotnego polega na od-
działywaniu wstecz (czyli zwrotnie) skutku (jakim jest w tym przypadku
prędkość obrotowa) na przyczynę (napływ pary). Tworzy się w ten sposób
zamknięta pętla oddziaływań: przyczyna powoduje skutek, ale skutek też
zwrotnie wpływa na przyczynę.
Ujemny charakter sprzężenia oznacza, że zwiększenie skutku (pręd-
kości obrotowej) powoduje zmniejszenie przyczyny (napływu pary) i od-
wrotnie: gdy napływ pary maleje – urządzenie usiłuje doprowadzić do jego
zwiększenia. Prowadzi to (a przynajmniej powinno prowadzić) do stabili-
zacji zjawiska objętego pętlą ujemnego sprzężenia zwrotnego. W dobrze
działającym układzie prędkość obrotowa wału waha się nieznacznie wokół
pewnej wartości zadanej. Gdyby zarówno ciśnienie pary, jak i obciążenie
maszyny były idealnie stałe – prędkość obrotowa również wyrównałaby się
do wartości zadanej.
Warto zdać sobie sprawę, że istnieje także druga strona medalu: sprzę-
żenie zwrotne dodatnie, nieuchronnie związane z niestabilnością. W tym
przypadku, zwiększenie się skutku przyczynia się (zwrotnie) do powięk-
szenia przyczyny. Postawmy długi zaostrzony kijek na stole, pionowo, na
czubku, tak by wektor jego siły ciężkości celował dokładnie w punkt pod-
parcia. Teoretycznie, kijek powinien stać, ale praktycznie – długo tak nie
postoi: wystarczy przypadkowy podmuch powietrza lub drganie podłoża,
by przechylił się na bok choć o ułamek milimetra. Wyobraźmy sobie, jak
teraz rozłoży się wektor jego siły ciężkości: skutkiem przechylenia się jest
pojawienie się malutkiej siły składowej prostopadłej do kijka. Składowa ta
powoduje powiększenie przechylenia, czyli przyczyny jej pojawienia się.
Przechylenie powiększa się, a efektem jest tym większa składowa prosto-
padła... Pętla dodatniego sprzężenia zwrotnego zamyka się, zjawisko na-
rasta lawinowo i kijek musi się przewrócić. Ale gdybyśmy tym prętem
balansowali na palcu, kompensując jego przechylenia drobnymi ruchami
w przeciwnym kierunku (a więc działając jako ujemne sprzężenie zwrot-
ne) – potrafilibyśmy przez dłuższy czas utrzymać go w pionie.
Dodatnie sprzężenie zwrotne niekoniecznie musi powodować natych-
miastowe, lawinowe „wywrócenie się” zjawiska. Niestabilność może się ob-
jawiać także w postaci niegasnących wahań (oscylacji). Wyobraźmy sobie,
że w regulatorze odśrodkowym oś obrotu dźwigni łączącej pierścień z wlo-
towym zaworem została umieszczona zbyt daleko od zaworu, tak że już ma-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Analogowe układy automatycznej regulacji 131

lutki ruch pierścienia w górę powoduje nagłe, prawie całkowite zamknięcie


zaworu, a mały ruch w dół – jego nagłe i prawie pełne otwarcie. Mówimy,
że wzmocnienie w pętli sprzężenia zwrotnego jest w takim przypadku za
duże. Regulator działa wtedy, jak gdyby „zbyt gorliwie”, na przemian a to
dławiąc dopływ pary do zera, a to przepuszczając ją pod ciśnieniem szybko
narastającym do maksymalnej wartości.
Prędkość obrotowa wału maszyny będzie oscylować: na przemian
spadając prawie do zera i rozpędzając się do znacznej wartości. Działoby
się tak nawet wtedy, gdyby ciśnienie pary i obciążenie były stałe: wystar-
czyłoby drobne, losowe wahanie prędkości obrotowej, a układ z dodatnim
sprzężeniem zwrotnym podchwyciłby je, wzmocnił i dalej już samoistnie
generowałby niegasnące drgania.
Identyczny efekt niegasnących oscylacji pojawi się, jeśli wzmocnie-
nie może nie jest tak wielkie, lecz pomiędzy otwarciem zaworu a reakcją
regulatora występuje znaczne opóźnienie. Wyobraźmy sobie, że wchodzi-
my pod prysznic, w którym rurka łącząca krany z głowicą jest wyjątkowo
długa. Zaczyna ciec zimna woda, więc odkręcamy kurek z wodą gorącą.
Opóźnienie powoduje, że początkowo nie czujemy żadnej reakcji, odkręca-
my więc jeszcze bardziej i bardziej. W pewnym momencie zaczyna się lać
ukrop, zakręcamy więc kurek z gorącą wodą, ale reakcja jest znów opóź-
niona, więc zakręcamy dalej. Wreszcie zaczyna ciec lodowata woda… i tak
dalej, od skrajności do skrajności2.
Tak czy inaczej, niestabilność powodowana przez źle zaprojektowany
układ automatycznej regulacji jest destrukcyjna i dla samej maszyny, i dla
urządzenia, które napędza. Każde urządzenie zużywa się i psuje szybciej,
jeśli nie działa gładko, tylko szarpie się pomiędzy skrajnościami.
Zresztą, wyobraźmy sobie podróż pociągiem, którego parowóz jest
wyposażony w tak działające dodatnie sprzężenie zwrotne.
Próbę matematycznego wyjaśnienia tych zjawisk podjął jeszcze
w XIX wieku James Clerk Maxwell. Jego praca (z 1868 r.), dotycząca teorii
właśnie takich urządzeń, jak governor Watta, dała początek bardziej ogól-
nej teorii sterowania (ang. control theory). To oddzielny temat, wykracza-
jący znacznie poza ramy tej książki. Wspomnijmy jedynie, że podstawo-

2 Przychodzi na myśl Małpa w kąpieli Aleksandra Fredry.


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

132 6. Obliczenia analogowe i cyfrowe

wym matematycznym narzędziem opisu układów sterowania są równania


różniczkowe. W nich zaś wykorzystuje się – oprócz operacji dodawania,
odejmowania, mnożenia, dzielenia – także operacje różniczkowania i cał-
kowania.
Już w erze lamp próżniowych elektronika dostarczyła układów, które
umożliwiają wykonywanie tych operacji na analogowych napięciach elek-
trycznych. Okazało się, że analogowe sumowanie napięć, ich mnożenie, cał-
kowanie i różniczkowanie – dają się zrealizować dość łatwo i są wykonywa-
ne bardzo szybko, zwłaszcza jeśli się je porównuje z czasami wykonywania
algorytmów mnożenia czy całkowania przez ówczesne komputery, które
ponadto kosztowały setki tysięcy dolarów. Dzięki temu można było kon-
struować analogowe modele równań różniczkowych, łącząc ze sobą w od-
powiednią sieć wzmacniacze operacyjne, układy całkujące i różniczkujące
itd., ustalając ich parametry za pomocą potencjometrów czy kondensatorów
o zmiennej pojemności – a następnie obserwować (na przykład na ekra-
nie oscyloskopu) lub rejestrować (choćby pisakiem na papierowej taśmie)
zachowanie modelowanego układu, jego stabilność, reakcję na zewnętrzne
zakłócenia itd.
Tak właśnie działały ówczesne maszyny analogowe (ang. analog
computers), wykorzystywane w wielu działach nauki i techniki do analogo-
wego modelowania zjawisk dynamicznych, w tym między innymi do mode-
lowania zmian pogody i określania prognoz meteorologicznych.
Rozwój teorii sterowania w połączeniu z możliwościami, jakie dawała
analogowa elektronika, zaowocował licznymi praktycznymi zastosowania-
mi: w przemyśle, nauce, technice lotniczej, motoryzacji, wojsku. Aż do lat
siedemdziesiątych XX wieku, termin automatyka przemysłowa kojarzył się
prawie wyłącznie z setkami i tysiącami analogowych pętli ujemnego sprzę-
żenia zwrotnego, które dbały o regularną, stabilną pracę zarówno wielkich
przemysłowych obiektów, jak i wielu prostszych urządzeń.
Cyfrowe programowane sterowniki, minikomputery, a póź-
niej mikroprocesory i całe komputery przemysłowe zaczęły wkraczać do
automatyki w końcu lat sześćdziesiątych XX wieku, po opanowaniu maso-
wej produkcji układów scalonych. Radykalnie zmieniły się wówczas pro-
porcje między szybkością i ceną urządzeń analogowych i cyfrowych. Dla
przykładu, analogowy układ całkujący jest prosty, tani i działa bardzo szyb-
ko, podczas gdy numeryczne całkowanie np. metodą sumowania prosto-
kątów lub metodą Monte Carlo (mówiliśmy o tym w rozdziale 5) oznacza
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Cybernetyczne wizje: serwomechanizmy czy komputery? 133

zatrudnienie komputera i wykonanie algorytmu składającego się z wielu


kroków. Dopóki komputer jest stosunkowo powolny, a na dodatek kosztuje
kilkaset lub kilkadziesiąt tysięcy dolarów – nie stanowi konkurencji dla
układów analogowych. Jeśli jednak scalony procesor i półprzewodnikowa
pamięć kosztują grosze, a ich szybkość jest tak duża, że wystarcza do prze-
twarzania w czasie rzeczywistym przebiegów występujących w praktyce
– to zastosowanie cyfrowych sterowników i całych komputerów do celów
sterowania staje się celowe i opłacalne.
Co więcej, nie tylko zastępują one dawne analogowe układy auto-
matyki, ale stwarzają możliwość ogromnego rozszerzenia funkcji danego
urządzenia. Chodzi nie tylko o wykonywanie algorytmów sterowania znacz-
nie bardziej wyrafinowanych niż te, które dają się złożyć z analogowych
układów. Możliwe się staje wzbogacenie mechanizmów porozumiewania
się z użytkownikiem (interfejs użytkownika), autodiagnostyka i raportowa-
nie niesprawności, integracja z bazami danych, dostęp poprzez Internet itd.
Dlatego dziś mamy cyfrowe układy sterowania nie tylko w wielkoprzemy-
słowej automatyce, ale też w samochodach, mieszkaniach, pralkach, win-
dach i tysiącach innych miejsc, o których kilkadziesiąt lat temu nikomu się
nawet nie śniło.

Cybernetyczne wizje: serwomechanizmy czy


komputery?
Jeśli oderwać się od specyficznej konstrukcji np. maszyny parowej, popa-
trzeć na zjawisko szerzej i sformułować zasadę działania regulatora właśnie
w kategoriach zwrotnego oddziaływania między skutkami a przyczynami,
to można zauważyć, że występowanie sprzężeń zwrotnych jest zjawiskiem
niesłychanie powszechnym.
Kierując samochodem na prostej drodze, wykonujemy mały ruch kie-
rownicą w lewo, jeśli pojazd zaczyna zjeżdżać w prawo. Działamy więc jak
governor ujemnego sprzężenia zwrotnego. Jeśli jednak zareagujemy zbyt
gorliwie i wykonamy zbyt duży ruch kierownicą w lewo – będziemy za
chwilę musieli gwałtownie obrócić ją w prawo. Jeśli zastosujemy zbyt duże
wzmocnienie – nie opanujemy dodatniego sprzężenia zwrotnego, samochód
zacznie sinusoidalnie wężykować po jezdni – i nieszczęście gotowe.
Wspomnieliśmy, że ten sam skutek miałoby zbyt duże opóźnienie
między odchyleniem od linii jazdy a reakcją w postaci kompensującego ru-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

134 6. Obliczenia analogowe i cyfrowe

chu kierownicą. Alkohol właśnie wydłuża czas reakcji, dlatego samochód


prowadzony przez nietrzeźwego kierowcę porusza się torem sinusoidalnym,
co niestety często kończy się źle.
Sam nasz organizm też zawiera setki, jeśli nie tysiące układów sprzę-
żenia zwrotnego. Gdy temperatura ciała wzrasta, pocimy się, by dzięki pa-
rowaniu temperatura spadła. Gdy na skutek wysiłku wzrasta zużycie tlenu
dostarczanego do naszych mięśni – tętno i oddech przyspieszają, by ilość
dostarczanego tlenu wzrosła. To oczywiście ujemne sprzężenia zwrotne.
Takich bardzo skomplikowanych mechanizmów biologicznego sprzężenia
zwrotnego, zapewniających stabilne działanie (homeostazę) naszego ciała
można wymienić wiele. Z drugiej strony, drżenie, drgawki czy cykliczne
skurcze można interpretować jako przejaw niestabilności, a więc dodatniego
sprzężenia zwrotnego, związanego zapewne z zaburzeniem (np. opóźnie-
niem) rozchodzenia się impulsów nerwowych.
Co więcej, w podobny sposób można interpretować również wiele
zjawisk ekonomicznych i społecznych. Dla przykładu, występowanie cykli
ekonomicznych (następowanie po sobie okresów rozwoju i recesji) można
interpretować jako oscylacje spowodowane działaniem dodatniego sprzęże-
nia zwrotnego. Jego przyczyną – podobnie jak w technicznych układach
automatycznej regulacji – mogą być zbyt gwałtowne reakcje na zjawiska
ekonomiczne lub zjawiska opóźnienia występujące w ekonomicznych pro-
cesach (np. opóźnienie między rozpoczęciem inwestycji a czasem, kiedy za-
czyna ona przynosić korzyści).
Podobnie, proces narastania kryzysu bankowego można opisać jako
destrukcyjne dodatnie sprzężenie zwrotne, takie samo jak to, które powodo-
wało przewrócenie się kijka postawionego na czubku. Oto pewien bank po-
pada w kłopoty finansowe i pierwsi klienci wycofują zeń swoje pieniądze.
To powoduje oczywiście tylko powiększenie kłopotów banku. Dowiaduje
się o tym jeszcze więcej klientów, którzy biegną i też chcą wycofać swoje
wkłady, itd. Klienci innych banków też na wszelki wypadek zaczynają wy-
cofywać z nich pieniądze, przez co owe – początkowo nawet Bogu ducha
winne – banki też zaczynają mieć kłopoty z wypłacalnością. Panika lawi-
nowo narasta i cały system bankowy przewraca się, jak ów kijek na stole.
Ponury scenariusz, niestety rzeczywisty i znany z historii.
Zadaniem współczesnych państw i międzynarodowych organizacji
finansowych jest więc opracowanie i przestrzeganie procedur (takich jak
np. manipulowanie stopami procentowymi, fundusze gwarancyjne, czasowe
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Cybernetyczne wizje: serwomechanizmy czy komputery? 135

zawieszanie obrotu akcjami na giełdzie itd.), odgrywających rolę stabilizu-


jących ujemnych sprzężeń zwrotnych i pozwalających na „utrzymanie kijka
w pionie”. Również liczne rynkowe i demokratyczne mechanizmy samore-
gulacji działają jak ujemne sprzężenia zwrotne: zmierzają do równoważenia
popytu i podaży, stabilizacji wartości pieniądza, nastrojów społecznych itd.
Zasługa dostrzeżenia tak uniwersalnego charakteru zjawisk komuni-
kowania się i sterowania (a w szczególności sprzężenia zwrotnego) przy-
pada amerykańskiemu matematykowi, Norbertowi Wienerowi (1894–1964).
Po latach naukowych kontaktów z inżynierami, neurologami, lekarzami,
matematykami i logikami, po własnych doświadczeniach z serwomecha-
nizmami – Wiener opublikował w roku 1948 głośną książkę Cybernetyka,
czyli komunikacja i sterowanie w zwierzęciu i maszynie, a później, w roku
1951 – obszerny esej Cybernetyka i społeczeństwo. Roztoczył w nich śmiałą
wizję nowej nauki: cybernetyki, która objęłaby wspólną, jednorodną teorią
wszelkie zjawiska przekazywania informacji, komunikowania się, sterowa-
nia, rozpoznawania obrazów, uczenia się, sprzężeń zwrotnych itd., będące
dotąd przedmiotem bardzo różnych, odrębnych dziedzin wiedzy: od teleko-
munikacji do medycyny, od językoznawstwa do robotyki.
Nazwę tej nowej dziedziny Wiener wywiódł od greckiego słowa
χυβερνετης (kybernetes), czyli sternik, ponieważ chodziło mu właśnie
o grecki termin odpowiadający angielskiemu governor. W ten sposób po-
mysłowe urządzenie ze staroangielskiego wiatraka nadało imię nowej dzie-
dzinie nauki, stanowiącej jeden z ważniejszych symboli połowy XX wieku.
Idea zarysowana przez Wienera szybko rozpaliła wyobraźnię wielu
ludzi. Wydawało się, że cybernetyka wkrótce zintegruje ogromny obszar ba-
dań naukowych i niezwykle przyspieszy rozwój techniki, medycyny i nauk
społecznych. Wielu, zwłaszcza laików, wierzyło, że za rok czy dwa (no,
może pięć) pojawią się cybernetyczne inteligentne roboty i cybernetyczne
protezy mózgu3, że cybernetyka wyjaśni tajemnice przekazywania informa-
cji genetycznej i pozwoli na racjonalne, optymalne sterowanie zarówno ma-
szynami i fabrykami, jak zjawiskami społecznymi i ekonomicznymi. Przez

3 Cybernetyczne organizmy – cyborgi – do dziś straszą na ekranach kin, w filmach


science fiction, nie zawsze najwyższego lotu. Teraz jednak są one najczęściej
„komputerowe”: cyfrowe, a nie analogowe.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

136 6. Obliczenia analogowe i cyfrowe

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

Cybernetyczne wizje: serwomechanizmy czy komputery? 137

Konstruktorzy samolotów, samochodów, statków, budowli hydrotech-


nicznych i drapaczy chmur wciąż sprawdzają swoje rozwiązania, budując
ich modele w zmniejszonej skali i badają ich dynamiczne właściwości w la-
boratoriach pomiarowych, tunelach aerodynamicznych i basenach doświad-
czalnych. To typowa analogowa metoda postępowania.
Inna rzecz, że zgromadzona w ten sposób wiedza przyczynia się do
lepszego rozumienia badanych zjawisk, do ich dokładniejszego opisywania
za pomocą reguł i równań, które można następnie przekształcić na algorytm
obliczeniowy, który będzie symulował te zjawiska. Wykorzystanie odpo-
wiednio szybkich superkomputerów i systemów wielokomputerowych spra-
wia, że symulacja złożonych fizycznych zjawisk też coraz częściej zastępuje
badanie ich analogowych modeli.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

7. Cyfrowe przetwarzanie sygnałów

Dyskretyzacja ciągłego sygnału


Z sygnałami docierającymi z otaczającego nas świata mamy do czynienia
stale. Niosą one w sobie informację, którą możemy wykorzystać przy podej-
mowaniu decyzji, poznawaniu świata lub po prostu dla rozrywki. Sygnały
dźwiękowe, cieplne, optyczne, mechaniczne, elektryczne, zapachowe, sma-
kowe umiemy odbierać za pomocą wyrafinowanych czujników, jakimi są
nasze narządy zmysłów. Wymyśliliśmy także wiele urządzeń wspomagają-
cych nasze zmysły, zwłaszcza do obserwacji i rejestracji sygnałów optycz-
nych i dźwiękowych, które stanowią dla nas podstawowe medium rozpo-
znawania otoczenia. Posługujemy się sygnałami wytwarzanymi przez różne
techniczne urządzenia (jak choćby aparatura radarowa i ultradźwiękowa),
aby przez badanie tego, jak odbijają się one od różnych struktur – dowia-
dywać się o faktach i zależnościach, które nie są bezpośrednio dostępne dla
naszych oczu czy uszu. My sami również produkujemy sygnały: kardiolo-
giczne, elektroencefalograficzne, elektromiograficzne wytwarzane przez na-
sze ciało. Ich analiza ma podstawowe znaczenie dla diagnostyki medycznej,
a więc dla naszego zdrowia i życia.
Takie przykłady można by wymieniać jeszcze bardzo długo. Nie ule-
ga wątpliwości, że zaprzęgnięcie techniki cyfrowej do ich przetwarzania ma
wielkie znaczenie.
Z reguły, choć nie zawsze1, sygnały niosące informację są w swej
źródłowej postaci analogowe, tzn. ciągłe zarówno w czasie, jak i co do
zmian amplitudy. Taki charakter mają np. sygnały mowy, sygnały teleko-
munikacyjne czy wspomniane sygnały fizjologiczne generowane przez or-
ganizm człowieka. W najprostszym, jednowymiarowym przypadku sygnał

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

Dyskretyzacja ciągłego sygnału 139

analogowy jest zmieniającym się w czasie przebiegiem jednej, i to ciągłej,


fizycznej zmiennej, na przykład napięcia elektrycznego, ciśnienia akustycz-
nego, temperatury. Spotykamy też sygnały wielowymiarowe, polegające na
jednoczesnej zmianie w czasie kilku zmiennych, jednak dla naszej książki
wystarczy, jeśli pozostaniemy przy pojedynczej zmiennej.
Jeżeli chcemy przetwarzać taki sygnał przy użyciu technik cyfrowych
– musimy najpierw przetworzyć go z postaci ciągłej, analogowej – na cy-
frową. Łatwo się domyślić, że urządzenie, które zastosujemy w tym celu
nazywa się przetwornikiem (lub konwerterem) analogowo-cyfrowym (kon-
werter AC, ang. analog-to-digital converter). Mówimy, że przetwornik AC
dokonuje dyskretyzacji sygnału zarówno w czasie, jak i w amplitudzie.
Problemy, z jakimi to się wiąże, są zilustrowane na rysunku 7.1 w celowo
wyolbrzymionej postaci.

Rys. 7.1. Kłopoty z dyskretyzacją ciągłego sygnału

Przyjmijmy, że obserwujemy analogowy przebieg napięcia elektrycznego


U(t), dostarczonego przez odpowiedni czujnik, np. mikrofon. Na rysunku
7.1 reprezentuje je krzywa narysowana grubszą linią ciągłą.
Dyskretyzacja musi dotyczyć obu wymiarów rysunku, a więc po-
ziomej osi czasu oraz pionowej osi wartości sygnału. Dyskretyzacja na
osi czasu polega na tym, że co pewien ustalony czas tp (zwany okresem
próbkowania) wykonuje się (jak gdyby „migawkowo”) pomiar wartości sy-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

140 7. Cyfrowe przetwarzanie sygnałów

gnału. Dyskretyzacja na osi wartości sygnału polega natomiast na tym, że


zmierzoną w ten sposób wielkość każdej próbki zapisuje się zgodnie z za-
sadami przyjętej reprezentacji cyfrowej, na przykład jako liczbę naturalną
z ustalonego zakresu.
Dla przejrzystości rysunku 7.1 cały przedział zmienności napięcia (od
Umin do Umax) podzieliliśmy (inaczej: skwantowaliśmy) na jedynie dziesięć
podprzedziałów, ponumerowanych (od dołu do góry) liczbami od 0 do 9.
Linie pionowe pokazują chwile, w których dokonuje się pomiaru (próbko-
wania, ang. sampling) chwilowej wartości napięcia.
Jako wynik próbkowania zapisuje się liczby mówiące, w który z pod-
przedziałów wpadają kolejne próbki. Powstaje w ten sposób ciąg liczb, re-
prezentujący ciągły sygnał w postaci cyfrowej (dyskretnej). Fragment sy-
gnału z rysunku 7.1 przybrałby więc w wyniku dyskretyzacji następującą
postać:

6, 7, 2, 2, 2, 4, 5, 6, 6, …

Ciąg ten można zapisać albo przesłać do odległego odbiorcy, a następnie


odczytać i odtworzyć z powrotem w postaci przebiegu napięcia. Wynik jed-
nak będzie – na pierwszy rzut oka – bardzo rozczarowujący.
Wyobraźmy sobie, że usiłujemy zrekonstruować oryginalny ciągły
sygnał z powyższego ciągu liczb. Możemy to uczynić na przykład w taki
sposób, że odtwarzamy najpierw kratkę poziomych i pionowych linii, a na-
stępnie łączymy środki odpowiednich podprzedziałów odcinkami prostymi.
Powstała w ten sposób linia łamana (przerywana na rysunku 7.1) trochę
może przypomina pierwotny sygnał, jednak znacznie odbiega od oryginału.
Konwersja analogowo-cyfrowa zdaje się grozić bezpowrotnym zniszcze-
niem i zniekształceniem właściwości sygnału.
Popatrzmy na fragment A pokazanego sygnału. Na tym odcinku wiel-
kość sygnału zmienia się, ale pozostaje w granicach tego samego przedziału,
więc kolejne próbki mają tę samą wartość. Po odtworzeniu wygląda to tak,
jak gdyby sygnał nie zmieniał się wcale. To oczywiście skutek niedoskona-
łej dyskretyzacji „w pionie”, na osi wartości sygnału.
Fragment B ilustruje z kolei inne niebezpieczeństwo, związane z dys-
kretyzacją wzdłuż osi czasu. Pierwotny sygnał wykonuje tu szybkie waha-
nia w górę i w dół, które mieszczą się jednak pomiędzy kolejnymi chwilami
próbkowania. Ponieważ wartość sygnału jest mierzona i zapisywana tylko
w tych chwilach – „wyskoki” sygnału nie zostaną w ogóle zanotowane.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dyskretyzacja ciągłego sygnału 141

Skoro rezultat jest tak kanciasty, zniekształcony, pozbawiony waż-


nych – być może – właściwości, to czy przekształcanie sygnału z postaci
analogowej na cyfrową ma w ogóle sens? Odpowiedź brzmi: tak, ma sens,
tylko trzeba to zrobić właściwie. Niebezpieczeństwa dyskretyzacji w obu
wymiarach zostały na rysunku 7.1 wyolbrzymione, właśnie po to, byśmy
znaleźli sposób, jak się z nimi uporać.
Z niedokładnością wynikającą z dyskretyzacji na pionowej osi warto-
ści sygnału można sobie poradzić stosunkowo łatwo. W naszym przykładzie
przedział zmienności sygnału podzieliliśmy – dla prostoty rysunku – bardzo
„grubo”, na 10 części. Prawdziwe konwertery AC operują liczbami nie dzie-
siętnymi, lecz dwójkowymi, o długości np. 8, 12 lub 16 bitów. Przedział
zmienności sygnału jest więc podzielony znacznie drobniej, na 28 = 256,
212 = 4096 lub 216 = 65 536 części. Nasz hipotetyczny konwerter zaliczał do
tego samego przedziału wartości, które różnią się od siebie o nie więcej niż
o dziesiątą część (czyli 10%) całego zakresu zmiennej, tymczasem konwerter
1
8-bitowy zapewnia rozróżnianie wartości, różniących się o mniej niż ,
256
czyli około 0,4%, a 12-bitowy – o , czyli mniej niż 0,025%.
Warto sobie uświadomić, że przeznaczeniem sygnału jest, by dotrzeć
do jakiegoś odbiornika. Jeśli jest to sygnał akustyczny – to jego odbiorcą jest
zapewne ludzkie ucho. Jeśli wizyjny – będzie to zapewne oko widza obserwu-
jącego ekran komputera lub telewizora. Jeśli radarowy – to antena i odbiornik
radaru itd. Każdy taki odbiornik ma właściwą dla siebie, lecz zawsze ograni-
czoną rozdzielczość, to znaczy, że nie jest w stanie rozróżnić dwóch warto-
ści sygnału, które różnią się od siebie o mniej niż pewna progowa wartość.
Jeżeli uda nam się zmniejszyć niedokładność konwersji tak, że bę-
dzie mniejsza od tej granicy rozdzielczości – to odbiorca i tak nie będzie
w stanie odróżnić amplitudy sygnału odtworzonego z jego skwantowanej
wersji od oryginału. Tak więc na przykład w telefonii, ukierunkowanej na
przekazywanie sygnałów akustycznych mowy, ośmiobitowa konwersja AC
zupełnie wystarcza dla oszukania ludzkiego ucha. Jeśli dla jakiegoś urzą-
dzenia potrzebna jest większa precyzja – zastosujemy konwersję dwunasto-
czy szesnastobitową itd.
Niedokładność związaną z dyskretyzacją wartości chwilowych sy-
gnału wzdłuż osi pionowej specjaliści od teorii sygnałów nazywają szumem
kwantowania. Jak się niżej przekonamy, jest to jedyny błąd, jakiego nie da
się uniknąć w procesie konwersji analogowo-cyfrowej. Nie stanowi on jed-
nak wielkiego zmartwienia, ponieważ możemy go kontrolować i sprowa-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

142 7. Cyfrowe przetwarzanie sygnałów

dzić do takiego poziomu, by był praktycznie niezauważalny dla odbiornika,


a więc na przykład dla naszego wzroku czy słuchu.
Sytuacja przedstawiona we fragmencie B jest w pewnym sensie po-
dobna. Tu z kolei zapewne okres próbkowania jest zbyt długi, dzięki cze-
mu nagłe zmiany sygnału kryją się między chwilami próbkowania i nie są
w ogóle rejestrowane. Wiadomo, że okres próbkowania należałoby skrócić.
Jak jednak dobrać jego wartość, by ten niekorzystny efekt nie występował?
Czy również tutaj istnieje jakiś próg decydujący o dokładności?
Rozwiązanie podsuwa twierdzenie o próbkowaniu (ang. sampling the-
orem), zwane także twierdzeniem Shannona, twierdzeniem Nyquista–Shan-
nona albo twierdzeniem Nyquista–Shannona–Kotielnikowa. Do jego sfor-
mułowania przyczyniły się zresztą badania nie tylko tych trzech uczonych
(Amerykanie: Claude Shannon i Harry Nyquist oraz Rosjanin Władimir
Aleksandrowicz Kotielnikow), lecz także wielu innych badaczy, którzy
w latach trzydziestych XX wieku (a nawet wcześniej) pracowali nad tymi
nowymi wówczas zagadnieniami.
Twierdzenie o próbkowaniu formułuje jedną z najbardziej fundamen-
talnych zależności, leżących u podstaw teorii i praktyki przetwarzania sygna-
łów. Mówi ono, jak dobrać okres próbkowania tp tak, żeby można było potem
z otrzymanego ciągu próbek zrekonstruować z powrotem sygnał ciągły bez
żadnej straty zawartej w nim informacji. Wprawdzie jeżeli próbki są skwan-
towane (tzn. przypisane do skończonej liczby przedziałów) – to szum kwan-
towania jest nieuchronny, jednak (jak powiedzieliśmy wyżej) dobierając od-
powiednio dokładność konwersji, potrafimy zminimalizować jego szkodliwy
wpływ. Shannon (opierając się m.in. na rezultatach Edwarda T. Whittakera)
podał również sposób na konwersję cyfrowo-analogową, czyli rekonstrukcję
sygnału ciągłego z ciągu cyfrowych próbek, znacznie doskonalszy niż prosto-
duszne produkowanie linii łamanej, jak my to zrobiliśmy na rysunku 7.1.
Do twierdzenia o próbkowaniu wrócimy wkrótce. Żeby zrozumieć
jego sens – musimy wdać się najpierw w dygresję o innych, jeszcze bardziej
podstawowych zagadnieniach: o widmie sygnału, o przekształceniu Fouriera
i o analizie sygnału w dziedzinie częstotliwości. Dla laika terminy te brzmią
może obco, jednak każdy, kto rozmawia przez telefon komórkowy, słucha
nagrań CD i DVD, ma odtwarzacz MP3, wyświetla obrazy w formacie JPG
czy MPEG, sprowadza z Internetu nagrania muzyczne i filmowe – chcąc
nie chcąc, na co dzień korzysta z naukowych rezultatów, które się z tymi
terminami wiążą.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Widmo sygnału i przekształcenie (transformata) Fouriera 143

Widmo sygnału i przekształcenie (transformata)


Fouriera
Wiemy, że matematycznym modelem prostych drgań czy oscylacji prze-
biegających w czasie jest funkcja sinusoidalna, graficznie reprezentowana
przez znaną krzywą: sinusoidę. Jest to funkcja ciągła i okresowa, co znaczy,
że jej przebieg powtarza się dokładnie co pewien czas T, nazywany okresem
tej funkcji. Oczywiście, odwrotnością okresu T jest częstotliwość f = 1 .
T
Tak więc, jeśli np. okres funkcji sinusoidalnej wynosi T = 0,01 [se-
kundy], jej częstotliwość jest równa f = 100 [razy na sekundę], inaczej mó-
wiąc f = 100 Hz (herców).
O sinusoidach mówiliśmy już w rozdziale 5, ale przypomnijmy: si-
nusoida2 y(t) zadana jest wyrażeniem o postaci y(t) = a ⋅ sin(2πf ⋅ t + p).
Ma więc ona jedną zmienną niezależną t (reprezentującą czas) oraz trzy
stałe parametry, mianowicie amplitudę a, częstotliwość f oraz przesunięcie
fazowe p. Znajomość tych trzech parametrów całkowicie wystarcza do wy-
znaczenia wartości y(t) w dowolnej chwili t, zarówno w przyszłości, jak
i w przeszłości.
Warto zauważyć, że sinusoida – teoretycznie – nie ma początku ani
końca: zmienna niezależna t może przyjmować zupełnie dowolne wartości
rzeczywiste, od minus nieskończoności (dowolnie daleko „w lewo”) do plus
nieskończoności (dowolnie daleko „w prawo”). Dlatego na rysunku może-
my pokazać zawsze tylko pewien ograniczony fragment sinusoidy.
Weźmy teraz kilka sinusoid, różniących się wartościami wspomnia-
nych trzech parametrów i dodajmy je do siebie. Aby otrzymać taką sumę,
musimy dodać do siebie wartości wszystkich sinusoid dla każdej chwili t,
oczywiście pamiętając, że każda z tych sinusoid przyjmuje wartości zarów-
no dodatnie, jak i ujemne.
Otrzymany kształt krzywej zależy od parametrów sinusoid, które do-
dajemy. Na rysunku 7.2 pokazano taką przykładową sumę pewnych sześciu
różnych sinusoid. Nie będziemy tu podawali konkretnych wartości wszyst-

2 Formalnie rzecz biorąc, funkcja sinus (odwzorowanie) – to nie to samo co odpo-


wiadająca jej krzywa – sinusoida (obiekt graficzny). Dla uproszczenia wykładu
będziemy jednak używali tych terminów wymiennie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

144 7. Cyfrowe przetwarzanie sygnałów

Rys. 7.2. Przykładowa suma sześciu sinusoid

kich (osiemnastu) parametrów funkcji biorących udział w tym prostym do-


świadczeniu, bo nie to jest w tej chwili ważne. Ważne jest to, że gdyby po-
kazać przebieg z rysunku 7.2 komuś niedoświadczonemu i nieświadomemu,
że złożono go z zaledwie kilku regularnych, prostych sinusoid – to zapewne
nigdy by się sam tego nie domyślił.
Zaraz, zaraz... Przecież to rozumowanie działa także w odwrotnym
kierunku! Oto ktoś nam pokazuje jakiś dziwny, nieregularny przebieg, na
przykład napięcia odczytywanego z mikrofonu. Może on też powstał przez
złożenie jakichś – nieznanych nam – dziesięciu, stu, pięciuset, a może wręcz
nieskończonej liczby sinusoid?
Przebieg pokazany na rysunku 7.2 jest okresowy. Przy pewnej wpra-
wie można to zauważyć od razu. Ale może w ogóle dowolny przebieg jest
także sumą jakiejś liczby sinusoid o nieznanych nam parametrach? Gdyby
się udało udowodnić, że tak rzeczywiście jest, to czekałaby nas sława, uzna-
nie i nagrody, gdyż byłoby to wielkie odkrycie, o trudnych do przecenienia
konsekwencjach.
Niestety dla nas, a na szczęście dla ludzkości, wielki francuski mate-
matyk, Jean Baptiste Joseph Fourier (1768–1830) już dwieście lat temu był
nas ubiegł w dokonaniu tego odkrycia. Dlatego to z jego właśnie nazwiskiem
kojarzą się metody i procedury obliczeniowe, jak analiza Fouriera (ang.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Widmo sygnału i przekształcenie (transformata) Fouriera 145

Fourier analysis), szereg Fouriera (ang. Fourier series) czy przekształcenie


(transformata) Fouriera (ang. Fourier transform), bez których nie byłoby
dzisiejszej telekomunikacji ani komputerowej analizy sygnałów.
Oczywiście, Fourier nie myślał o przetwarzaniu sygnałów, o ich prób-
kowaniu ani tym bardziej o przesyłaniu multimedialnych danych w kom-
puterowej sieci. W jego czasach dopiero wschodził rewolucyjny dla tech-
niki XIX wiek: wiek pary i elektryczności. Za jego życia włoski profesor
Alessandro Volta pokazał (w 1800 roku) pierwszy prototyp baterii elektrycz-
nej. Na żarówkę, telefon, gramofon (nie mówiąc o elektronice i radiokomu-
nikacji) trzeba było czekać jeszcze prawie całe stulecie.
Ale Fourier zajmował się – między innymi – okresowymi funkcjami
ciągłymi, opisującymi drgania strun3. Wykazał on najpierw, że każdą taką
okresową funkcję (okresowy sygnał) można przedstawić w postaci try-
gonometrycznego szeregu Fouriera, to znaczy sumy pewnej, w ogólnym
przypadku nieskończonej, ale przeliczalnej, liczby sinusoid o ściśle okre-
ślonych częstotliwościach, amplitudach oraz przesunięciach fazowych.
W sumie tej może wystąpić jedynie: sinusoida o częstotliwości f = 0 (tzw.
składowa stała sygnału), sinusoida o częstotliwości podstawowej ,
będącej odwrotnością jego okresu (tzw. pierwsza składowa harmoniczna),
oraz sinusoidy o częstotliwościach będących krotnościami częstotliwości
podstawowej (tzw. wyższe składowe harmoniczne: druga, trzecia... itd.).
Fourier podał również wzory, na podstawie których, znając przebieg funkcji
w jego jednym okresie, można analitycznie wyznaczyć amplitudy i fazy po-
szczególnych składowych harmonicznych, a więc w pełni określić strukturę
częstotliwościową sygnału okresowego. Warto tu podkreślić, że struktura
częstotliwościowa w przypadku sygnałów okresowych jest dyskretna, tzn.
wśród jego składowych harmonicznych występują tylko składowe o dys-
kretnych wartościach częstotliwości
1
f = k , k = 0, 1, …
T
Użyteczność praktyczna wzorów Fouriera umożliwiających wyznacze-
nie amplitud i przesunięć fazowych kolejnych składowych harmonicznych
sygnału okresowego jest jednak ograniczona jedynie do takich sygnałów,

3 Ściślej – były to funkcje okresowe, bezwzględnie całkowalne za okres. Nie bę-


dziemy tu dokładniej wyjaśniali tych terminów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

146 7. Cyfrowe przetwarzanie sygnałów

których przebieg w jednym okresie da się opisać zwartą formułą analitycz-


ną. We współczesnych zagadnieniach analizy sygnałów mamy najczęściej
do czynienia z sygnałami okresowymi na tyle złożonymi, że nie można ich
opisać jawnym wzorem matematycznym. W takich sytuacjach przychodzą
nam w sukurs odpowiednie numeryczne algorytmy obliczania amplitud i faz
poszczególnych składowych harmonicznych szeregu Fouriera.
W praktyce dysponujemy najczęściej ciągiem liczb otrzymanych
w wyniku próbkowania np. jednego okresu sygnału okresowego. Liczby te
stanowią dane wejściowe dla naszych algorytmów. Wynikiem działania al-
gorytmu jest wykaz parametrów skończonej liczby sinusoid, które składają
się na sygnał. Jeżeli w analizowanym okresie sygnału pobraliśmy N próbek
(na przykład N = 2048), to algorytm zwraca parametry sinusoid (a więc
w tym przypadku jest ich 1024). Jest wśród nich składowa stała sygnału, jest
taka sinusoida, której okres jest równy okresowi naszego sygnału (pierwsza
składowa harmoniczna) i taka, której okres jest równy połowie okresu sy-
gnału (druga składowa harmoniczna), i taka, której okres jest równy jednej
trzeciej, jednej czwartej, jednej piątej okresu sygnału (trzecia, czwarta, piąta
składowa harmoniczna)… i tak dalej, aż do takiej sinusoidy, której okres
zmieściłby się w okresie sygnału dokładnie razy. Dla każdej z nich moż-
na odczytać jej parametry: amplitudę i przesunięcie fazowe. Oczywiście,
jeśli któraś z tych sinusoid nie jest w rzeczywistości obecna w analizowa-
nym sygnale – to algorytm zwraca wartość jej amplitudy równą zeru. Taki
algorytm, zastosowany do przebiegu z rysunku 7.2, z łatwością odkryłby, że
jest to suma sześciu sinusoid i podałby parametry wszystkich składowych.
W ogólnym przypadku analizowany sygnał okresowy może mieć nie-
skończenie wiele, ale zawsze przeliczalnie wiele4 niezerowych składowych
harmonicznych. Przy założeniu, że znamy jedynie N jego próbek pobranych
w jednym okresie, omówiony algorytm umożliwia przeprowadzenie nume-
rycznej analizy struktury częstotliwościowej takiego sygnału jedynie z do-

4 Zbiór (na przykład składowych harmonicznych) jest przeliczalny, jeśli potrafimy


jego elementy ponumerować za pomocą liczb naturalnych i powiedzieć: ten jest
pierwszy, ten drugi, ten trzeci, … ten k-ty… itd., choćby ich było nieskończenie
wiele, podobnie, jak nieskończenie wiele jest liczb naturalnych. Zbiór nieprzeli-
czalny tej własności nie ma. Na przykład, ciągła oś częstotliwości też składa się
z nieskończonej liczby punktów, ale pomiędzy każdymi dwoma dowolnie blisko
siebie leżącymi punktami mieści się również nieskończenie wiele punktów. Nie
możemy zatem ich ponumerować kolejnymi liczbami naturalnymi.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Widmo sygnału i przekształcenie (transformata) Fouriera 147

kładnością do jego pierwszych składowych harmonicznych. Gdyby struk-


tura ta była bardziej złożona, tzn. sygnał składał się rzeczywiście z większej
liczby niż składowych harmonicznych, to w celu zwiększenia dokładno-
ści analizy należałoby oczywiście pobrać więcej niż N próbek w jednym
okresie sygnału.
Sformułowane przez Fouriera twierdzenie o trygonometrycznym sze-
regu funkcji okresowej stanowi ważny matematyczny rezultat, ale dla teorii
sygnałów jego przydatność jest ograniczona. Sygnały okresowe, a więc po-
wtarzające się z nudną regularnością i przez to całkowicie przewidywalne,
mają oczywiście swoje znaczenie w technice (np. sygnały synchronizujące,
zegarowe, fala nośna w radiofonii czy telewizji), ale przy przekazywaniu
informacji mogą odgrywać co najwyżej pomocniczą rolę. Jednak Fourier
wkrótce znacznie uogólnił swój wyżej opisany naukowy wynik, definiując
przekształcenie dowolnej (znów: bezwzględnie całkowalnej) funkcji ciągłej,
które nazywamy dziś transformatą Fouriera. Pokazał mianowicie, że dowol-
ny, a nie tylko okresowy, przebieg ciągły można też uważać za sumę sinuso-
id, tyle tylko, że tych sinusoid jest nieprzeliczalnie wiele, a ich częstotliwości
są ułożone w sposób ciągły na osi częstotliwości. Co więcej, amplituda każ-
dej z nich jest równa zeru.
W pierwszej chwili takie stwierdzenie może się laikowi wydać nie-
dorzecznością. Tak jednak nie jest. Z podobną sytuacją, również dla laika
początkowo nieoczywistą i wyglądającą na paradoks, mamy do czynienia
w rachunku prawdopodobieństwa. Przypomnijmy sobie, jak w rozdzia-
le 5, opisując probabilistyczny algorytm Monte Carlo, wybieraliśmy loso-
wo punkt z prostokąta, ograniczającego wykres pewnej krzywej. Punktów
w prostokącie jest nieprzeliczalnie wiele, każdy pojedynczy punkt ma zero-
we wymiary, a więc od strony matematycznej prawdopodobieństwo trafie-
nia w jeden, konkretny punkt jest równe dokładnie zeru5. Jednak prawdo-
podobieństwo trafienia w pewien ciągły obszar, o niezerowych wymiarach
(na przykład w kwadracik o polu 1 mm2), jest już różne od zera, ponieważ
sumują się na nie nieskończenie małe prawdopodobieństwa trafień w nie-

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

148 7. Cyfrowe przetwarzanie sygnałów

skończoną liczbę punktów należących do tego obszaru. Tam, w rozdziale 5


postulowaliśmy, by losowe trafienia rozkładały się równomiernie na całej
powierzchni prostokąta, innymi słowy zakładaliśmy, że gęstość losowanych
punktów jest taka sama na całej powierzchni prostokąta. Gdyby jednak ta
gęstość była różna w różnych miejscach, to prawdopodobieństwo trafienia
w kwadracik o takim samym polu np. 1 mm2 byłoby w jednym miejscu inne
niż w drugim.
Podobnie dzieje się w przypadku sygnału nieokresowego. Niezależnie
od tego, że każda z elementarnych sinusoid ma zerową amplitudę – w każ-
dym analizowanym sygnale grupują się one gęściej w pewnych zakresach
na osi częstotliwości, a rzadziej w innych. To, jak ich gęstość się zmienia
wraz z częstotliwością, jest ważną właściwością sygnału. Co więcej, wpro-
wadzone przez Fouriera przekształcenie pozwala na wyliczenie, jak dla da-
nego sygnału układa się owa gęstość amplitudy na osi częstotliwości.
Inaczej mówiąc, poddając przekształceniu Fouriera sygnał, którego
przebieg w czasie znamy, otrzymamy nową funkcję, której zmienną nieza-
leżną jest częstotliwość (a nie czas), a zmiennymi zależnymi są: gęstość am-
plitudy oraz przesunięcie fazowe dla danej częstotliwości. Ta nowa funkcja
nazywa się widmem (ang. spectrum) analizowanej funkcji czy sygnału.
To trudny moment. Większość ludzi ma duże kłopoty ze zrozumie-
niem, o co tu chodzi i dlaczego ten pomysł jest taki ważny. Ale przecież
z samym pojęciem widma wszyscy zetknęliśmy się jeszcze w szkolnej fi-
zyce. Pokazywano nam, jak pryzmat rozszczepia białe światło słoneczne,
rzutując na ekran właśnie widmo tegoż światła słonecznego. Fizycznie, był
to pasek mieniący się wszystkimi kolorami tęczy, matematycznie – funkcja,
która przyporządkowuje częstotliwości fali elektromagnetycznej (tu – wi-
dzianej jako kolor) – jej położenie na ekranie.
Częstotliwości te są uporządkowane wzdłuż barwnego paska, jak
wzdłuż osi liczbowej: najniższej z widzialnych częstotliwości odpowiada
kolor czerwony, najwyższej – fioletowy. Wiemy także, że to jedynie wi-
dzialny fragment widma. Oś częstotliwości fal elektromagnetycznych roz-
ciąga się dalej, w sposób ciągły, zarówno „w lewo”, jak i „w prawo” od pa-
sma widzialnego. Promieniowanie ultrafioletowe ma częstotliwości wyższe
niż fiolet, jeszcze wyżej mieści się promieniowanie rentgenowskie. Poniżej
częstotliwości światła czerwonego mamy promieniowanie podczerwone,
jeszcze niżej – mikrofalowe, jeszcze niżej – elektromagnetyczne fale ultra-
krótkie, krótkie, średnie, długie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Widmo sygnału i przekształcenie (transformata) Fouriera 149

Fragmenty osi częstotliwości fal elektromagnetycznych są nawet do-


brem, które się sprzedaje i kupuje: operatorzy telefonii komórkowej czy
nadawcy telewizyjni i radiowi płacą niemałe pieniądze za prawo używania
wybranych zakresów częstotliwości. My sami często jeździmy po osi czę-
stotliwości, kręcąc gałką strojenia odbiornika radiowego w poszukiwaniu
ulubionej stacji (albo robi to za nas automat programujący, po naciśnięciu
przycisku „programuj”).
W podobny sposób możemy mówić o uporządkowaniu częstotliwości
innych rodzajów drgań, nie tylko elektromagnetycznych, ale również me-
chanicznych, akustycznych itp. Drgania mechaniczne o częstotliwościach
z zakresu od 20 Hz do 20 kHz są słyszalne dla ludzkiego ucha, wyższe
częstotliwości – to ultradźwięki, niższe – infradźwięki itd. Sama oś częstot-
liwości nie jest więc czymś nieznanym, nienaturalnym czy niezwykłym.
Analitycznej postaci przekształcenia Fouriera nie będziemy tu oma-
wiali, gdyż musielibyśmy wspomnieć o liczbach zespolonych i innych spra-
wach, które zbytnio oddaliłyby nas od głównego nurtu wykładu. Jednak
wynik przekształcenia Fouriera z pewnością widzieliśmy już wielokrotnie.
Pojawia się on np. na wyświetlaczu wielu urządzeń do odtwarzania płyt CD.
Widzimy tam „słupki”, których wysokość dynamicznie się zmienia: gdy za-
grają kontrabasy – słupki z lewej strony podskakują w górę, gdy zabrzmi
głos kobiecy – wzrastają gwałtownie słupki ze środka i prawej części wy-
świetlacza. Tak, to nic innego, jak widmo mocy sygnału. Pasmo słyszalnych
częstotliwości jest podzielone na kilkanaście przedziałów, a odtwarzacz CD
pokazuje w postaci słupków, jaka część mocy sygnału przypada w danej
chwili na sinusoidy mieszczące się w poszczególnych przedziałach. W sto-
sunku do widma amplitudowego różnica jest taka, że moc jest proporcjonal-
na nie do amplitudy, lecz do jej kwadratu.
W praktyce cyfrowego przetwarzania sygnałów widmo sygnału wy-
znaczamy numerycznie, obliczając jego wartości w dyskretnych równoodle-
głych punktach osi częstotliwości. Im wartości widma są obliczane w więk-
szej liczbie punktów, tym jest ono wyznaczone z większą rozdzielczością.
Narzędziem służącym do tego celu jest dyskretna transformata Fouriera,
realizowana przy użyciu odpowiedniego algorytmu. W przypadku sygnałów
nieokresowych otrzymuje on jako daną wejściową ciąg N zakodowanych
dwójkowo próbek sygnału, a jako wynik zwraca N wartości widma amplitu-
dowego, będących w istocie wartościami gęstości amplitudy przypadającej
na 1 Hz osi częstotliwości. W opracowanie bardzo sprawnych algorytmów
dyskretnej transformaty Fouriera włożono wiele starań i pomysłowości.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

150 7. Cyfrowe przetwarzanie sygnałów

Na szczęście, ich złożoność obliczeniowa nie sprawia takich kło-


potów, jak w przypadku problemów NP. Prosty algorytm, wykorzystujący
w bezpośredni sposób zależności, które podał Fourier, ma złożoność O(N 2),
gdzie N jest liczbą próbek. Obecnie (i to już od lat) w powszechnym użyciu
są tzw. algorytmy FFT, czyli szybkiej transformaty Fouriera (od ang. Fast
Fourier Transform). Osiągają one złożoność rzędu O(Nlog2N) i są istot-
nie bardzo szybkie, ale przy założeniu, że liczba próbek w przetwarzanym
odcinku sygnału spełnia pewien nieskomplikowany arytmetyczny warunek.
Nie będziemy go tu przytaczali, wystarczy, jeśli powiemy, że na przykład
wszystkie liczby będące potęgami dwójki (a więc np. 512, 1024, 2048,
4096, 8192 itd.) ten warunek spełniają. Mówimy wówczas o 512-punktowej
FFT, 1024-punktowej FFT itd. Jak już stwierdziliśmy wyżej, im obliczana
FFT jest wyższego rzędu, tym widmo sygnału jest obliczone numerycznie
z większą rozdzielczością.
Na rysunku 7.3 widzimy pewien sygnał, ten sam, ale w dwóch wcie-
leniach. W górnej części jest pokazany pięciosekundowy odcinek rzeczy-
wistego nagrania dźwiękowego, wzięty z pewnej audycji radiowej. Został
on już przetworzony na postać cyfrową, jest więc ciągiem próbek o warto-
ściach pokazanych na wykresie (a). Stąd wizualizacja graficzna tego sygnału
przedstawiona na rysunku wydaje się „postrzępiona”, choć w rzeczywisto-
ści w swojej pierwotnej analogowej postaci ma przebieg gładki. Po podda-
niu go szybkiemu przekształceniu Fouriera (FFT) jego widmo amplitudowe
ma postać zamieszczoną na dolnym wykresie (b)6.
Widmo ujawnia pewne własności sygnału, których nie sposób domy-
ślić się z jego przebiegu w czasie. Okazuje się na przykład, że największy
udział w sygnale mają składowe o częstotliwościach zbliżonych do 1 kHz,
natomiast składowe o częstotliwościach powyżej ok. 5 kHz (zwróćmy uwa-
gę na logarytmiczną skalę na osi częstotliwości) – znikomy i to coraz mniej-
szy w miarę przesuwania się w kierunku częstotliwości coraz większych.
Tak więc w praktyce każdy sygnał możemy obserwować, analizować, reje-
strować w czasie albo – po przekształceniu Fouriera – analizować strukturę
jego widma, pokazującego zależność od częstotliwości. Bardziej fachowo
powiemy, że ten sam sygnał można analizować w dziedzinie czasu (ang.

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

Widmo sygnału i przekształcenie (transformata) Fouriera 151

Rys. 7.3. Przykładowy sygnał w dziedzinie czasu (a) i częstotliwości (b)

time domain) lub w dziedzinie częstotliwości (ang. frequency domain). Są to


dwa różne, ale w pełni równoważne punkty widzenia na dokładnie ten sam
sygnał. Ich matematyczne piękno i zarazem siłę stanowi to, że wspaniale
uzupełniają się wzajemnie ze względu na interpretację właściwości fizycz-
nych sygnału.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

152 7. Cyfrowe przetwarzanie sygnałów

Dla wsparcia intuicji, pomyślmy przez chwilę o świątecznym makow-


cu. Ma on zazwyczaj kształt podłużnego placka, który na stole kroimy na
poprzeczne plasterki. Wiemy dobrze, jak taki plasterek makowca wygląda,
jak układa się w nim makowe nadzienie. Ale gdybyśmy – choć nikt tak nie
robi – pokroili ten placek wzdłuż? Podłużne plastry wyglądałyby zupełnie
inaczej. Kto bez zastanowienia powie jak? Podłużny przekrój ujawnia inne
właściwości placka niż przekrój poprzeczny. Na przykład, rzuciwszy okiem
na choćby jeden taki podłużny plasterek, od razu stwierdzimy, czy nadzie-
nie jest rozłożone równomiernie na całej długości ciasta. Taka ocena byłaby
trudniejsza, gdybyśmy operowali tylko plasterkami poprzecznymi. Jednak
jest to wciąż to samo ciasto, tyle że raz – pokrojone w poprzek, a innym
razem – wzdłuż.
Zasługą Fouriera jest to, że jako pierwszy zaproponował „pokrojenie
sygnału wzdłuż”, dokonując dekompozycji sygnału na równolegle biegnące
sumujące się sinusoidy. Znajomość widma sygnału umożliwia wykonywa-
nie operacji na oddzielnych sinusoidach lub ich grupach, a więc przetwarza-
nie sygnału w dziedzinie częstotliwości. Po ponownym złożeniu otrzymuje
się nowy efekt w dziedzinie czasu.
Kilka poniższych przykładów pozwoli lepiej ocenić korzyści z takiego
postępowania.

Korzyści ze znajomości widma


Na początek wróćmy do twierdzenia o próbkowaniu. Powiedzmy, że mie-
liśmy zarejestrowany przebieg pewnego sygnału w czasie, zastosowaliśmy
do niego przekształcenie Fouriera i już wiemy, że obecna w tym sygnale
sinusoidalna składowa o częstotliwości największej ze wszystkich – ma czę-
stotliwość fmax. Twierdzenie o próbkowaniu mówi, że częstotliwość próbko-
wania fp (a więc liczba próbek pobranych w ciągu sekundy) powinna być co
najmniej dwukrotnie większa niż fmax:

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

Korzyści ze znajomości widma 153

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

154 7. Cyfrowe przetwarzanie sygnałów

A jakie operacje na widmie sygnału można by wykonywać w dzie-


dzinie częstotliwości? Pozostańmy przy nagraniach akustycznych, które są
bliskie naszemu codziennemu doświadczeniu. Można, powiedzmy, filtrować
(czyli wycinać lub przynajmniej osłabiać) składowe o pewnych częstotliwo-
ściach, na przykład zakłócenia wnoszone przez pracujący niedaleko hałaśli-
wy silnik czy inne urządzenie. Najlepiej, jeśli się zna widmo tego hałasu.
Można, przeciwnie, wzmacniać składowe z pewnej części widma. Miłośnik
rythm and bluesa może chcieć nieco „podkręcić” niższe częstotliwości wid-
ma, gdyż podkreśli to rytmiczną warstwę utworu, a amator piosenki po-
etyckiej – częstotliwości średnie i wyższe, te, które silniej decydują o czy-
telności tekstu. Miłośnik muzyki symfonicznej będzie wolał równomierny
rozkład wzmocnienia, nieuprzywilejowujący sztucznie żadnego z zakresów
częstotliwości. Każdy współczesny odtwarzacz cyfrowych nagrań oferuje
szeroki wybór możliwych ustawień w tej sprawie.
Przykładem nieco bardziej złożonej operacji w dziedzinie częstotli-
wości jest otrzymywanie (albo odwrotnie, usuwanie) efektu pogłosu. Pogłos
bierze się stąd, że dźwięk odbija się od ścian, mebli albo od odległych gór
– i wraca jako echo, osłabiony i opóźniony w czasie, nakładając się z aktu-
alnym dźwiękiem. To zjawisko można od biedy zasymulować w dziedzinie
czasu, na przykład nagrywając sygnał dźwiękowy na dwóch magnetofono-
wych taśmach, a następnie odtwarzając je razem tak, by jedna z taśm była
przesunięta w czasie (opóźniona) względem drugiej, a odczytywany z niej
sygnał – osłabiony. Efekt byłby jednak mało realistyczny. Powodem jest
fakt, że każde otoczenie inaczej pochłania i odbija fale głosowe o różnych
częstotliwościach. Niskie, średnie i wysokie częstotliwości są w różnym
stopniu pochłaniane, i to inaczej przez las, inaczej przez domowe meble
i ściany, inaczej przez wnętrze studni, a jeszcze inaczej przez ściany i pu-
bliczność w sali koncertowej. Dlatego widma amplitudy sygnału odbitego
nie powinno się po prostu zmniejszać o stały mnożnik, ten sam dla wszyst-
kich częstotliwości.
Dla osiągnięcia znacznie bardziej realistycznego efektu trzeba znać
widmową charakterystykę danego otoczenia, a ściślej – jego funkcję prze-
noszenia (transmitancję). Wygląda ona bardzo podobnie jak samo widmo
sygnału: podaje dla każdej częstotliwości wartość mnożnika, przez który
trzeba będzie pomnożyć wartość widma amplitudowego określoną dla tej
składowej sygnału, która ma tę właśnie częstotliwość, oraz przesunięcia
fazowego, o jakie trzeba powiększyć przesunięcie fazowe tejże składo-
wej, aby w końcu otrzymać specyficzne, realistyczne dla danego otoczenia
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Korzyści ze znajomości widma 155

widmo sygnału odbitego. Programowe sterowniki głośników i słuchawek


w naszych komputerach oferują zwykle do wyboru parę tuzinów różnych
efektów otoczenia: od pokoju do sali koncertowej, jaskini, gór czy otwartej
przestrzeni. W każdym przypadku dźwięk brzmi istotnie nieco inaczej.
Powyższe przykłady odwołują się do powszechnie zrozumiałych
zjawisk akustycznych, znanych z codziennej praktyki. Korzyści z przetwa-
rzania w dziedzinie częstotliwości są jednak znacznie większe. Znajomość
widma sygnału i operacje na nim można wykorzystać także na przykład do
rozpoznawania, analizy i syntezy komunikatów głosowych (np. w głosowej
komunikacji człowieka z maszynami), do identyfikacji osób, rekonstrukcji
archiwalnych nagrań, odtwarzania mało czytelnych zapisów rozmów i tak
dalej.
Co więcej, podobne techniki można zastosować także do obrazów
i filmów zapisanych w postaci cyfrowej. W tym przypadku obraz składa
się z dużej liczby elementarnych kwadracików (pikseli), a każdy opisywa-
ny jest kompletem trzech binarnych liczb, reprezentujących jasność każdej
z trzech podstawowych barw tego piksela. Przy zapisywaniu każdy obraz
(czy też klatka filmu) jest skanowany, linia po linii, tak więc cyfrowa repre-
zentacja obrazu staje się znów ciągiem liczb. Łatwo go rozdzielić na trzy
ciągi próbek – każdy dla jednej z barw – o takiej samej budowie jak te,
które powstają w wyniku dyskretyzacji sygnału analogowego9.
Obraz można więc również – jak każdy inny ciąg próbek – poddać
przekształceniu Fouriera, otrzymać jego widmo i przetwarzać w dziedzinie
częstotliwości. Podobnie jak w przypadku nagrań dźwiękowych, usuwając
z widma pewne składowe, można pozbyć się zakłóceń, które „zaśmieca-
ją” obraz. Można dobrać takie procedury filtrowania, które spowodują wy-
ostrzenie konturów obiektów, uwypuklenie różnic barw i tak dalej. Takie
algorytmy są wykorzystywane nie tylko przy cyfrowej rekonstrukcji starych
fotografii i filmów. To dzięki nim osiąga się niewiarygodną precyzję np.
zdjęć lotniczych i satelitarnych, wykonywanych z wysokości nawet setek
kilometrów, czy astronomicznych obserwacji obiektów, oddalonych od nas
jeszcze bardziej.
Poza dźwiękami i obrazami, przeznaczonymi dla naszych ludzkich
zmysłów, posługujemy się też innymi sygnałami, które są wytwarzane

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

156 7. Cyfrowe przetwarzanie sygnałów

i przetwarzane przez różne urządzenia. Tu również przetwarzanie w dziedzi-


nie częstotliwości otwiera zupełnie nowe możliwości.
Na przykład, sygnały radarowe były początkowo wykorzystywane
głównie do pomiaru odległości i prędkości poruszających się obiektów: sa-
molotów, samochodów itp. Wystarczyło w tym celu badanie w dziedzinie
czasu: wysłanie krótkich impulsów i mierzenie odstępu czasowego, po jakim
powracały one – odbite – z powrotem do radarowej anteny. Można jednak
wysłać bardziej złożony sygnał, o znanym, odpowiednio zaprojektowanym
widmie. Odbije się on od odległego obiektu i wróci, podobnie jak zwykłe
akustyczne echo. Pamiętajmy jednak, że również podobnie jak w przypadku
echa, różne obiekty inaczej, w specyficzny dla siebie sposób, pochłaniają
i odbijają składowe o różnych częstotliwościach. Stąd, poprzez analizę wid-
ma odbitego sygnału, można wnioskować o wielu właściwościach danego
obiektu: o jego wielkości, kształcie, gęstości, przestrzennym ustawieniu itd.
Trudno przecenić znaczenie takiej analizy np. dla bezpieczeństwa lo-
tów, lotnictwa w ogóle, czy dla techniki wojskowej. Ale przecież nie tyl-
ko: radary geologiczne umożliwiają na takiej zasadzie badanie układu pod-
ziemnych struktur geologicznych, w tym wykrywanie takich, w których
z dużym prawdopodobieństwem występują złoża poszukiwanych surowców.
Radary meteorologiczne śledzą chmury i układy baryczne, które mogą
stanowić zagrożenie dla ruchu powietrznego albo grożą powstawaniem nisz-
czących burz i tornad. Podobne w działaniu sonary (posługujące się falami
nie elektromagnetycznymi, lecz dźwiękowymi i ultradźwiękowymi) są wy-
korzystywane w poszukiwaniach podmorskich, identyfikacji okrętów i łodzi
podwodnych itp., ale także w diagnostyce urządzeń technicznych, gdzie wy-
krywają np. ukryte pęknięcia czy inne miejsca osłabienia konstrukcji.
To jednak wciąż jeszcze nie koniec korzyści z przetwarzania w dzie-
dzinie częstotliwości. Oferuje ono także możliwość znacznej kompresji sy-
gnału, to znaczy zmniejszenia ilości informacji, którą trzeba zapisać lub
przesłać, tak aby dany sygnał można było później odtworzyć bez istotnego
zniekształcenia odczuwalnego dla odbiorcy.
Wyobraźmy sobie, że chcemy (jako nadawca) przesłać pewnemu od-
biorcy dźwięk, reprezentowany przez tylko jedną sinusoidę o częstotliwo-
ści, powiedzmy, 1 kHz i trwający przez jedną sekundę. Jeżeli zastosujemy
rozwiązanie analogowe – powinniśmy przekształcić ten dźwięk na sygnał
elektryczny, zestawić (na czas co najmniej jednej sekundy) połączenie mię-
dzy nadawcą i odbiorcą i wysłać taki sygnał do miejsca przeznaczenia.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Korzyści ze znajomości widma 157

Odebrany sygnał będzie prawdopodobnie przypominał pierwotną sinusoidę,


chociaż zapewne będzie osłabiony i zniekształcony przez nieuchronny szum
w transmisyjnym medium.
Jeżeli chcemy zastosować najprostsze rozwiązanie cyfrowe – musimy
oczywiście dokonać konwersji sygnału na postać cyfrową. Zgodnie z twier-
dzeniem o próbkowaniu, czas próbkowania powinien być co najmniej dwu-
krotnie krótszy od okresu sinusoidy, zatem w ciągu założonej jednej sekun-
dy (i przy częstotliwości sinusoidy 1 kHz) powinniśmy przesłać do odbiorcy
ponad 2000 próbek, z których każda jest, powiedzmy, ośmiobitową liczbą
dwójkową.
Możemy jednak postąpić znacznie bardziej pomysłowo. Jeżeli wiemy,
że przesyłany sygnał jest dokładnie jedną sinusoidą i trwa przez jedną se-
kundę – wystarczy, że prześlemy odbiorcy tylko trzy liczby (a więc parame-
try tej sinusoidy: amplitudę, częstotliwość i przesunięcie fazowe), mówiąc
jednocześnie, żeby odbiorca zrobił sobie taką sinusoidę sam. Po upływie
sekundy, prześlemy ewentualnie do odbiorcy nowe dane, dotyczące nowe-
go odcinka sygnału. Mając te dane, odbiorca jest w stanie wyprodukować
u siebie, na miejscu, dokładnie taki sygnał, o jaki chodziło, i to zupełnie
niezależnie od szumu w kanale transmisyjnym.
Oznacza to nie tylko uniezależnienie się od szumu, lecz także zmniej-
szenie ilości przekazywanej informacji, a także zmniejszenie czasu, przez
który kanał jest rzeczywiście zajęty. W tym przypadku, zamiast ponad 2000
liczb w ciągu sekundy – wystarczy przekazać tylko trzy liczby, a przez resz-
tę czasu kanał jest wolny i może być na przykład wykorzystany przez in-
nych użytkowników.
Takie samo rozumowanie można by przeprowadzić dla przypadku
nie tylko przesyłania, lecz rejestracji i odtwarzania sygnału. Chodzi o to, by
zarejestrować na cyfrowym nośniku nie całą (próbkowaną) sinusoidę, lecz
jedynie jej parametry, niezbędne i wystarczające do jej ponownego wytwo-
rzenia po odczytaniu z nośnika. Oznacza to ogromną oszczędność miejsca,
które musimy przeznaczyć na zapisanie informacji.
To oczywiście przykład uproszczony, lecz dobrze oddający istotę po-
mysłu. Możemy go łatwo rozszerzyć z jednej na wiele sinusoid. Oto wy-
obraźmy sobie, że sygnał, próbkowany zgodnie z twierdzeniem o próbkowa-
niu, dzielimy (w dziedzinie czasu) na krótkie odcinki, o ustalonej długości
znanej zarówno nadawcy, jak i odbiorcy (lub systemom i zapisu, i odczytu).
Niech to będzie, na przykład, 0,1 sekundy. Gdybyśmy oryginalny sygnał
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

158 7. Cyfrowe przetwarzanie sygnałów

próbkowali z częstotliwością np. 40 000 próbek na sekundę, to w jednym


takim odcinku o długości 0,1 sekundy znajdzie się 4000 próbek (a jeszcze
lepiej, dla potrzeb algorytmu FFT, 212 = 4096 próbek).
Poddajmy ten odcinek szybkiemu przekształceniu Fouriera (FFT).
W otrzymanym widmie z całą pewnością znaczna większość składowych ma
znaczenie zupełnie drugorzędne i nie decyduje o czytelności sygnału. W wid-
mie trzeba więc znaleźć tych kilkanaście, kilkadziesiąt czy może nawet kil-
kaset najważniejszych składowych, z których można by sygnał zrekonstru-
ować z powrotem (w dziedzinie czasu), tak by był on dostatecznie zrozu-
miały, choć oczywiście pozbawiony pewnych mniej istotnych szczegółów.
Parametry tych wybranych sinusoid przekazujemy do odbiorcy (lub zapisuje-
my na nośniku) w postaci cyfrowego pakietu o umówionej budowie. Na jego
podstawie odbiorca (lub odtwarzacz dźwięku) może sam wyprodukować
z powrotem (w dziedzinie czasu) odcinek sygnału, bardzo przypominający
oryginał, Tak samo postępuje się z kolejnymi odcinkami sygnału, ważne je-
dynie, by zdążyć, zanim upłynie umówiona dziesiąta część sekundy.
Tę zasadę (jeśli pominąć techniczne szczegóły) wykorzystują systemy
cyfrowej telefonii, jak GSM czy DCS, a także współczesne standardy reje-
stracji sygnałów audio i wideo, jak np. JPG, DVD czy MP3.
Kluczową sprawą jest sposób wyboru reprezentatywnych składowych
widma danego odcinka sygnału, a więc podziału widma na te sinusoidy,
których parametry mają zostać zapisane w postaci cyfrowego pakietu i te,
które można pominąć. Dlatego ogłoszenie każdego ze wspomnianych wyżej
standardów było poprzedzone wieloletnimi badaniami nad mechanizmami
percepcji obrazów i dźwięków przez człowieka. Na podstawie wyników
opracowano zasady kompresji, wykorzystujące wiele pomysłów i technik,
wśród których przekształcenie Fouriera nie jest jedyne, ale odgrywa bardzo
ważną rolę.
Warto dodać, że transformata Fouriera była także inspiracją dla zdefi-
niowania nowych, wzorowanych na niej przekształceń, które również znajdują
zastosowanie w cyfrowym przetwarzaniu sygnałów. Jednak jej pierwotna, kla-
syczna postać i oparte na niej algorytmy FFT nie starzeją się i są nieustannie
w użyciu. Co więcej, uważa się ją za najważniejszą z obecnie znanych i uży-
wanych w praktyce procedur numerycznych.
W chwili, gdy to czytamy, setki milionów (jeśli już nie miliardy) kom-
puterów, telefonów komórkowych, systemów telewizji cyfrowej, odtwarzaczy
audio i wideo, radarów, sonarów… tnie swój sygnał (w dziedzinie czasu) na
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Matematyka i francuska epopeja 159

odcinki, przekształca je przy użyciu FFT w widmo, dokonuje kompresji, pa-


kuje w cyfrowe pakiety, zapisuje je lub przesyła… Albo odwrotnie: odbiera,
rozpakowuje, przekształca w dziedzinę czasu, wyświetla lub przekształca na
dźwięk… Tak więc, z przetwarzaniem sygnałów i transformatą Fouriera mamy
dziś do czynienia nieustannie, nawet jeśli o tym nie wiemy.

Matematyka i francuska epopeja


Na koniec warto wrócić jeszcze do Jeana Baptiste’a Josepha Fouriera, któ-
rego nazwisko wymieniliśmy tu wiele razy. Gdy się w 1768 roku urodził,
na tronie Francji zasiadał Ludwik XV. Królewski dwór był dla wielu innych
europejskich domów panujących wzorem wyrafinowanej arystokratycznej
kultury, języka, elegancji. Zajmowano się tam nie tylko balami, dworskimi
intrygami i romansami. Spotykano się w salonach dla dyskusji o literaturze,
sztuce, społeczeństwie, wychowaniu, ale także o nowych osiągnięciach na-
uki. W bardziej wykształconych warstwach francuskiego społeczeństwa już
torowały sobie drogę idee epoki oświecenia.
Jednak mały Fourier nie miał nic wspólnego z życiem dworu i in-
telektualnymi prądami w stolicy. Przyszedł na świat w prowincjonalnym
Auxerre, jako dwunaste z piętnaściorga dzieci krawca. Możemy tylko wy-
obrażać sobie, jakie to luksusy były w dzieciństwie jego udziałem. Co gor-
sza, zanim skończył dziesięć lat, oboje jego rodzice zmarli, a mały Fourier,
za wstawiennictwem lokalnego biskupa, trafił do internatu Królewskiej
Szkoły Wojskowej w rodzinnym Auxerre. Już podczas szkolnej nauki dały
o sobie znać jego wybitne zdolności matematyczne.
Po ukończeniu szkoły Fourier uznał, że jako osoba pochodząca z ni-
skiego stanu ma niewielkie szanse na znaczniejszą karierę wojskową. Przez
pewien czas wahał się, czy nie zostać księdzem, rozpoczął nawet nowicjat
w benedyktyńskim klasztorze, jednak w końcu matematyczne zainteresowa-
nia wzięły górę i w roku 1790 Fourier podjął pracę jako nauczyciel matema-
tyki w tej samej szkole, którą ukończył.
Właściwie już w tej chwili mógłby powiedzieć, że w wieku 22 lat
osiągnął życiowy sukces: dzięki własnym zdolnościom i pracy wydźwignął
się z biedy i sieroctwa i zdobył szanowaną pozycję nauczyciela w dobrej
szkole. Powinno to było zapewnić mu do końca życia względny dostatek
i społeczny prestiż.
Ale spokojny żywot prowincjonalnego nauczyciela nie był Fourierowi
pisany. Już kilka miesięcy wcześniej, w lipcu 1789 roku, w Paryżu wybu-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

160 7. Cyfrowe przetwarzanie sygnałów

chły rozruchy. Paryski lud zburzył Bastylię, co symbolicznie rozpoczęło


okres przewrotu, który nazywamy Wielką Rewolucją Francuską. Wkrótce
król (już Ludwik XVI) został zdetronizowany (i kilka lat później – straco-
ny), a arystokracja i duchowieństwo – odsunięte od władzy. Cały dotychcza-
sowy porządek społeczny staje na głowie. Rozpoczyna się czas tworzenia
nowych praw, prób ustanawiania nowych obyczajów, nowego kalendarza,
a nawet nowej republikańskiej religii.
W całej Francji powstają komitety rewolucyjne, w których aktywną
rolę odgrywają właśnie prowincjonalni nauczyciele, adwokaci, urzędnicy,
aptekarze... Fourier też przystępuje do takiego komitetu i daje się poznać
jako dobry mówca i sprawny organizator. Wkrótce, w 1794 roku, zostaje
skierowany do Paryża na studia w nowo utworzonej wyższej uczelni, École
Normale, która ma kształcić kadry nauczycielskie dla młodej republiki.
Uczelnia zaczyna działać od stycznia 1795 roku. Wykładowcami są tam
między innymi Pierre Laplace, Joseph Lagrange, Gaspard Monge... Ach, cóż to
za nazwiska! To tytani nowożytnej matematyki, do dziś wymieniani we wszyst-
kich podręcznikach analizy matematycznej, geometrii, mechaniki teoretycz-
nej i astronomii. Fourier, choć od nich młodszy, szybko zyskuje ich uznanie.
Będzie z nimi potem współpracował, korespondował, dyskutował i rywalizo-
wał przez wiele lat. Dzięki ich poparciu, już we wrześniu tegoż roku 1795 sam
otrzymuje stanowisko wykładowcy matematyki w niedawno utworzonej École
Polytechnique, która – dodajmy – istnieje do dnia dzisiejszego i należy do czo-
łówki wyższych uczelni technicznych nie tylko Francji, ale i świata.
Nie jest to jednak nadal czas na spokojną akademicką karierę. Od
kilku lat w kraju trwają walki nie tylko między zwolennikami i przeciw-
nikami nowego porządku, ale między różnymi frakcjami wewnątrz obozu
republikańskiego. Władza przechodzi z rąk do rąk, po szafocie toczą się gło-
wy kolejnych przeciwników politycznych. O oskarżenie nietrudno: zdarzyło
się, że i sam Fourier, dzięki pomocy przyjaciół, cudem uniknął więzienia,
a może nawet gilotyny.
Wreszcie, w tymże 1795 roku na scenie politycznej pojawia się am-
bitny, niespełna dwudziestosześcioletni dymisjonowany generał artyle-
rii, Napoleon Bonaparte. W dramatycznych okolicznościach (kto nie wie
jakich – niech sobie przeczyta) obejmuje dowództwo najpierw garnizonu
paryskiego, potem jednej z republikańskich armii. Prowadzi błyskotliwą,
zwycięską kampanię we Włoszech, zdobywa wielką sławę i popularność,
a w roku 1798 wyprawia się ze swoją armią na Bliski Wschód, by wyprzeć
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Matematyka i francuska epopeja 161

stamtąd Anglików i uczynić Syrię i Egipt francuską kolonią, która mogłaby


stać się potem bazą wypadową do podboju Indii.
Generał Bonaparte był świadom tego, że stąpa po śladach swe-
go rówieśnika, sławnego zdobywcy sprzed dwóch tysiącleci: Aleksandra
Macedońskiego, zwanego Wielkim. Aleksander, również mając zaledwie
dwadzieścia kilka lat, podbił Azję Mniejszą oraz Egipt i dotarł do Indii.
Jednak podboje, panowanie i łupy nie były jego jedynym celem. Szerzył hel-
leńską kulturę i helleńskie prawa, starając się jednocześnie szanować oby-
czaje podbitych narodów. W delcie Nilu ufundował nowe, wspaniałe miasto
swojego imienia: Aleksandrię, a w nim – sławną aleksandryjską bibliotekę,
która potem przez kilka stuleci była intelektualnym centrum śródziemno-
morskiego antycznego świata. Nie darmo był Aleksander wychowankiem
Arystotelesa, jednego z najznakomitszych umysłów naszej cywilizacji, któ-
ry – jako wychowawca i nauczyciel macedońskiego królewicza – zaszczepił
mu szacunek dla wiedzy i dla ludzi, którzy jej pomnażaniem się zajmują.
Napoleon Bonaparte nie chciał być gorszy: oprócz wojska zabrał
więc na tę wyprawę stu osiemdziesięciu dziewięciu wybitnych francuskich
uczonych. Ich zadaniem było utworzenie w Egipcie silnego francuskiego
ośrodka naukowego. Joseph Fourier i jego niedawny nauczyciel, Gaspard
Monge, są wśród nich. Fourier zostaje wybrany na sekretarza powołanego
przez Napoleona Instytutu Egipskiego w Kairze i przez dwa lata kieruje
pracą tej instytucji, a równolegle – w tym trudnym wojennym czasie – pełni
obowiązki gubernatora Dolnego Egiptu.
Niezależnie od toczącej się kampanii wojennej, francuscy historycy,
geografowie, archeolodzy i przyrodnicy tworzą podwaliny współczesnej
egiptologii, a do obowiązków Fouriera należy m.in. gromadzenie i publika-
cja wyników ich badań. Będzie się tym zajmował jeszcze przez wiele lat,
już po powrocie do Francji. Jednocześnie, Fourier jest też w Kairze prze-
wodniczącym dwunastoosobowej sekcji matematycznej Instytutu. Należał
do niej także sam Napoleon Bonaparte, który był miłośnikiem matematyki
i starał się brać udział w seminariach sekcji.
Niedługo potem, już po powrocie do Francji, Napoleon zostaje pierw-
szym konsulem, czyli praktycznie – dyktatorem państwa, a później, w roku
1804, Cesarzem Francuzów. Nieco wcześniej, w roku 1801 mianuje Fouriera
prefektem departamentu ze stolicą w Grenoble. Uczony nie jest zachwyco-
ny, lecz nie potrafi Napoleonowi odmówić i ostatecznie sprawuje ten urząd
przez kilkanaście lat. Dzieli więc swój czas między matematykę, przygoto-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

162 7. Cyfrowe przetwarzanie sygnałów

wywanie monumentalnego opisu Egiptu i działalność państwowo-administra-


cyjną. Po klęsce Napoleona (w 1815 roku) i restauracji monarchii, Fourier
wraca wreszcie na stałe do Paryża, gdzie kontynuuje badania matematyczne,
a w 1817 roku zostaje członkiem Akademii Francuskiej. Umiera w roku 1830,
otoczony szacunkiem jako wielki uczony i niezwykła postać swoich niezwy-
kłych czasów.
Minęło prawie dwieście lat. Ktoś może pomyśleć: cóż mnie obcho-
dzą jacyś goście, którzy i tak od dawna nie żyją? Co oni w ogóle o świecie
wiedzieli? Prawie nic. Nie mieli telewizji, Internetu, telefonu, komputera.
Ba, nawet elektryczności nie mieli, a świeczki były drogie – więc pewno
chodzili spać z kurami. Ganiali się tylko po polach z pałaszami, raz jeden
zwyciężył, raz drugi... Wciąż jakieś bitwy, traktaty pokojowe... Dlaczego ja
muszę się uczyć tych dat, nazwisk? Boże, jakie to nudne. Lepiej pogadam
z Jolką przez komórkę, a potem obejrzę film na DVD albo ściągnę z sieci
nowe pliki MP3.
Dobrze, pogadaj, obejrzyj. Ale nie zaszkodzi, byś wiedział, że ponad
dwieście lat temu był taki biedny chłopiec, sierota z francuskiej prowincji,
który potem, gdy dorósł, zarządzał połową Egiptu i w obezwładniającym
kairskim upale dyskutował o matematyce ze swoim rówieśnikiem, wielkim
dowódcą i przyszłym Cesarzem Francuzów. A jeszcze później, pewnego
dnia, zapewne gęsim piórem, zapewne przy świecy, zapisał parę kartek for-
mułami, bez których nie byłoby dziś twojej komórki, odtwarzacza DVD,
MP3, Skype’a ani cyfrowej telewizji.
On już rzeczywiście od dawna nie żyje, ale jego geniusz pozostał, za-
klęty w algorytmach FFT, zapisany w układach scalonych i komputerowych
programach. Teraz, po dwustu latach, i ty korzystasz z niego, ilekroć naciskasz
zielony przycisk na telefonie komórkowym, by porozmawiać z przyjaciółką.
Historia wcale nie jest nudna. Potrafi być nawet przejmująca.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

8. Maszyna Turinga

Zasada działania maszyny Turinga


Na początku rozdziału 6 wyraziliśmy wątpliwość, czy obliczenia muszą ko-
niecznie składać się z jakichś kroków. Potem zapoznaliśmy się z przykła-
dami obliczeń analogowych, które bez sekwencji kroków się obywają, ale
stwierdziliśmy także, że współczesna informatyka – to jednak głównie do-
mena obliczeń cyfrowych. W tym przypadku przetwarzaniu podlegają dane,
które są sekwencjami złożonymi z symboli należących do wybranego, skoń-
czonego alfabetu. Na czym jednak polega samo przetwarzanie?
Przekonujący model obliczeń sekwencyjnych sformułował w 1936 r.
wybitny angielski matematyk Alan Mathison Turing. Nie był to ani pierw-
szy, ani jedyny naukowy pomysł, jak formalnie zdefiniować pojęcie obli-
czeń i funkcji obliczalnej. Jednak to rozwiązanie, zwane maszyną Turinga
(ang. Turing machine), jest tak proste, że można by je chyba pokazywać
dzieciom w przedszkolu, gdyby nie to, że w przedszkolnej edukacji są te-
maty pilniejsze.
Maszyna Turinga jest maszyną abstrakcyjną, a więc nie jest urzą-
dzeniem w fizycznym czy mechanicznym sensie. Można by ją oczywiście
zbudować, ale nie warto. Jest to raczej urządzenie, o którym wyobrażamy
sobie, jak mogłoby działać, i to wystarczy1. Wnioski są jednak tak ważne, że
niepodobna, by nie powiedzieć o tej maszynie w każdym wykładzie o spra-
wach podstawowych dla informatyki.
Taki fundamentalny charakter ma m.in. tak zwana teza Churcha–
–Turinga, która oświetla zaskakujące relacje między maszynami, algoryt-
mami, komputerami – i nami samymi, ludźmi. Dojdziemy i do tej tezy, lecz
zaczniemy od zapoznania się z samą zasadą działania maszyny Turinga.

1 Niemniej w Internecie można znaleźć różne poglądowe, fizyczne modele maszy-


ny Turinga, w tym także model zbudowany z klocków lego.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

164 8. Maszyna Turinga

Pomysł jest istotnie bardzo prosty. Wyobraźmy sobie maszynę, skła-


dającą się z taśmy i przesuwającego się wzdłuż niej mechanizmu z gło-
wicą czytającą i piszącą (rysunek 8.1). Taśma jest podzielona na komórki
i rozciąga się nieograniczenie daleko w lewo i w prawo. W każdej komórce
można zapisać dowolny, ale dokładnie jeden symbol pewnego wybranego
alfabetu. W każdej chwili głowica „patrzy” na tylko jedną komórkę i potrafi
odczytać symbol, który się w danej komórce znajduje. Może też wpisać do
tej komórki dowolny (ale też tylko jeden) symbol z alfabetu. W takim przy-
padku nowa zawartość komórki wymazuje (zastępuje) wtedy starą.

Rys. 8.1. Zasada budowy maszyny Turinga

Przebieg obliczenia musimy opisać sami, za pomocą grafu sterowania, który


potem kieruje działaniami mechanizmu. Graf sterowania składa się ze skoń-
czonej liczby węzłów (stanów sterowania) i skierowanych strzałek (przejść
między stanami). Przy każdej strzałce jest dopisane umowne oznaczenie, mó-
wiące, jaką operację maszyna wykonuje, przechodząc od jednego stanu do dru-
giego. Jeden krok obliczenia polega na wykonaniu jednej operacji na zawarto-
ści jednej komórki taśmy i następnie przejściu od jednego stanu sterowania do
następnego. Przykład za chwilę lepiej wyjaśni tę sprawę.
Operacje te zdają się niesłychanie prymitywne, a ich sens jest zawsze
następujący: „jeśli w aktualnie widzianej komórce jest symbol x, to zastąp
go przez symbol y, a następnie przeskocz o jedną komórkę w kierunku k”.
Projektując graf sterowania, musimy oczywiście dla każdego przejścia mię-
dzy stanami określić, jakie symbole z alfabetu grają w tej konkretnej sy-
tuacji rolę pozycji x i y w powyższym zdaniu, i czy kierunek k – to w tej
konkretnej sytuacji znaczy „w lewo”, czy „w prawo”.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada działania maszyny Turinga 165

Graf sterowania ma też wyróżniony stan początkowy (od którego roz-


poczyna się wykonywanie obliczenia) oraz co najmniej jeden stan końcowy
(stan stopu), w którym maszyna się zatrzymuje.
Przebieg obliczenia jest następujący. Najpierw na taśmie umieszcza-
my dane początkowe dla obliczenia. Są one oczywiście pewnym ciągiem
symboli, zapisanych szeregowo, jeden po drugim, w kolejnych komórkach
taśmy. Ten początkowy ciąg może mieć dowolną długość, nawet bardzo
wielką: taśma jest przecież i tak nieskończona. W lewo i w prawo od obsza-
ru zajętego przez dane taśma pozostaje niezapisana: zawiera więc tam same
komórki puste. Dla jednolitości całej koncepcji założymy jednak, że alfabet,
którym posługuje się maszyna, ma (oprócz innych symboli) specjalny znak
„puste” (oznaczymy go przez #), który jest wpisany do każdej pustej ko-
mórki na taśmie. Dzięki temu możemy powiedzieć, że każda komórka na
taśmie ma jakąś zawartość: albo umowny znak # (jeśli jest pusta), albo inny
symbol. Komórki puste mogą występować także wewnątrz obszaru danych,
na przykład w roli separatorów, oddzielających fragmenty danych.
Ustawiamy teraz cały mechanizm z głowicą w położeniu początko-
wym, umówmy się, że tak, by głowica „widziała” pierwszy (licząc od le-
wej strony) niepusty symbol danych początkowych. Sterowanie maszyny
ustawiamy też w stanie początkowym. Wtedy maszyna może zacząć pra-
cować.
Zgodnie z opisem zawartym w grafie sterowania mechanizm zaczyna
jeździć po taśmie w lewo i w prawo, zastępując jedne symbole na taśmie
– innymi. Z każdą elementarną operacją sterowanie przechodzi do następ-
nego stanu w grafie sterowania, być może wielokrotnie wraca do tych sa-
mych stanów itd., być może długo to trwa, ale wreszcie dociera do stanu
końcowego i maszyna się zatrzymuje. Wtedy – w chwili osiągnięcia stanu
stopu – popatrzmy na zawartość taśmy: jeśli dobrze zdefiniowaliśmy graf
sterowania, to to, co jest zapisane na taśmie – stanowi gotowy, ostateczny
wynik obliczenia.
I już.
Czy taka prymitywna maszyna potrafi w ogóle coś sensownego wy-
konać? Rolę pamięci danych odgrywa w niej jednowymiarowa taśma, której
komórki nawet nie są ponumerowane. Maszyna umie zajmować się tylko
jednym symbolem naraz. Operacje są tylko jednego rodzaju (ale o trzech
parametrach x, y oraz k), a graf sterowania jest skończony, mimo że dane
mogą mieć nieograniczoną długość. A jednak... O możliwościach maszyny
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

166 8. Maszyna Turinga

Turinga przekonamy się na przykładzie, który przy okazji wyjaśni pewne


szczegóły pomysłu.
Oto prosty, ale nie całkiem banalny przykład. Przyjmijmy, że w pew-
nym fikcyjnym języku znane są tylko dwie litery a i b. Dowolny ciąg zna-
ków, napisany z użyciem tych dwóch liter (oczywiście z możliwymi powtó-
rzeniami), niech będzie „wyrazem” w tym języku. Nie narzucamy żadnych
ograniczeń czy reguł co do następowania tych liter po sobie, również dłu-
gość wyrazu może być zupełnie dowolna. Jeśli tak, to hipotetyczny „słow-
nik” wyrazów tego języka jest nieskończony. Należą do niego dwa wyrazy
jednoliterowe (a oraz b), cztery dwuliterowe (aa, ab, ba, bb), osiem trzy-
literowych, szesnaście czteroliterowych... i tak dalej, do nieskończoności.
Każdy pojedynczy wyraz ma wprawdzie skończoną długość (choć dowolnie
wielką), ale możliwych wyrazów jest nieskończenie wiele.
Z jakichś powodów (powiedzmy, że dla celów kryptograficznych)
chcielibyśmy mieć urządzenie, któremu będziemy mogli zadać dowolny
wyraz (niezależnie od jego długości, która jest skończona, ale może być
– powtórzmy – nieograniczenie wielka), a ono utworzyłoby „zaszyfrowa-
ną” wersję tegoż wyrazu. Szyfrowanie polegać ma na tym, że każda litera
a zostanie zastąpiona przez bb i odwrotnie, każde b przez aa. Tak więc,
zadawszy maszynie do zaszyfrowania wyraz baba, powinniśmy otrzymać
wynik aabbaabb, zadawszy aaabba – otrzymać bbbbbbaaaabb, i tak dalej.
Oczywiście, jest to pełnoprawne zadanie obliczeniowe, choć nie numerycz-
ne: nie będziemy tu mieli do czynienia z dodawaniem, odejmowaniem czy
innymi matematycznymi operacjami na liczbach.
Aby przekonać się, jak z takim zadaniem mogłaby sobie poradzić ma-
szyna Turinga, musimy przede wszystkim zdefiniować alfabet tego oblicze-
nia. Wiemy już, że ma to być skończony, co najmniej dwuelementowy zbiór
elementarnych symboli. Oczywiście, litery a i b muszą wchodzić w skład
alfabetu. Przyjmijmy jednak dodatkowo, że alfabet A obowiązkowo zawiera
także symbol „puste”, który będziemy oznaczać znakiem #. W sumie, w na-
szym obliczeniu będziemy się więc posługiwali alfabetem A ={#, a, b}2.

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

Zasada działania maszyny Turinga 167

Najpierw (jak gdyby „fabrycznie”) taśma jest pusta, to znaczy w każ-


dej komórce znajduje się symbol #. Po to właśnie ten symbol był wprowa-
dzony: żeby umownie zaznaczyć czymś, że tam nie ma nic.
Dane początkowe obliczenia stanowi zadany wyraz, który maszyna
ma zaszyfrować. Dla ilustracji, niech to u nas będzie wyraz baba. Zapiszmy
go na taśmie, umieszczając każdy symbol w oddzielnej komórce. Zauważmy,
że poza fragmentem zajętym przez dane początkowe taśma jest nadal pusta:
zarówno w lewo, jak i w prawo od tego obszaru znajduje się nadal nie-
ograniczenie wiele komórek ze znakiem #3. Ustawmy też głowicę maszyny
w położeniu początkowym, to znaczy tak, że patrzy ona na komórkę, w któ-
rej teraz jest pierwsza od lewej strony litera b.
Budowa i semantyka (znaczenie) tego początkowego ciągu jest cał-
kowicie naszą sprawą, dla maszyny nie ma to żadnego znaczenia. Nie jest
też ważne, w jaki sposób umieściliśmy te dane na taśmie, powiedzmy, że
ręcznie. Dla działania maszyny ważne jest jedynie to, że zanim podejmie
ona swoją aktywność – te dane początkowe tam już są.
Musimy jeszcze skonstruować graf sterowania, który będzie kierował
działaniem maszyny. Żeby to zrobić, najpierw opiszemy słownie, nieformal-
nie, ogólną zasadę dokonywania obliczenia. Potem przekształcimy ten opis
w „prawdziwy”, formalnie poprawny graf sterowania.
Przyjmijmy więc, że po ostatnim znaku zadanego wyrazu pozosta-
wimy jedną pustą komórkę, a dalej, na prawo od niej, nasza maszyna bę-
dzie stopniowo zapisywać na taśmie wynikowy (zaszyfrowany) ciąg liter.
Maszyna będzie brała do zaszyfrowania kolejno literę po literze z zadanego
wyrazu, zaczynając od jego lewego brzegu. Każda wzięta do zaszyfrowania
litera będzie od razu wymazywana (zastępowana znakiem #): w ten sposób
zadany wyraz będzie stopniowo znikał, „zjadany” od lewej strony. Z każdą
zabraną literą maszyna będzie musiała przejechać (jadąc w prawo) przez
pozostałą część zadanego wyrazu, przeskoczyć pustą komórkę (zostawioną
jako separator #) i przejechać (dalej w prawo) przez już napisany ciąg wy-

3 Generalnie, użycie symbolu # również wewnątrz obszaru danych początkowych


nie jest zabronione: puste komórki mogą na przykład oddzielać od siebie frag-
menty bardziej złożonego zestawu danych początkowych. Jednak w naszym przy-
kładzie wewnątrz „wyrazu” nie pojawi się znak #: to wynika z przyjętej definicji
„wyrazu”.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

168 8. Maszyna Turinga

nikowy aż do obszaru pustych komórek. Tam w dwóch kolejnych pustych


komórkach maszyna zapisze odpowiednie dwie litery szyfru i zacznie wra-
cać (w lewo) po kolejną literę do zaszyfrowania.
Wracając, będzie musiała przejechać (jadąc tym razem w lewo) przez
dotąd utworzony ciąg wynikowy, przeskoczyć separator, przejechać (nadal
poruszając się w lewo) przez nieprzetworzoną jeszcze część zadanego wyra-
zu aż do jego pierwszej (niepustej) litery z lewej strony. Wtedy znów zabie-
rze tę literę, wymaże ją i – podobnie jak poprzednio – pojedzie jak najdalej
w prawo aż do obszaru pustych komórek, dwie z nich zapisze odpowiednim
szyfrem i wróci po kolejną literę. Te czynności będą się powtarzać dopóty,
dopóki zadany wyraz nie zostanie „zjedzony” w całości. Wtedy maszyna
zatrzyma się, a na taśmie będzie zapisany wynik obliczenia: ciąg znaków
zaszyfrowany zgodnie z założeniami. Oprócz niego na taśmie znów będą
same puste komórki.
Graf sterowania, odpowiadający dokładnie temu nieformalnemu
opisowi, jest przedstawiony na rysunku 8.2. Składa się on z węzłów (kó-
łek, dla wygody objaśniania – ponumerowanych) oraz skierowanych kra-
wędzi (strzałek). Węzły odpowiadają stanom sterowania, strzałki – przej-
ściom od jednego do drugiego stanu, etykiety dopisane do przejść grają
zaś rolę elementarnych instrukcji, które maszyna wykonuje podczas tego
przejścia.

Rys. 8.2. Graf sterowania przykładowej maszyny Turinga


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada działania maszyny Turinga 169

Jeden ze stanów jest wyróżniony jako stan początkowy. U nas ma on


podwójną obwódkę i jest oznaczony numerem 1. Jest też jeden stan końco-
wy (stan 2). Napisano przy nim „stop”, ale właściwie mogłoby się obyć bez
tego: stan końcowy łatwo poznać po tym, że nie wychodzą zeń żadne strzałki,
więc nie ma możliwości wyjścia z niego. Graf sterowania miał być skończony
– i jest: zawiera dziesięć stanów oraz siedemnaście przejść między nimi.
Przy strzałkach oznaczających przejścia dopisane są oznaczenia o bu-
dowie x/y, k. Z jednym przejściem może być związane jedno lub kilka
takich oznaczeń. Grają one rolę elementarnych instrukcji maszyny, sym-
bolizują bowiem operacje (akcje) wykonywane podczas danego przejścia.
Zgodnie z tym, co powiedziano wyżej, w każdej z nich, x i y są symbolami
alfabetu, k – oznaczeniem kierunku (L – w lewo, P – w prawo). Znaczenie
takich instrukcji najlepiej objaśnią przykłady:
– od stanu 1 do stanu 3 prowadzi strzałka z przypisana instrukcją a/#,P.
Oznacza to: jesteś teraz w stanie 1; jeśli w aktualnie obserwowanej ko-
mórce taśmy jest a, to wpisz tam # (czyli: wymaż tę literę), przesuń się
o jedną komórkę w prawo (P) i przejdź do stanu 3;
– od stanu 5 do stanu 6 prowadzi strzałka z instrukcją #/b,L. Oznacza to:
jesteś teraz w stanie 5; jeśli aktualna komórka jest pusta (#), to wpisz tam
b, przesuń się o jedną komórkę w lewo (L) i przejdź do stanu 6;
– od stanu 3 z powrotem do stanu 3 prowadzi strzałka („ucho”) z przypisa-
nymi dwiema instrukcjami: a/a,P oraz b/b,P. Oznacza to: jesteś w stanie
3; jeśli w aktualnie obserwowanej komórce taśmy jest a, to wpisz tam a
(czyli w istocie nic nie zmieniaj) i przesuń się o jedną komórkę w prawo
(P). Podobnie, jeśli tam jest b, to też nic nie zmieniaj i też przesuń się
w prawo. W każdym przypadku pozostań w stanie 3;
...i tak dalej. Zauważmy, że wszystkie instrukcje są warunkowe: każda
z nich zaczyna się od „jeśli...”, a więc wykonuje się „pod warunkiem, że...”,
na przykład pod warunkiem że w komórce jest a... , pod warunkiem że ko-
mórka jest pusta... itd.
Mając to na uwadze, łatwo odtworzymy zachowanie maszyny przy
szyfrowaniu zadanego jako przykład wyrazu baba. Przypomnijmy, że na po-
czątku obliczenia:
– początkowa zawartość taśmy jest …####baba###...,
– głowica patrzy na literę b (aktualnie obserwowany symbol będziemy wy-
różniali przez pogrubienie i podkreślenie),
– graf sterowania jest w stanie 1.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

170 8. Maszyna Turinga

Ponieważ w obserwowanej komórce jest b, spośród trzech warunko-


wych przejść wychodzących ze stanu 1 może być wykonane tylko jedno,
prowadzące do stanu 8. Zgodnie z instrukcją b/#, P, maszyna zastępuje
więc b symbolem #, przesuwa się o jedną komórkę w prawo i przechodzi do
stanu 8. Teraz zawartość taśmy jest... ###aba###... Zgodnie z instrukcjami
na „uchu” stanu 8 maszyna pozostawia bez zmiany kolejne litery a i b, za
każdym razem przesuwa się w prawo, ale pozostaje w stanie 8 (kolejno:
...###aba###..., ...###aba###..., wreszcie... ###aba###...). Tę pustą ko-
mórkę maszyna pozostawia jako separator, przesuwa się w prawo i przecho-
dzi do stanu 9. Sytuacja na taśmie jest teraz taka: ...###aba###...
Czas wreszcie na samo szyfrowanie. W pustej komórce maszyna za-
pisuje a, przesuwa się w prawo i przechodzi do stanu 10 (na taśmie powstaje
sytuacja ...###aba#a###...). Tu składuje drugą literę a, cofa się na taśmie
o jedną komórkę w lewo i przechodzi do stanu 6 (...###aba#aa###...). To
początek powrotu po następną literę do zaszyfrowania. Maszyna postępuje
zgodnie ze ścieżką w grafie sterowania, wiodącą przez stany 6, 7 z powro-
tem do 1. Proszę sprawdzić, że w chwili powrotu do stanu 1 sytuacja na
taśmie jest następująca: ...###aba#aa###...
Tak więc maszyna teraz zajmie się literą a. O ile przedtem, rozpo-
znawszy literę b, przeszła do stanu 8, to teraz wybierze przejście ze stanu
1 do 3 i dalej podąży ścieżką wiodącą przez stany 3, 4, 5, 6, 7 z powrotem
do 1. Potem znowu rozpozna literę b... Łatwo sobie dopowiedzieć, co się
będzie później działo.
Za czwartym powrotem do stanu 1 wszystkie (właśnie cztery) znaki
przykładowego wyrazu są już wymazane z taśmy, a ich zaszyfrowane od-
powiedniki – zapisane na taśmie. Sytuacja na taśmie jest więc następująca:
...###aabbaabb###..., a sterowanie jest z powrotem w stanie 1. Teraz jed-
nak widzianym znakiem jest #, więc maszyna przechodzi do stanu 2 i za-
trzymuje się, a na taśmie jest spodziewany wynik obliczenia: ciąg aabba-
abb.
Łatwo sobie wyobrazić, że maszyna poradzi sobie z zakodowaniem
dowolnego z wyrazów naszego hipotetycznego języka, choć jest ich nieskoń-
czenie wiele, a długość zadanego wyrazu może być nieograniczenie wielka.
W każdym przypadku wynikowy ciąg będzie dwukrotnie dłuższy od zada-
nego, ale przecież na nieskończonej taśmie zawsze jest dostatecznie dużo
miejsca na prawo od zadanego wyrazu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada działania maszyny Turinga 171

A jak musiałaby zmienić się maszyna, gdyby w założonym hipote-


tycznym języku znane były nie tylko litery a i b, ale jeszcze c? I gdyby c
kodowało się jako parę, powiedzmy, ab? To również łatwo sobie wyobrazić.
W grafie sterowania trzeba by było dorobić jeszcze jedną ścieżkę, analo-
giczną do tej, która (dla litery a) prowadzi przez stany 1, 3, 4, 5, 6, albo
do tej, która (dla litery b) prowadzi przez stany 1, 8, 9, 10, 6. W podob-
ny sposób można by dodać jeszcze litery d, e, f, i tak dalej, odpowiednio
określając dla każdej z nich sposób kodowania. Rysunek grafu będzie się
oczywiście komplikował, ale w gruncie rzeczy nie ma potrzeby go rysować.
Wystarczy zrozumieć istotę sprawy i zasadę rozbudowy grafu, by dojść do
wniosku, że na pewno da się to zrobić, graf sterowania pozostanie skoń-
czony, a zmodyfikowana maszyna nadal poradzi sobie z kodowaniem ciągu
znaków o dowolnej długości.
No dobrze, ale czy maszyna Turinga jest w stanie poradzić sobie
z jakimiś obliczeniami arytmetycznymi: dodawaniem, odejmowaniem itd.?
Choćby z takim najprostszym zadaniem, z jakim mają do czynienia dzieci
w przedszkolu czy zerówce: z dodawaniem liczb naturalnych?
Zazwyczaj operacje takie ilustrowane są układaniem kupek kaszta-
nów czy patyczków. Mówi się: „masz tu, Jasiu (czy Arturku), cztery pa-
tyczki, a tu – dwa patyczki. Ile ich będzie razem?”. Po dłuższej chwili pada
odpowiedź: „Sześć”. „A teraz: tu trzy, a tu – pięć. Ile razem?”. „Osiem”.
Czy maszyna Turinga potrafi się wykazać podobną bystrością?
Oczywiście. Weźmy najprostszy, binarny alfabet A={#,1}, a więc za-
wierający symbol # (puste) oraz jeden niepusty symbol 1. Przyjmijmy naj-
prostszy sposób zapisywania liczb naturalnych: niech każda liczba będzie
ciągiem samych jedynek, a jej wartość niech będzie równa liczbie jedynek
w tym ciągu. To ta sama, przedszkolna metoda: jedynki odgrywają tu rolę
patyczków czy kasztanów. Zapiszmy na taśmie maszyny Turinga dwie takie
liczby, oddzielone jedną pusta komórką (rysunek 8.3a) i ustawmy głowicę
maszyny w położeniu początkowym (na pierwszej od lewej strony komórce
pierwszej liczby).
Musimy jeszcze skonstruować graf sterowania. Powinien on tak po-
kierować działaniami maszyny, by w chwili zatrzymania się na taśmie pozo-
stała jako wynik liczba, która jest sumą dwóch zadanych liczb naturalnych.
Oczywiście, musi tak się dziać dla zupełnie dowolnych danych początko-
wych, nawet jeśli mają one długość tysięcy czy milionów jedynek.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

172 8. Maszyna Turinga

Zadanie okazuje się banalnie proste, a jego rozwiązanie jest pokazane


na rysunku 8.3b. Trzeba po prostu z dwóch liczb zrobić jedną, usuwając
separator oddzielający je od siebie. W tym celu maszyna powinna (jadąc
w prawo) przejechać przez całą pierwszą liczbę (stan 1 z „uchem” oznaczo-
nym instrukcją 1/1,P), a po natrafieniu na separator # (oddzielający obie
liczby) napisać tam 1 i przejść do stanu 2. W tym momencie granica mię-
dzy liczbami znika, ale jedynek jest o jedną za dużo. W stanie 2 maszyna
przejeżdża przez drugą liczbę (pozostawiając wszystkie jedynki na swoim
miejscu, zgodnie z instrukcją 1/1,P na uchu tego stanu). Po natrafieniu na
pustą komórkę (co oznacza, że głowica już przejechała poza koniec drugiej
liczby) trzeba cofnąć się (w lewo) i wymazać ostatnią jedynkę z drugiej
liczby. W ten sposób przejściowy nadmiar jedynek zostaje zlikwidowany.
Już można się zatrzymać, a na taśmie jest poprawny wynik dodawania (ry-
sunek 8.3c).

Rys. 8.3. Dodawanie liczb naturalnych

Cztery kółka, pięć strzałek, pięć prostych instrukcji – a maszyna poradzi


sobie (oczywiście przy tak przyjętym sposobie reprezentowania liczb) z do-
dawaniem dowolnej pary liczb naturalnych, których jest przecież nieskoń-
czenie wiele. Proszę sprawdzić, że wynik jest poprawny także wtedy, kiedy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada działania maszyny Turinga 173

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

4 Zgodnie z przyjętą konwencją, liczba 0 ma zero jedynek, jest zatem reprezento-


wana po prostu komórką pustą (#). Dane początkowe dla dodawania 0 + 3 wyglą-
dałyby więc tak: ...####111###..., dla 3 + 0 – tak: ...###111###..., dla ‘0+0’ – tak:
...#####..., przy czym podkreślona komórka zaznacza początkowe położenie gło-
wicy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

174 8. Maszyna Turinga

Rys. 8.4. Dziesiętne zliczanie jedynek

widzi #. Wtedy nie kłopocze się nawet przesuwaniem do obszaru licznika:


pisze 0, przechodzi do stanu 2 i staje. Rzeczywiście, taka liczba ma dziesięt-
ną wartość równą zeru.
Jeśli jednak zadana liczba ma choć jedną jedynkę, to maszyna pozo-
stawi ją na swoim miejscu, przesunie się w prawo i przejdzie do stanu 3. Tę
czynność (1/1, P) maszyna powtórzy tyle razy, ile jest jedynek w zadanym
ciągu, stale wracając do stanu 3. Praktycznie znaczy to, że maszyna przeje-
dzie (posuwając się w prawo) przez cały zadany ciąg, nie zmieniając go, aż
wreszcie wyjedzie na pierwszą (z prawej) pustą komórkę. Wtedy zostawi ją
pustą, cofnie się nad ostatnią (z prawej) jedynkę ciągu i przejdzie do stanu
4. Tę ostatnią jedynkę wymaże (zastąpi przez #) i ruszy w drogę powrot-
ną w lewo. Wielokrotnie wracając do stanu 5, przejedzie przez dany ciąg,
wreszcie napotkawszy pustą komórkę (ów separator, z lewej strony ciągu),
przeskoczy ją i przejdzie do stanu 6. Teraz maszyna jest już na terenie licz-
nika dziesiętnego i powinna zwiększyć jego wartość o jeden. Po wykonaniu
tej czynności (objaśnimy ją za chwilę) maszyna znowu pojedzie jak najdalej
w prawo, wymaże kolejną jedynkę, wróci do licznika i zmodyfikuje jego
zawartość itd. Zadany ciąg będzie więc stopniowo znikał, „zjadany” od pra-
wego końca, a za każdym obiegiem pętli licznik będzie zwiększał swoją
wartość o jeden.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada działania maszyny Turinga 175

Cała reguła zwiększania dowolnej liczby dziesiętnej o jeden opisana


jest instrukcjami, przypisanymi do dwóch przejść: od stanu 6 z powrotem
do 6 („ucho” z instrukcją 9/0, L) oraz od stanu 6 do 7, gdzie jest napisanych
dziesięć instrukcji. Jednak spośród tych dziesięciu instrukcji zawsze można
wykonać tylko dokładnie jedną, zależnie od tego, jaki symbol widzi aktual-
nie głowica na taśmie.
Za pierwszym razem maszyna, wjechawszy na teren licznika, widzi
komórkę pustą, więc zgodnie z instrukcją #/1, P pisze tam 1, wraca w pra-
wo i przechodzi do stanu 7. Za drugim razem głowica odczyta 1, dlatego
zgodnie z instrukcją 1/2, P zmieni 1 na 2. Za trzecim – 2 na 3, za czwartym
– 3 na 4 i tak dalej, wreszcie za dziewiątym – 8 na 9. Ale za dziesiątym
razem będzie inaczej: znalazłszy się w stanie 6 maszyna odczyta 9, więc
zgodnie z „uchem” zamieni 9 na 0 i przesunie się w lewo. Tu jeszcze nie
byliśmy: teraz głowica patrzy na nową pustą komórkę, pisze więc tam 1,
wraca w prawo i przechodzi do stanu 7. Teraz w liczniku jest #10#, a ma-
szyna jedzie po następną jedynkę.
Proszę sprawdzić, że – analogicznie – za tysięcznym razem z #999#
zrobi się #1000#, za tysięcznym pierwszym – #1001# i tak dalej. Licznik
wydłuża się w lewo o kolejne pozycje dziesiętne. Dlatego właśnie utworzy-
liśmy licznik z lewej strony liczby jedynkowej: na początku obliczenia nie
jesteśmy w stanie przewidzieć jego długości i zostawiamy możliwość, by
mógł się nieograniczenie rozrastać w lewo.
Warto zauważyć, że bez żadnej zmiany układu stanów i przejść w gra-
fie z rysunku 8.4, jedynie poprzez modyfikację instrukcji przypisanych tyl-
ko trzem przejściom, można opisać zliczanie jedynek w systemie nie dzie-
siętnym, a dwójkowym, trójkowym czy o dowolnej innej podstawie5. Na
przykład, dla licznika dwójkowego:
– na przejściu („uchu”) od stanu 6 z powrotem do 6 byłaby instrukcja 1/0, L,
– na przejściu od stanu 6 do stanu 7 – dwie instrukcje: #/1, P oraz 0/1, P,
– Na przejściu („uchu”) od stanu 7 z powrotem do 7 – dwie instrukcje: 0/0,
P oraz 1/1, P.

5 Zapis musi być jednak systematyczny. Co to dokładnie znaczy przekonamy się


w rozdziale 11. W każdym razie, dla kontrprzykładu, rzymski system zapisywa-
nia liczb systematyczny nie jest.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

176 8. Maszyna Turinga

Łatwo też sobie wyobrazić połączenie dwóch grafów sterowania: z ry-


sunków 8.3 oraz 8.4, tak by maszyna dodawała dwie jedynkowe liczby do
siebie, a następnie – bez zatrzymywania się – zliczała je i podawała wynik
w postaci dziesiętnej albo dwójkowej, trójkowej itd. Tę prostą zabawę pozo-
stawimy czytelnikowi: trzeba połączyć stan końcowy pierwszego grafu ze sta-
nem początkowym drugiego. Trzeba sobie tylko poradzić z tym, że końcowe
położenie głowicy w dodawaniu liczb jedynkowych jest inne niż zakładane
początkowe położenie w przypadku grafu zliczania jedynek.
Nie będziemy nudzić czytelnika dalszymi przykładami. Sama idea
jest chyba jasna, a szczególnie zainteresowany czytelnik może sam zbudo-
wać własną maszynę Turinga, na przykład taką, która każdy zadany jej wy-
raz napisałaby wspak albo podała resztę z dzielenia zadanej liczby jedynek
przez 3. Nie byłyby to maszyny bardziej skomplikowane niż te, które omó-
wiliśmy wyżej. Pamiętać tylko należy, że to my sami definiujemy alfabet
maszyny oraz reguły budowy początkowych i końcowych danych, możemy
więc dla wygody wprowadzać do alfabetu dodatkowe symbole, które posłu-
żą na przykład jako dodatkowe separatory czy znaczniki.
Powie ktoś, że opisana maszyna Turinga jest zbyt prymitywna i moż-
na by ją łatwo ulepszyć. Dlaczego na przykład ograniczać się do pamięci
mającej postać jednowymiarowej taśmy? Może lepiej by było zapisywać
dane na dwuwymiarowej płaszczyźnie, pokratkowanej poziomo i pionowo
tak, że każda kratka jest elementarną komórką pamięci? Głowica czytająco-
-pisząca mogłaby się wtedy przesuwać w każdym kroku nie tylko w lewo
albo w prawo, lecz także w górę albo w dół i w pozostałych czterech kie-
runkach na skos?
A może ponumerować komórki pamięci i dozwolić, by głowica mo-
gła przeskakiwać od razu do komórki o numerze (adresie) wskazanym w in-
strukcji, zamiast przesuwać się zawsze tylko o jedną pozycję we wskaza-
nym kierunku?
A może wprowadzić kilka taśm i znacznie rozszerzyć treść instrukcji?
Na przykład, gdyby były dwie taśmy, instrukcje mogłyby mieć następującą
treść: „jeśli na pierwszej taśmie widzisz symbol x, a na drugiej y, to na
pierwszej napisz z, na drugiej napisz v i na pierwszej taśmie przesuń się
w kierunku k1, a na drugiej w kierunku k2”. Może taka maszyna byłaby
znacznie lepsza od tej najprostszej?
Okazuje się, że takie ulepszenia są nieskuteczne i bezcelowe. Może
rzeczywiście pozwoliłyby one na zmniejszenie liczby stanów i krawędzi
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Teza Churcha–Turinga dla obliczeń sekwencyjnych 177

w grafie sterowania lub na nieco zgrabniejsze zapisywanie danych i instruk-


cji, ale udowodniono, że każdy z algorytmów, zrealizowanych w postaci
któregoś z tych „ulepszonych” rozwiązań daje się również wykonać w naj-
prostszej maszynie Turinga, takiej, jak wyżej opisana. Może nieco dłużej
wędrowałaby ona po taśmie, odwiedziła większą liczbę stanów itd., ale wy-
konane obliczenie i sam wynik byłyby identyczne.
Maszyna Turinga jest i tak tworem abstrakcyjnym, a nie inżynierskim
– więc jej przyspieszanie, zwiększanie wydajności, skracanie czasu dostępu
do pamięci po prostu nie ma sensu. Ponieważ takie ulepszenia nie zwięk-
szają jakościowych możliwości przetwarzania – lepiej jest pozostać przy jej
pierwotnej definicji, ponieważ jest zwyczajnie najprostsza i najłatwiejsza do
wyjaśnienia.

Teza Churcha–Turinga dla obliczeń sekwencyjnych


Kilka powyższych przykładów wyjaśniło – miejmy nadzieję – zasadę dzia-
łania maszyny Turinga. Czy jednak z tego modelu wynikają jakieś ogólniej-
sze wnioski, czy jest to tylko rodzaj intelektualnej zabawy, łamigłówki dla
zabicia czasu?
Wniosek jest, i to bardzo ważny, wręcz fundamentalny. Zaskakujący,
a jednocześnie – skłaniający do wielu przemyśleń. Nosi on nazwę tezy
Churcha–Turinga dla obliczeń sekwencyjnych (ang. Church–Turing thesis,
Church–Turing conjecture)6.
Teza ta mówi, że maszyny Turinga są w stanie rozwiązać każdy efek-
tywnie rozwiązywalny problem algorytmiczny. Innymi słowy, jeśli my po-
trafimy (posługując się jakimkolwiek znanym nam formalizmem, zbiorem
wzorów czy reguł, językiem programowania itp.) sformułować algorytm
rozwiązania jakiegoś zadania, i to w taki sposób, by dał się on skutecznie
wykonać – to musi dać się skonstruować maszynę Turinga, która rozwią-
że ten sam problem. Co to znaczy „skonstruować maszynę Turinga”? To
już wiemy: zdefiniować jej alfabet, określić sposób zapisywania danych
i wyników na jednowymiarowej taśmie i wreszcie – zbudować odpowiedni
skończony graf sterowania, zgodny z prostymi regułami, które opisaliśmy
wyżej.

6 Drugie nazwisko należy do wielkiego amerykańskiego matematyka i logi-


ka, Alonzo Churcha (1903–1995). Turing był wcześniej jego doktorantem.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

178 8. Maszyna Turinga

Ale tezę Churcha–Turinga można też czytać w odwrotnym kierunku.


Jeżeli wymyślimy sobie zadanie, do którego rozwiązania nie można skon-
struować maszyny Turinga, to nie ma też takiego formalizmu ani języka
programowania, ani komputera, przy użyciu którego dałoby się to zadanie
efektywnie rozwiązać.
A dlaczego miałoby się nie udać skonstruowanie maszyny Turinga?
Na przykład dlatego, że nie potrafimy zdefiniować skończonego alfabetu
dogodnego dla zapisania potrzebnych danych. Albo dlatego, że nie potrafi-
my określić struktury danych początkowych lub nie potrafimy zserializować
tych danych (czyli „rozwałkować” ich w jednowymiarową sekwencję, którą
moglibyśmy zapisać na taśmie). Albo dlatego, że graf sterowania musiałby
mieć nieskończoną liczbę stanów. Albo nie osiągałby stanu stopu. Jeśli więc
z analizy zadania wyniknie, że (dla któregoś z takich powodów) z całą pew-
nością nie można zrealizować odpowiedniej maszyny Turinga – to możemy
być pewni, że nie znajdziemy na całym świecie takiego komputera czy su-
perkomputera, który rozwiązałby dla nas to zadanie.
Mówiąc językiem potocznym, wynika z tego, że żaden komputer nie
jest „mądrzejszy” od maszyny Turinga. Jest wygodniejszy w użyciu, szybki,
ma ekran, klawiaturę, graficzny interfejs użytkownika... Każdy język pro-
gramowania oferuje znacznie bogatsze struktury danych niż liniowy ciąg
symboli i na pewno więcej niż jeden typ instrukcji. Jednak to tylko technicz-
ne (choć ważne) udogodnienia. Komputer „umie” zrobić tylko to, co „umie”
maszyna Turinga. Jeśli ona czegoś „nie umie”, to on też „nie umie”, i żaden
ekran, zaawansowana karta graficzna czy bogata lista instrukcji tego stanu
rzeczy nie zmieni.
Stwierdzono również, że podobieństwo zachowania maszyny Turinga
i zasad wykonywania algorytmów rozciąga się także na podział na podsta-
wowe klasy złożoności obliczeniowej: P oraz NP. Sens tego stwierdzenia
jest następujący. Wyobraźmy sobie, że dla pewnego zadania obliczenio-
wego mamy pewien algorytm (wyrażony w postaci opisu, sieci działań,
programu itp.) oraz maszynę Turinga, która rozwiązuje ten sam problem.
Okazuje się, że jeśli algorytm ma złożoność wielomianową, to również
w odpowiadającej mu maszynie Turinga liczba przejść między stanami
(potrzebna do jej zatrzymania się) rośnie w sposób wielomianowy wraz ze
wzrostem rozmiaru danych początkowych. Jeżeli natomiast algorytm jest
klasy NP – to liczba koniecznych przejść w grafie sterowania maszyny
Turinga eksploduje także wykładniczo. Nie ma wprawdzie żadnej gwaran-
cji, że będzie to taki sam stopień wielomianu czy (w drugim przypadku)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Teza Churcha–Turinga dla obliczeń sekwencyjnych 179

taki sam wykładnik, ale zostaje zachowana zasada, że to, co wielomianowe


tu – to wielomianowe tam, a co tu wykładnicze – tam również będzie wy-
kładnicze.
Maszyna Turinga nie jest więc sama w sobie komputerem: jest ona
modelem sposobu wykonywania wszelkich algorytmów i modelem możli-
wości komputerów, dobrze pokazującym, czego możemy od nich oczeki-
wać i czego sami musimy dokonać, by przekształcić zadanie obliczeniowe
w odpowiednio zorganizowane dane i algorytm, którego wykonanie można
powierzyć maszynie.
Człowiek i działająca jak algorytm maszyna – to jednak różne byty.
Nasz mózg nie działa tak jak maszyna Turinga. Potrafimy codziennie od-
powiadać sobie na setki pytań, rozwiązywać setki małych i dużych proble-
mów, podejmować setki mniej i bardziej ważnych decyzji, posługując się
doświadczeniem, intuicją, skojarzeniami, analogiami, mądrością życiową…
a więc kryteriami, które są maszynie obce.
Czy ten krem pachnie ładniej niż tamten? Czy to danie jest smaczne?
Czy ta książka jest ciekawsza i lepiej napisana niż tamta? Czy ten pan nie
jest przypadkiem podobny do mojego wuja? Czy powinienem się ożenić
z tą panią (lub wyjść za mąż za tego pana)? Czasami chcielibyśmy, żeby
jakiś niezawodny i genialny komputer pomógł nam w znalezieniu odpo-
wiedzi. Jak jednak każdy z tych problemów opisać w postaci liniowej, jed-
nowymiarowej sekwencji symboli z pewnego skończonego alfabetu? Jak
opisać w postaci formalnego schematu (takiego, jak skończony graf stero-
wania) ciąg kroków, które z pewnością doprowadzą do odpowiedzi „tak”
albo „nie”? Jeśli nie będziemy potrafili tego zrobić – żaden superkomputer
nie wspomoże nas w podejmowaniu decyzji.
Sprawa nie jest jednak wcale beznadziejna. W ogromnej ilości
przypadków już udało się to zrobić. Do przeszłości należą czasy, kiedy
komputery przetwarzały wyłącznie dane, które ze swej natury są od razu
ciągami znaków lub cyfr. Dawno nauczyliśmy się skanować dwuwymia-
rowe barwne obrazy, a więc przerabiać je na liniowy ciąg pikseli opisa-
nych liczbami, które reprezentują jasność trzech barw. Dzięki temu może-
my te obrazy przesyłać na odległość albo zapisywać w pamięci komputera
(tak jak na taśmie maszyny Turinga), a następnie przetwarzać przy uży-
ciu skomplikowanych algorytmów. Umiemy zapisać dźwięk w posta-
ci sekwencji liczb, która potem podlega przetwarzaniu za pomocą odpo-
wiednich algorytmów. Potrafimy przedstawić odciski palców w postaci
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

180 8. Maszyna Turinga

grafu7, którego węzłami są znane kryminologom szczególne punkty połą-


czeń linii daktyloskopijnych. Dzięki temu (i odpowiednim algorytmom)
możemy zatrudnić komputer do wyszukiwania w bazie danych odcis-
ków palców, które są podobne do innych, znalezionych na miejscu przestęp-
stwa. Na podobnej zasadzie próbuje się identyfikować osoby na podstawie
nagrań z ulicznych kamer. Nawet w tak delikatnych i osobistych sprawach,
jak wybór życiowego partnera – istnieją internetowe serwisy randkowe czy
matrymonialne, które przy użyciu odpowiednich kwestionariuszy usiłują
(mniej czy bardziej skutecznie) opisać cechy swych klientek i klientów za
pomocą ciągu parametrów, po to, by na tej podstawie kojarzyć pary, po-
tencjalnie „odpowiadające sobie” w myśl pewnych kryteriów zapisanych
w algorytmie. Choć taka metoda podejmowania życiowych decyzji może
budzić wątpliwości – nie można wykluczyć, że wielu samotnych ludzi zna-
lazło w ten sposób partnera czy partnerkę.
To jedynie kilka pierwszych z brzegu przykładów. Można by je
mnożyć w nieskończoność. Co więcej, liczba sytuacji, w których kom-
putery wspomagają w decyzjach człowieka stale rośnie. Mimo że nasza
pamięć to zupełnie coś innego niż taśma maszyny Turinga, czy pamięć
komputera, a naszymi działaniami nie kierują na ogół algorytmy czy grafy
sterowania – maszyny górują nad nami szybkością oraz odpornością na
zmęczenie i błędy. Dlatego – pod warunkiem że się opracuje odpowiednie
struktury danych i algorytmy – potrafią całkiem skutecznie udawać ludz-
kie zachowania, na przykład grając w szachy, przewidując pogodę, prowa-
dząc wielki samolot, parkując za nas samochód, rozpoznając obiekty na
zdjęciach, czytając odręczne pismo i tak dalej. My jednak już wiemy, że
w istocie potrafią one robić tylko to, co maszyna Turinga: zapisywać i wy-
mazywać umowne znaczki z kolejnych komórek swojej pamięci, zgodnie
z formalnym schematem, który my sami wymyśliliśmy i zadaliśmy im do
wykonania.

7 Przerabianie grafu na sekwencję tekstowych symboli (i odwrotnie) jest łatwe. Na


przykład, jeśli przyjąć, że oznaczenia stanów są jednoznakowe, średnik (;) od-
dziela elementy opisu, a stan początkowy wymienia się na samym początku – to
z następującego napisu:
###111P2;1#1P2;211P2;2##L3;31#L4;3##L4###

każdy z łatwością odtworzy etykietowany graf z rysunku 8.3b, co najwyżej


z dokładnością do wielkości i graficznego rozmieszczenia elementów grafu na
kartce.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Enigma życia i działalności Alana Turinga 181

Możemy czuć się rozczarowani, że komputery są w swej istocie urzą-


dzeniami tak prymitywnymi. Ale możemy także zadziwić się i poczuć dumę
z faktu, że ludziom udało się za pomocą tak teoretycznie ubogich środków
osiągnąć tak wiele.

Enigma życia i działalności Alana Turinga


Ciekawe, że już dawno przed Turingiem liczni matematycy, wynalazcy i my-
śliciele próbowali konstruować prawdziwe (nie tylko hipotetyczne) maszyny
do celów obliczeniowych. Były wśród nich nawet tak ambitne (i w końcu
niezrealizowane w całości) zamierzenia, jak dziewiętnastowieczna maszyna
analityczna Babbage’a8, w założeniu zdolna do wykonywania całych, z góry
zaplanowanych (a więc programowanych) ciągów operacji arytmetycznych.
Co więcej, już na przełomie XIX i XX wieku weszły w praktyczne uży-
cie maszyny analityczno-liczące: elektromechaniczne urządzenia zdolne do
wczytywania dużych ilości danych (uprzednio zapisanych na kartach dziur-
kowanych) i wykonywania na nich sekwencji dość złożonych arytmetycz-
nych obliczeń: dodawań, mnożeń, dzieleń itd., potrzebnych w urzędach sta-
tystycznych, w działalności handlowej lub przy rozliczaniu rachunków za
telefon czy energię elektryczną dla setek tysięcy odbiorców. Jednak żaden

Fot. 8.5. Alan Mathison Turing (1912–1954). Zdjęcie ze zbioru National Portrait Gallery,
Wielka Brytania

8 O tej maszynie i wielu innych urządzeniach do celów obliczeniowych powiemy


więcej w następnych rozdziałach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

182 8. Maszyna Turinga

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

Enigma życia i działalności Alana Turinga 183

Różycki i Henryk Zygalski. Po wybuchu wojny przedostali się oni do


Wielkiej Brytanii, a wyniki ich badań zostały udostępnione brytyjskiemu
wywiadowi. Turing z całą pewnością znał te rezultaty, a prawdopodobnie
również ich autorów.
W warunkach działań wojennych praca nad deszyfrowaniem przeję-
tych depesz niemieckiego dowództwa musiała być szczególnie intensywna.
To był wyścig z czasem: odczytanie zaszyfrowanego rozkazu dla „wilczych
stad” niemieckich łodzi podwodnych polujących na północnym Atlantyku
na alianckie konwoje nie mogło przecież zajmować kilku dni czy tygodni,
jeśli poznanie jego treści miało skutkować odpowiednimi kontrdziałaniami.
Tymczasem ustawienia kodujące w maszynach Enigma zmieniały się zgod-
nie z najtajniejszymi instrukcjami i mogły być każdego dnia inne. Dlatego
należało opracować metody, algorytmy i narzędzia przyspieszające proces
deszyfrowania.
Przed wojną posługiwano się w tym celu pomysłowymi arkuszami
i szablonami. Polscy matematycy już wtedy pracowali także nad skonstru-
owaniem maszyny, która znacznie usprawniłaby proces znajdowania klucza
kodowego. Cały zaawansowany projekt był oczywiście ściśle tajny i miał
polski kryptonim „Bomba”. Maszyna miała być budowana w warszawskich
zakładach PZT.
W początkowym okresie wojny projekt ten urzeczywistniono, budując
(już w Wielkiej Brytanii, pod kierownictwem Turinga) elektromechaniczną
maszynę nazywaną tak samo: „Bomba” (ang. The Bomb). Do końca woj-
ny zbudowano – i intensywnie wykorzystywano – ponad 200 egzemplarzy
tego urządzenia. Później zespół związany z Bletchley Park opracował me-
todę dekodowania komunikatów tekstowych pochodzących z jeszcze innej,
nowszej maszyny szyfrującej (Lorenz SZ 40/42) używanej przez niemiecką
armię. Aby tę metodę sprawnie wykorzystać w praktyce – zbudowano super-
tajny komputer Colossus, który był pierwszą praktycznie działającą maszyną
pracującą w systemie dwójkowym9. Przy końcu wojny wykorzystywano już
w sumie 10 sztuk tych (stopniowo ulepszanych) maszyn.
Z ośrodkiem kryptograficznym w Bletchley Park współpracowała
cała sieć brytyjskiego wywiadu. Na dziesiątki sposobów poszukiwano choć-
by najdrobniejszych wskazówek, które mogłyby sugerować, od czego roz-

9 Nie był to jednak komputer z programem zapisywanym w pamięci.


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

184 8. Maszyna Turinga

począć deszyfrowanie dzisiejszych wiadomości. Pozyskiwano książki kodo-


we i tajne instrukcje z zatapianych lub przechwyconych łodzi podwodnych
i okrętów. Porównywano depesze nadawane za pomocą środków innych, niż
Enigma, w poszukiwaniu nazw, terminów i słów, które mogły się powtarzać
w kilku zakodowanych depeszach. Poprzez sieć agentów usiłowano zdobyć
wszelkie instrukcje i sugestie dotyczące użycia Enigmy. Jednocześnie, przy
użyciu różnych dezinformacyjnych sztuczek dbano o to, by nieprzyjaciel nie
zorientował się, że kod Enigmy został złamany. Doprawdy, z cudem grani-
czy fakt, że wszystko udało się zachować w ścisłej tajemnicy aż do końca
wojny, a nawet jeszcze przez dalsze trzydzieści lat.
Po wojnie, z inicjatywy premiera Winstona Churchilla, maszyny
Colossus zdemontowano i fizycznie zniszczono, a wszelką dokumentację
spalono. Chodziło o zachowanie w tajemnicy faktu, że Wielka Brytania po-
trafi deszyfrować wiadomości kodowane za pomocą maszyn takiego typu,
jak Enigma czy Lorenz, którymi posługiwało się już wówczas wiele państw.
Jednak później, już na początku XXI wieku, zrekonstruowano (i wystawio-
no w muzeach) kilka egzemplarzy tych urządzeń, korzystając przy tym z za-
chowanych notatek i osobistej pamięci starych inżynierów i matematyków
biorących udział w projektach z czasów wojny. Choć po wielu latach, odda-
no im też sprawiedliwość jako pionierom informatyki, którym w najlepszym
okresie ich kariery nie wolno było chwalić się swymi osiągnięciami.
Wojenna działalność brytyjskiego wywiadu, ośrodka w Bletchley Park
i samego Turinga okazała się niesłychanie skuteczna. Niektórzy historycy
oceniają, że działalność zespołu brytyjskich kryptologów (i jej praktyczne
konsekwencje w postaci tysięcy odczytanych depesz i udaremnionych pla-
nów nieprzyjaciela) pozwoliła przyspieszyć klęskę niemieckiej III Rzeszy
o dwa lata, ratując w ten sposób życie zapewne kilku milionom ludzi po obu
stronach konfliktu.
Oczywiście, to trudne do udowodnienia spekulacje. Gdyby wojna
w Europie nie skończyła się w maju 1945 roku, to wcale nie znaczy, że
trwałaby jeszcze dwa lata. Może jej kres by położyło zrzucenie bomb ato-
mowych na Hiroszimę i Nagasaki w sierpniu tegoż roku? A może nie na
Hiroszimę i Nagasaki, ale na Hiroszimę i Berlin? Jak wtedy wyglądałby
nasz dzisiejszy świat? Zostawmy takie rozważania miłośnikom historii al-
ternatywnej. Niemniej, bez wdawania się w takie spekulacje, uważa się, że
złamanie kodu Enigmy było jednym z najważniejszych osiągnięć nauko-
wych, które wpłynęły bardzo znacząco na losy całej II wojny światowej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Enigma życia i działalności Alana Turinga 185

Po wojnie Turing powrócił do cywilnej pracy naukowej, podejmując


w brytyjskim National Physics Laboratory projekt „automatycznej maszyny
liczącej” (ACE, Automatic Computing Engine). W roku 1948 objął stano-
wisko profesora w University of Manchester, gdzie w laboratorium, którego
był wicedyrektorem, konstruowano jedne z pierwszych prawdziwych pro-
gramowanych komputerów w Europie.
Jednak osobiste życie Alana Turinga (i niestety jego tragiczne zakoń-
czenie) także było dalekie od konwencjonalnych wyobrażeń. Turing był homo-
seksualistą, a żył w czasach – zaledwie nieco ponad pół wieku temu! – kiedy
utrzymywanie homoseksualnych kontaktów było w Wielkiej Brytanii przestęp-
stwem zagrożonym karą więzienia.
W roku 1952 jego partner, młody Norweg, włamał się w celach ra-
bunkowych, wraz ze swym kompanem, do domu Turinga. Ten, nieświadom
sprawcy, zgłosił przestępstwo policji, a dochodzenie szybko ujawniło za-
równo związek młodego człowieka z uczonym, jak i charakter tego związ-
ku. Turing niczego się nie wypierał, uważając, że ukrywanie naturalnych
skłonności jest poniżej jego godności. Został skazany na karę więzienia, za-
wieszoną pod warunkiem że podda się rocznej terapii hormonalnej. Pozostał
więc na wolności, lecz odebrano mu prawo dostępu do państwowych tajem-
nic, a dla wyciszenia skandalu skłoniono go do dłuższego urlopu z uczelni.
Usunął się więc w cień i pracował samotnie, w domowym zaciszu.
W czerwcu 1954 roku niespełna czterdziestodwuletniego Alana
Turinga znaleziono martwego w jego domu. Oficjalnie śledztwo zakoń-
czyło się orzeczeniem samobójczego otrucia cyjankiem potasu. To oczywi-
ście możliwe: niełatwa sytuacja osobista, upokarzająca terapia hormonal-
na, wpływająca zarówno na organizm, jak i psychikę uczonego, załamanie
akademickiej kariery, być może kryzys twórczy matematyka wkraczającego
w wiek średni – wszystko to mogło sprawić, że rzeczywiście sam targnął się
na swoje życie.
Jednak nie wszyscy akceptowali ten werdykt: dopuszczano przy-
padkowe otrucie na skutek nieostrożnego obchodzenia się z chemikaliami,
ale nie brakło także podejrzeń o zabójstwo. Miały w nim rzekomo maczać
palce służby specjalne, które bardzo nie lubią, kiedy ich współpracowni-
cy, dopuszczeni do pilnie strzeżonych tajemnic, są jednocześnie zamieszani
w obyczajowe skandale i podatni na ewentualny szantaż. Tak czy inaczej
– taki był tragiczny koniec życia uczonego o nieprzeciętnym umyśle, szero-
kich horyzontach intelektualnych i wielkich dokonaniach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

186 8. Maszyna Turinga

Turingowi zawdzięczamy także i to, że w 1950 roku wprowadził do


światowego słownika pojęcie sztucznej inteligencji (ang. AI – artificial in-
telligence). W jednej z prac wyraził bowiem przekonanie, że maszyny ob-
liczeniowe – komputery – już wkrótce zostaną udoskonalone do tego stop-
nia, iż będą się cechowały inteligencją, praktycznie nieróżniącą się od tej,
którą przypisujemy ludziom. Ten pogląd (daleko wybiegający w przyszłość,
zwłaszcza jeśli się wie, jakie możliwości oferowały współczesne Turingowi
komputery) wzbudził niesłychane emocje i gorące dyskusje nie tylko w krę-
gu ludzi nauki, ale także wśród szerszej publiczności.
Aby przeciąć coraz bardziej jałowe spory o to, co w istocie inteligencją
jest, a co nie – Turing zaproponował proste doświadczenie, później nazwa-
ne testem Turinga (ang. Turing test). Wyobraźmy sobie – powiedział – że
pewnego dnia ktoś ogłosi, iż zbudował inteligentną maszynę. Zainstalujmy
ją wtedy w jednym pokoju (niech się nazywa pokój A), w drugim pokoju (B)
posadźmy człowieka, a w trzecim zgromadźmy zespół sędziów czy jurorów.
Niech sędziowie nie wiedzą, w którym pokoju jest maszyna, a w którym
– człowiek. Pozwólmy sędziom zadawać na karteczkach dowolne pytania
skierowane do pokoi A oraz B. Dla zachowania tajemnicy, niech między po-
mieszczeniem jury a pokojami A i B kursuje posłaniec, który będzie zanosił
pytania i po chwili wracał z udzielonymi odpowiedziami.
Po serii pytań i odpowiedzi niech sędziowie przeprowadzą głosowa-
nie: kto uważa, że w pokoju A jest maszyna, a kto sądzi, że maszyna jest
w pokoju B? Jeżeli okaże się, że głosy rozkładają się mniej więcej po po-
łowie albo nawet większość sędziów pomyli się co do tożsamości rozmów-
ców z pokoi A i B – to uznamy, że inteligentną maszynę istotnie udało się
skonstruować.
Nieograniczony test Turinga (a więc taki, w którym pytania mogą
dotyczyć zupełnie dowolnych kwestii, a czas trwania „egzaminu” może być
dowolnie długi) nigdy się nie powiódł i zapewne nigdy się nie powiedzie.
Jednak przy wyraźnym ograniczeniu tematyki konwersacji i długości sesji
pytań można oczekiwać, że już dziś zespół jurorów mógłby mieć poważne
trudności z prawidłowym odróżnieniem człowieka od maszyny.
Wystarczy choćby przypomnieć, że w roku 1997, w sławnym me-
czu do sześciu partii, system Deep Blue (zaprojektowany i oprogramowany
przez zespół naukowców z IBM Corp.) pokonał ówczesnego szachowego
mistrza świata Garri Kasparowa w stosunku 2:1 (przy trzech remisach).
Wiadomo, że Deep Blue nie miał wręcz żadnej inteligencji, nie potrafił
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Enigma życia i działalności Alana Turinga 187

się uczyć itd., a górował nad Kasparowem przede wszystkim szybkością,


umożliwiającą mu w ciągu jednej sekundy ocenę ok. 200 000 pozycji na
szachownicy. Jednak zespół jurorów, nawet dobrych szachistów, mógłby się
w teście Turinga (ograniczonym do tematyki szachowej) łatwo pomylić co
do tożsamości człowieka i maszyny.
Podobnie od lat znane są programy, które potrafią dość udatnie na-
śladować banalną konwersację na niewiele znaczące tematy, wykorzystując
zwroty użyte przez „ludzkiego” rozmówcę, zamieniając jego stwierdzenia
na pytania, pytania na niby-odpowiedzi itd. Choć taka konwersacja jest pu-
sta i szybko staje się nieciekawa – niewiele odbiega od treści np. wielu
internetowych czatów i też może zwieść rozmówcę, który przez kilkanaście
minut nie zorientuje się, że ględzi z internetowym botem, a nie z innym
użytkownikiem sieci.
Test Turinga jest powtarzany od czasu do czasu, co kilka lat, ale nie
jako wydarzenie naukowe, tylko raczej przedsięwzięcie medialne, na przy-
kład służące promocji jakiejś firmy, większej wystawy czy imprezy targo-
wej. Niemniej poważnymi badaniami nad sztuczną inteligencją zajmują się
na całym świecie setki zespołów naukowych, które mają na swym koncie
wiele fascynujących i użytecznych wyników, tyle że nie jest ich celem na-
śladowanie czy udawanie człowieka. Badania dotyczą algorytmów uczenia
się maszyn (ang. machine learning), rozpoznawania wzorców (ang. pattern
recognition), wyszukiwania nieznanych dotąd reguł ukrytych w ogromnych
zbiorach danych (ang. data mining), automatycznego dowodzenia twierdzeń
(ang. theorem proving) i wielu innych dziedzin, w których techniczne możli-
wości współczesnych komputerów (zwłaszcza ich szybkość i zdolność prze-
chowywania ogromnych ilości danych) mogą wspomóc człowieka w pozna-
waniu złożonej rzeczywistości i próbach sensownego nią kierowania.
A Alan Turing jest – słusznie – uważany za jednego z najbardziej
zasłużonych ojców założycieli współczesnej informatyki.
Warto dodać, że we wrześniu 2009 roku brytyjski premier Gordon
Brown, nawiązując do rocznicy wybuchu II wojny światowej, oddał hołd
pamięci Alana Turinga i przeprosił za to, w jaki sposób został on potrakto-
wany przez obowiązujące w jego czasach prawo i wymiar sprawiedliwości.
Brown zaliczył Turinga do grona nie tylko wybitnych uczonych, ale
i bohaterów wojennych, zasługujących na to miano nie mniej niż żołnierze
lądujący w 1944 roku na plażach Normandii. Wszyscy oni walczyli z sys-
temem, u którego podstaw leżał rasizm, skrajny nacjonalizm, nietoleran-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

188 8. Maszyna Turinga

cja i nieposzanowanie godności jednostki. Tym boleśniejszym paradoksem


jest fakt – zauważył Brown – że Alan Turing, już w czasach pokoju, sam
padł ofiarą obyczajowej nietolerancji, utrwalonej w ówczesnym brytyj-
skim prawie.
Ten gest brytyjskiego premiera dodatkowo skłania do refleksji nad
trudnymi losami narodów i pojedynczych ludzi, uplątanych w historię, poli-
tykę i problemy obyczajowe.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

9. O lingwistyce matematycznej

Czy lingwistyka może być matematyczna?


Po przeczytaniu poprzednich rozdziałów nie mamy chyba wątpliwości co
do tego, jak wielką rolę odgrywa nie tylko w informatyce, ale w naszym
myśleniu, rozumieniu świata, wnioskowaniu – zdolność do tworzenia, zapi-
sywania i przetwarzania pewnych sekwencji, złożonych z umownych sym-
boli, które pochodzą ze skończonego alfabetu.
Ale czy do tego, by taki wniosek wyciągnąć, rzeczywiście potrzebna
jest aż znajomość tezy Churcha–Turinga i zrozumienie różnic między ana-
logowym i cyfrowym sposobem wykonywania obliczeń? Przecież w istocie
mówimy tu o zjawisku znanym każdemu od dziecka: o języku.
Rolą języka jest właśnie wyrażanie faktów, zdarzeń, zjawisk, a także
uczuć i pojęć abstrakcyjnych – w formie ciągów (złożonych ze znaków,
symboli czy dźwięków), zwanych potocznie wyrazami, zdaniami, wypowie-
dziami, raportami..., a także artykułami, powieściami, poematami, podręcz-
nikami... Ciągi te, zapisywane lub wypowiadane, przechowywane na glinia-
nych tabliczkach, kartach książek lub pendrive’ach, przesyłane przy użyciu
gońców, gołębi pocztowych lub satelitarnego łącza internetowego – stano-
wią od wieków nieodłączną część naszej kultury.
Jednocześnie wiemy, że (przynajmniej od czasu, gdy próbowano zbu-
dować wieżę Babel) istnieje ogromna mnogość różnych języków. Są one
tak różnorodne i czasami tak do siebie niepodobne, że można powziąć wąt-
pliwość, czy mają w ogóle coś między sobą wspólnego. Różnią się one od
siebie zarówno zasobem używanych słów (słownikiem), jak i gramatyką,
to znaczy regułami konstruowania wyrazów i składania ich w dłuższe jed-
nostki: frazy, zdania proste, zdania złożone... W wersji mówionej są jeszcze
reguły artykulacji, akcentowania, intonacji itd., sprawiające, że każdy język
ma swoją „melodię”. W wersji pisanej – dochodzą różnice w graficznej for-
mie stosowanych alfabetów (łaciński, cyrylica, grecki, arabski, hebrajski...),
różnice co do reguł poprawnego pisania (ortografia), a nawet zasad pięk-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

190 9. O lingwistyce matematycznej

nego pisania (kaligrafia), tak ważnych na przykład dla języka arabskiego,


chińskiego czy japońskiego.
Stąd bierze się nie tylko wrażenie oszałamiającej różnorodności, ale
również przypuszczenie, że trudno o większą sprzeczność niż między lin-
gwistyką a matematyką. Gdyby tak było w rzeczywistości – termin „lin-
gwistyka matematyczna” byłby oksymoronem, wyrażeniem wewnętrznie
sprzecznym, podobnie jak na przykład „górzysta równina”.
A jednak... Już z tego, co napisaliśmy wyżej, wynika, że wszyst-
kie języki jednak mają coś wspólnego: właśnie to, że każdy z nich ma ja-
kiś alfabet, jakiś słownik i jakąś gramatykę.
Gdybyśmy potrafili wznieść się ponad dzielące je różnice i zdefinio-
wać formalnie, co to w ogóle jest język, słownik, gramatyka; gdybyśmy
umieli zaproponować, jak można formalnie zapisywać różne reguły grama-
tyczne itd. – zyskalibyśmy narzędzie pojęciowe, które otworzyłoby zupeł-
nie nowe możliwości nie tylko w badaniach językoznawczych, lecz także
w informatyce i wielu pokrewnych dziedzinach. Matematyka może nam
podsunąć sposób na formalnie poprawne zdefiniowanie tych podstawowych
pojęć.
Próbując omówić niżej podstawowy formalny model języka i grama-
tyki, ograniczymy się do języka pisanego. W przypadku języka mówionego
dość trudno jest oddzielić samą językową wypowiedź od mówiącej osoby:
od jej wyglądu, sposobu artykulacji, intonacji, akcentu, mowy ciała i wresz-
cie całego sytuacyjnego i emocjonalnego kontekstu, w którym wypowiedź
jest osadzona. W języku pisanym formalna, sekwencyjna natura wypowie-
dzi jest wyraźniej widoczna.

Język jako zbiór


W matematyce, do skonstruowania przyzwoitej definicji powinny wystar-
czyć takie pojęcia, jak zbiór, relacja, funkcja. Weźmy więc na początek
zbiór A, zawierający – dla prostoty – tylko dwa symbole; powiedzmy, że
A = {a, b}. Niech będzie on alfabetem pewnego cyfrowego sposobu zapi-
sywania informacji. Wiemy już, że wtedy wszelkie informacje: dane, in-
strukcje, opinie, wypowiedzi... – powinny być reprezentowane przez ciągi
(sekwencje) symboli pochodzących z alfabetu A.
Teraz wykonajmy ważne i wcale niełatwe ćwiczenie na wyobraźnię.
Wyobraźmy sobie mianowicie zbiór wszystkich możliwych sekwencji (o do-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Język jako zbiór 191

wolnej, niczym nieograniczonej długości), jakie można napisać używając tych


dwóch liter. Zbiór taki zawiera:
– sekwencję pustą (niezawierającą żadnego symbolu, która odgrywa tu rolę
taką jak zero w zbiorze liczb naturalnych. To takie ważne nic, które jed-
nak istnieje);
– wszystkie możliwe sekwencje jednoliterowe (tu są takie dwie, mianowi-
cie a oraz b);
– wszystkie możliwe sekwencje dwuliterowe (tu są dokładnie cztery, mia-
nowicie aa, ab, ba, bb);
– wszystkie możliwe sekwencje trzyliterowe (osiem: aaa, aab, aba, abb,
baa, bab, bba, bbb);
– podobnie, wszystkie sekwencje cztero-, pięcio-, ..., dziesięcio-, ..., stu-,
..., tysiącliterowe... i tak dalej, bez ograniczania długości sekwencji.
Tak utworzony (oczywiście nieskończony) zbiór oznaczymy przez A*
i nazwiemy zbiorem ciągów (sekwencji) nad alfabetem A.
Formalna definicja języka jest zaskakująco lakoniczna: językiem (nad
alfabetem A) jest każdy podzbiór zbioru A*.
Czy ta ujmująca prostotą definicja ma coś wspólnego z takim pojmo-
waniem języka, do jakiego przywykliśmy w codziennym życiu? Oczywiście
tak, trzeba tylko popatrzeć z odpowiedniego punktu widzenia.
Przypomnijmy dla porządku, że mówimy tu o języku pisanym i wy-
obraźmy sobie teraz inny alfabet A, który składa się nie z dwóch, lecz ze stu
kilkudziesięciu znaków. Niech należą do niego wszystkie łacińskie i polskie
litery (duże i małe), cyfry dziesiętne, symbole specjalne (jak $, %, @ itd.)
oraz znaki przestankowe, łącznie ze spacją. Znamy ten alfabet dobrze: wy-
starczy popatrzeć na klawiaturę naszego komputera.
Teraz wyobraźmy sobie zbiór A* nad tym nowo zdefiniowanym al-
fabetem A. Podobnie jak w poprzednim, prościutkim przykładzie, zbiór A*
jest nieskończony i zawiera sekwencję pustą, wszystkie sekwencje jedno-
znakowe, wszystkie dwu-, trzy-, czteroznakowe... i tak dalej.
Ponieważ A* zawiera – z definicji – wszystkie możliwe sekwencje nad
alfabetem A, to są wśród nich także – między innymi – wszystkie polskie
wyrazy, wszystkie polskie zdania, wszystkie polskie wypowiedzi, wszystkie
polskie poematy, wszystkie polskie książki... i tak dalej. I to nie tylko te,
które znamy: także takie zdania, których jeszcze nie wypowiedziano, książ-
ki których jeszcze nie napisano, ale które można by napisać w polskim ję-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

192 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-

1 W arytmetyce liczb naturalnych już się do tego przyzwyczailiśmy: na przykład


zbiory liczb parzystych i liczb nieparzystych są nieskończone, a jednocześnie są
podzbiorami (również nieskończonego) zbioru liczb naturalnych, itp.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Język jako zbiór 193

legający na oddzieleniu formy języka od jego treści jest zamierzony i celo-


wy. Nie inaczej zresztą postępują od wieków językoznawcy, specjalizujący
się w opisowej gramatyce języków naturalnych. Nie mówią oni wprawdzie
o sekwencjach i zbiorach, lecz (równie beznamiętnie) o rzeczownikach, cza-
sownikach i przymiotnikach, częściach zdania i częściach mowy itd. – nie-
zależnie od znaczeń i emocji, które im się przypisuje.
Pozostaje jednak inna ważna wątpliwość. Podana wyżej formal-
na definicja języka wydaje się mocno niepraktyczna i niekonstruktywna.
Powiedzmy, że chcemy napisać poprawny program w języku programowa-
nia C (albo poprawne zdanie w języku polskim). Świadomość tego, że taki
ciąg symboli teoretycznie już znajduje się gdzieś wśród nieskończonej licz-
by sekwencji tworzących dany język – niewiele nam pomoże przy konstru-
owaniu konkretnego, poprawnego programu czy zdania.
Podobnie ktoś pokazuje nam długi ciąg znaków, twierdząc, że jest to
program zapisany w języku C (albo zdanie w języku polskim czy angielskim).
Chcielibyśmy wiedzieć, czy jest to prawda. Jeżeli tak jest, to dany ciąg sym-
boli należy do odpowiedniego podzbioru nad zadanym alfabetem. Ponieważ
jednak ów podzbiór, czyli język, może być (i najczęściej jest) nieskończony
– nie możemy go sobie w całości wydrukować (albo zapisać w pamięci kom-
putera) i potem drogą przeszukiwania próbować stwierdzić, czy zadany ciąg
znaków tam rzeczywiście się znajduje.
Potrzebujemy więc jakiegoś zestawu zasad czy reguł, które umożli-
wiłyby nam praktyczne rozwiązywanie obu wspomnianych rodzajów pro-
blemów, to znaczy, po pierwsze – produkowania nowych ciągów popraw-
nych w danym języku, po drugie – orzekania, czy pewien dany ciąg jest
poprawny.
Taki zestaw reguł musi być koniecznie skończony, tak by można
było je wszystkie wyliczyć, wypisać w postaci listy, a choćby nawet całego
podręcznika, ale o skończonej długości. Nieskończony zbiór zasad byłby
oczywiście równie niepraktyczny i niekonstruktywny, jak sam nieskończony
język.
Taki skończony zestaw reguł – to oczywiście gramatyka danego ję-
zyka. Poszukujemy więc teraz pilnie formalnego, matematycznego modelu
gramatyki.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

194 9. O lingwistyce matematycznej

Model gramatyki kombinatorycznej Chomsky’ego


i notacja BNF
Udany krok w kierunku formalizacji gramatyki jest zasługą Noama
Chomsky’ego. W latach pięćdziesiątych XX wieku zaproponował on defi-
nicję gramatyki kombinatorycznej. Przedstawimy ją niżej. Aby się z tą ideą
zapoznać, musimy wprowadzić najpierw kilka terminów i oznaczeń, które
w pierwszej chwili mogą się wydawać niejasne. Jednak zaraz potem przy-
toczymy prosty (i nie całkiem poważny) przykład, który uświadomi nam
naturalność tych pojęć, a także siłę i elegancję całej koncepcji.
Zgodnie z pomysłem Chomsky’ego, gramatyka jest następującą upo-
rządkowaną czwórką:

G = <S, T, P, s0 >;

co w żargonie matematyków znaczy, że dla zdefiniowania gramatyki G trze-


ba i wystarcza dobrze określić takie właśnie cztery elementy, przy czym:
– S oznacza słownik, a więc skończony zbiór symboli (jak gdyby elemen-
tarnych wyrazów), które występują w danej gramatyce;
– T jest podzbiorem słownika S, który zawiera symbole końcowe (inaczej
– terminalne, stąd T);
– P jest skończonym zbiorem produkcji, czyli podstawień. Każde z podsta-
wień definiuje pewną regułę językową;
– s0 jest symbolem początkowym, czyli aksjomatem języka. Aksjomat nie
może należeć do zbioru symboli końcowych.
Towarzysząca tej definicji interpretacja podaje przepis na produkowa-
nie (generowanie) poprawnych składniowo konstrukcji językowych, dlatego
mówi się, że Chomsky’ego model gramatyki jest modelem generatywnym.
Szczegóły tego przepisu poznamy za chwilę. Teraz powiemy jedynie, że
polega on na tym, by zacząć od napisu zawierającego tylko symbol po-
czątkowy, a następnie wielokrotnie przepisywać i modyfikować ten napis
(zamieniając jego części na inne zgodnie z regułami podstawiania) aż do
chwili, gdy zawiera on same elementy końcowe ze słownika T. Taka osta-
teczna konstrukcja jest wtedy na pewno poprawna.
Słownik S zawiera symbole, które (w ramach danej gramatyki) uzna-
jemy za elementarne i niepodzielne, podobnie jak przedtem traktowaliśmy
symbole alfabetu. Nie znaczy to oczywiście, że koniecznie muszą one być
reprezentowane przez pojedyncze znaki graficzne. Definiując słownik, mo-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Model gramatyki kombinatorycznej Chomsky’ego i notacja BNF 195

żemy życzyć sobie, by pewna grupa graficznych symboli – na przykład iks


– była traktowana łącznie jako niepodzielna całość, a nie jako ciąg zbudo-
wany z trzech oddzielnych symboli: i, k oraz s.
W tym miejscu musimy wdać się w dygresję, bez której bardzo trud-
no byłoby kontynuować omawianie poszczególnych elementów modelu
gramatyki Chomsky’ego. Musimy mianowicie wprowadzić pewną notację,
która pozwoli na definiowanie niepodzielnych grup znaków, na odróżnianie
symboli końcowych od niekońcowych, na skrótowe zapisywanie podsta-
wień (produkcji) należących do zbioru P itd. Posłużymy się tu notacją BNF,
powstałą w latach sześćdziesiątych XX wieku i dobrze znaną wszystkim
informatykom.
Skrót BNF bywa rozwijany dwojako. O gramatykach opisanych przy
użyciu notacji BNF mówi się jako o gramatykach w postaci normalnej
Backusa (ang. Backus Normal Form) albo w postaci Backusa–Naura (ang.
Backus-Naur Form), od nazwisk dwóch matematyków (John Backus, Peter
Naur), którzy ten formalizm zaproponowali. Istnieje wiele wersji i rozsze-
rzeń notacji BNF, my zaczerpniemy z niej tylko kilka najbardziej podsta-
wowych oznaczeń, powtarzających się we wszystkich wersjach i absolutnie
niezbędnych dla naszych rozważań.
Przyjmijmy mianowicie, że:
– symbole należące do słownika S mogą być dowolnymi ciągami znaków, ale
bez spacji w środku2. Dla odróżnienia od zwykłych, polskich wyrazów reszty
naszego tekstu będziemy je pisali inną, taką czcionką. Robiliśmy to zresztą
już wielokrotnie;
– symbole niekońcowe (a więc nienależące do zbioru T) będziemy dodatko-
wo ujmowali w nawiasy kątowe (< oraz >);
– do zapisywania reguł składniowych (tworzących zbiór P) wykorzystamy
następujące oznaczenia:
• grupa znaków ::= będzie oznaczała „jest zdefiniowane jako...” albo
„można podstawić...”. Na przykład, <aaa> ::= <bbb> będzie oznacza-
ło, że „zamiast <aaa> można podstawić <bbb> ”;

2 W zasadzie dowolnymi, bo z wyłączeniem metasymboli, o czym mowa kilkanaście


wierszy dalej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

196 9. O lingwistyce matematycznej

• pionowa kreska | oznacza „lub”. Na przykład, <aaa> ::= <bbb>|ccc;


będzie oznaczało, że zamiast symbolu niekońcowego <aaa> można
podstawić symbol niekońcowy <bbb> lub symbol końcowy ccc;
• średnik ; będzie oznaczał koniec reguły.
Wprowadzone dodatkowe oznaczenia (a więc ::=, |, <, >, ; ) są
nazywane metasymbolami notacji BNF. Są one zarezerwowane dla notacji
BNF, która sama jest metajęzykiem, czyli językiem służącym do opisu języ-
ka. Dlatego metasymbole nie powinny być używane „wewnątrz języka”, to
znaczy jako symbole definiowanego języka (czy też ich części składowe)3.
Przyjmijmy dodatkowo, że symbole końcowe będziemy oddzielali od
innych spacjami (znakami odstępu). Spację można jednak pominąć, jeżeli
tylko nie będzie to prowadziło do nieporozumień. To naturalna konwencja,
bardzo ułatwiająca życie. Dla przykładu, wolno nam będzie napisać 5a za-
miast 5 a, ale nie napiszemy 57 tam, gdzie chodzi o dwie oddzielne cyfry: 5
oraz 7, a nie o liczbę 57.

Panie gryzą psy ponieważ dzieci lubią koty


Pora na przykład, który pokaże, że nie jest to tak skomplikowane, jak się na
pierwszy rzut oka wydaje. Wymyślmy sobie gramatykę, która ma następują-
cy zbiór P (zbiór podstawień, czyli produkcji):
<zdanie> ::= <zdanie_proste> | <zdanie_złożone>;
<zdanie_proste> ::= <podmiot> <orzeczenie> <dopełnienie>;
<zdanie_złożone> ::= <zdanie_proste> <łącznik> <zdanie_proste>;
<podmiot> ::= <rzeczownik>;
<orzeczenie> ::= <czasownik>;
<dopełnienie>::= <rzeczownik>;
<rzeczownik> ::= dzieci | psy | koty | panie;

3 Jeżeli któryś ze znaków, używanych jako metasymbol musi występować „we-


wnątrz” języka – jest ujmowany w apostrofy (‘oraz’). Dla przykładu, jeśli znak <
ma występować w wyrażeniach matematycznych jako jeden z końcowych sym-
boli (np. oznaczający „mniejszy od...”) to powinien występować jako ‘<’. Ten
sam znak bez apostrofów będzie wtedy nadal jednym z metasymboli: jednym
z kątowych nawiasów obejmujących symbole niekońcowe. A co z apostrofem
wewnątrz języka? Są różne konwencje – najprostsza polega na użyciu dodatkowo
cudzysłowów: apostrof zapisuje się jako "'", a cudzysłów jako '"'.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Panie gryzą psy ponieważ dzieci lubią koty 197

<czasownik> ::= lubią | gryzą | biją | gonią;


<łącznik> ::= chociaż | ponieważ | albowiem;
Cokolwiek te reguły oznaczają, występuje w nich 20 różnych wyrazów.
Pełny ich zestaw, czyli cały słownik tej gramatyki, jest więc następujący:
S = { <zdanie>, <zdanie_proste>, <zdanie_złożone>, <podmiot>,
<orzeczenie>, <dopełnienie>, <rzeczownik>, <czasownik>,
<łącznik>, dzieci, psy, koty, panie, lubią, gryzą, biją, gonią,
chociaż, ponieważ, albowiem }
Stanowią one elementarne symbole, z których każdy mamy traktować jako
niepodzielną całość.
Niech wyróżnionym symbolem początkowym będzie s0 = <zdanie>.
Jest to więc gramatyka „zdań” w naszym sztucznym języku.
Pierwszych dziewięć symboli słownika S jest ujętych w kątowe na-
wiasy (<zdanie>, <zdanie_proste>4... itd.). Zgodnie z konwencją BNF
są to więc symbole niekońcowe. Pozostałe jedenaście – to symbole bez na-
wiasów kątowych, a więc symbole końcowe (terminalne). Tworzą one na-
stępujący zbiór T:
T = { dzieci, psy, koty, panie, lubią, gryzą, biją, gonią, chociaż,
ponieważ, albowiem }.
Komentarza wymaga jeszcze konstrukcja zbioru podstawień P, który zapi-
saliśmy wyżej w postaci dziewięciu wierszy.
Pierwszy wiersz mówi, że pod symbol <zdanie> można podstawić
<zdanie_proste> lub <zdanie_złożone>. Mówiąc prościej, w tej gramaty-
ce <zdanie> to może być <zdanie_proste> albo <zdanie_złożone>. Mamy
tu w zasadzie do czynienia z dwoma podstawieniami (produkcjami):

<zdanie> ::= <zdanie_proste>;


<zdanie> ::= <zdanie_złożone>;
tyle że użycie wygodnego metasymbolu | („lub”) skraca zapis do jednego
wiersza. Także – na przykład – wiersz definiujący <rzeczownik> zawie-
ra cztery podstawienia, zapisane w podobnie skrótowy sposób itd. Łatwo

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

198 9. O lingwistyce matematycznej

policzyć, że reguł składniowych w zbiorze P jest w sumie 18, choć są one


zapisane w 9 wierszach.
Oczywiście, metasymbol „lub” występuje nie we wszystkich regu-
łach. Na przykład, reguła podstawienia:

<zdanie_proste> ::= <podmiot> <orzeczenie> <dopełnienie>;


mówi, że za jeden symbol <zdanie_proste> można podstawić ciąg złożony
z trzech symboli: <podmiot> <orzeczenie> <dopełnienie>, napisanych tuż
po sobie w tej właśnie kolejności.
Zdefiniowaliśmy w ten sposób wszystkie cztery elementy uporządkowa-
nej czwórki postulowanej przez Chomsky’ego: mamy więc gramatykę naszego
sztucznego języka. Jak możemy teraz tę definicję wykorzystać do konstruowa-
nia poprawnych (a więc „gramatycznych”) wypowiedzi w tym języku?
Idea Chomsky’ego jest następująca. Symbol początkowy s0 został
wyróżniony właśnie po to, żeby było wiadomo, od czego zacząć. Weźmy
więc kartkę papieru i napiszmy na niej:

<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

Panie gryzą psy ponieważ dzieci lubią koty 199

Tu znów mamy dowolność. Możemy zająć się którymkolwiek z trzech


napisanych symboli. Powiedzmy, że akurat spodobało nam się zająć środko-
wym z nich. Zgodnie z regułą <orzeczenie> ::= <czasownik> zastępujemy
go przez <czasownik>, a pozostałe dwa – co bardzo ważne – przepisujemy
bez zmian. Powstaje w ten sposób wiersz czwarty:

<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

200 9. O lingwistyce matematycznej

<rzeczownik> lubią <dopełnienie>


dzieci lubią <dopełnienie>
dzieci lubią <rzeczownik>
dzieci lubią psy
Drogą kolejnych podstawień dotarliśmy więc do ciągu złożonego
z trzech symboli końcowych, dla których nie ma już dalszych podstawień.
Wyprowadziliśmy ten ciąg z aksjomatu <zdanie> poprzez wielokrotne za-
stosowanie „legalnych” dla tej gramatyki podstawień (produkcji) z jej zbioru
P, więc możemy z czystym sumieniem powiedzieć, że napis dzieci lubią
psy jest zdaniem składniowo poprawnym, potocznie mówiąc – „gramatycz-
nym”, w sensie tak zdefiniowanej gramatyki.
Zauważmy, że po drodze dokonaliśmy szeregu zupełnie arbitralnych
(ale zgodnych z zasadami) wyborów. Gdyby nasze decyzje były inne, mog-
libyśmy na końcu otrzymać zdanie psy biją dzieci albo panie gryzą koty
i tak dalej. Gdybyśmy w pierwszym kroku podstawiania wybrali produkcję
<zdanie> ::= <zdanie_złożone>, to możliwości byłoby jeszcze więcej: nie
tylko dzieci lubią koty chociaż koty gryzą psy, ale także panie biją
dzieci ponieważ psy lubią koty i wiele, wiele innych podobnych rewe-
lacji.
No właśnie, jak wiele? Kilkadziesiąt? Sto?
To daje się łatwo obliczyć, a rezultat może nieco zaskoczyć osoby
nieprzyzwyczajone do specyfiki obliczeń kombinatorycznych. Słownik
końcowy T liczy tylko 11 wyrazów, a reguły składniowe dały się zapisać
w raptem 9 wierszach notacji BNF. Cóż to za prymitywny język? Ale jed-
nak różnych „zdań prostych” zgodnych z tą gramatyką można wyproduko-
wać 4 ⋅ 4 ⋅ 4 = 64, na tyle bowiem sposobów można zestawić cztery „rze-
czowniki” („podmioty”), cztery „czasowniki” („orzeczenia”) i znów cztery
„rzeczowniki” (jako „dopełnienia”). „Zdań złożonych” jest w takim razie
64 ⋅ 3 ⋅ 64 = 12 288. Tak więc język generowany przez tę gramatykę, czyli
zbiór wszystkich możliwych „zdań”, liczy dokładnie 64 + 12 288 = 12 352
zdania. Nieźle! Gdybyśmy je wypisali wszystkie, umieszczając każde ze
zdań w jednym wierszu i drukując ich np. po 50 na jednej stronie, to po-
wstałaby książka licząca ponad 240 stron! To całkiem niezłe możliwości
wypowiedzi.
Powie ktoś, że większość z tych zdań jest kompletnie nonsensowna.
Czy ktoś jednak zapowiadał, że będziemy się w ogóle zajmowali sensem
wyprodukowanych zdań?
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Panie gryzą psy ponieważ dzieci lubią koty 201

Poważnie mówiąc, język, oprócz składni, ma też swoją semantykę,


to znaczy reguły przypisywania poprawnym konstrukcjom językowym
znaczeń. Konstrukcje niepoprawne są po prostu niepoprawne i ich zna-
czeniem nie musimy się kłopotać5. Model gramatyki kombinatorycznej
Chomsky’ego koncentruje się na składniowej poprawności języka, nie zaj-
mując się jego stroną semantyczną. W szczególności, dla modelu gramatyki
kombinatorycznej nie jest ważne, czy wyprodukowane zdania są prawdziwe
czy fałszywe, czy rzetelnie opisują jakąś rzeczywistość itd. Istotna jest tylko
konstrukcyjna poprawność struktur językowych, które można wyprowadzić
z symbolu początkowego danej gramatyki.
Nie ma jednak w tym nic zdrożnego. Każda, nawet najbardziej tra-
dycyjna w formie, opisowa gramatyka języka naturalnego formułuje reguły
odmiany czasowników czy rzeczowników, użycia czasów, reguły poprawnej
budowy zdań itd., w zasadzie w oderwaniu od semantyki zdania. W każdym
języku naturalnym jest możliwe napisanie zdania poprawnego gramatycz-
nie, a jednocześnie semantycznie (a więc co do znaczenia) dziwacznego,
absurdalnego czy komicznego.
Niektóre z nich są mimo to w powszechnym użyciu, jako idiomy, któ-
rych nie rozumiemy dosłownie. Czy nie moglibyśmy na przykład umówić
się, że zdanie panie gryzą panie oznacza – powiedzmy – wielką awanturę?
Przecież w polskim języku też czasami „panie drą koty” i tego zwrotu rów-
nież dosłownie nie odczytujemy. Często zdanie (albo jakiś zwrot czy fraza)
ma wiele znaczeń. Zabawa znaczeniami, niespodziewanymi skojarzeniami
i niespotykanymi zestawieniami słów – nierzadko stanowi o bogactwie ję-
zyka i daje czytelnikowi przyjemność obcowania z dobrą literaturą czy po-
ezją.
Tak się dzieje w przypadku języków naturalnych. Powstają one
i zmieniają się w skali historycznej i społecznej, w sposób spontaniczny. Ich
gramatykę i semantykę możemy obserwować, poznawać i opisywać, choć
(mimo widocznych starań uczonych językoznawców i komitetów poprawno-
ści językowej) mamy raczej niewielki wpływ na ich kształt. Jednak posługu-
jąc się modelem Chomsky’ego, możemy także konstruować sztuczne języki,
służące bardzo praktycznym celom. W takim przypadku nadmiar możliwo-

5 Z drugiej strony, nie wszystkie konstrukcje gramatycznie poprawne muszą mieć


przypisane znaczenie. Mamy wtedy prawo powiedzieć, że są bezzsensowne.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

202 9. O lingwistyce matematycznej

ści wypowiedzi i ich ewentualna wieloznaczność – stanowią bardziej wadę


niż zaletę. Dlatego języki programowania, języki opisu złożonych danych
itp., na których gramatykę mamy przecież wpływ, mają z zasady możliwie
proste reguły składniowe, prowadzące do jednoznaczności i bezkonteksto-
wości6 tworzonych konstrukcji językowych.

Języki skończone i nieskończone


Dowiedzieliśmy się w ten sposób, że gramatyka G = <S, T, P, s0> generu-
je pewien język nad zdefiniowanym w niej alfabetem końcowym T. Istotnie,
każde wyprodukowane w opisany sposób zdanie jest ciągiem składającym się
wyłącznie z symboli należących do T, a spośród nieskończonej liczby wszyst-
kich możliwych sekwencji nad T tylko niektóre daje się wyprowadzić z sym-
bolu początkowego przez użycie produkcji ze zbioru P. Razem tworzą one
pewien język, stanowiący podzbiór zbioru T*.
Język, którym się przed chwilą zajmowaliśmy, choć nadspodziewanie
bogaty (przynajmniej jeśli chodzi o liczbę składniowo poprawnych „zdań”)
– jest jednak skończony. Wyliczyliśmy, że poprawnych zdań jest w nim do-
kładnie 12 352 i ani jedno więcej. Czy można w podobny sposób (a więc
mając skończony słownik i zbiór P, zawierający skończoną liczbę reguł)
zdefiniować język nieskończony, to znaczy taki, w którym poprawnych
składniowo konstrukcji byłoby nieskończenie wiele? Poniższy przykład po-
może odpowiedzieć na to pytanie.
Popatrzmy na następujący zbiór reguł:

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;

6 O językach bezkontekstowych i kontekstowych wspomnimy dalej.


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Języki skończone i nieskończone 203

Niech <liczba_całkowita> będzie symbolem początkowym tej gra-


matyki. Jest to więc opis składni „liczb całkowitych”, dość dobrze zresztą
– jak zobaczymy – odpowiadający naszym codziennym przyzwyczajeniom.
Dla wygody wyjaśniania poszczególne wiersze ponumerowaliśmy, a pro-
dukcje 1, 2, 3 oraz 5 i 6 zapisaliśmy oddzielnie, bez użycia metasymbolu |.
Zwróćmy uwagę na produkcję z wiersza 6. Ma ona tę szczególną
właściwość, że niekońcowy symbol <liczba_naturalna> występuje w niej
po obu stronach metasymbolu podstawienia (::=). Taka reguła (nazywa
się ją rekursywną) natychmiast czyni generowany język nieskończonym.
Prześledźmy, jak to się dzieje.
Zgodnie z zasadami, zaczynamy od napisania symbolu początkowego:
<liczba_całkowita>
W tej sytuacji możemy zastosować każdą z reguł 1, 2 lub 3. Jeżeli wybie-
rzemy pierwszą – otrzymamy:
<liczba_całkowita>
0
i proces generowania skończy się, gdyż 0 jest symbolem końcowym i nie
znajdziemy go po lewej stronie metasymbolu ::=. Znaczy to, że 0 jest po-
prawną liczbą całkowitą.
Jeżeli natomiast wybierzemy produkcję 2, otrzymamy:
<liczba_całkowita>
<liczba_naturalna>
Jeżeli teraz zastosujemy regułę 4, a potem jakiekolwiek z dziewięciu pod-
stawień z wiersza 8, dostaniemy w wyniku poprawną i zgodną z intuicją
jednocyfrową liczbę całkowitą różną od zera, na przykład:
<liczba_całkowita>
<liczba_naturalna>
<cyfra_niezerowa>
8
Ciekawszy efekt uzyskamy jednak, stosując po produkcji 2 – rekursywną
regułę 6:

<liczba_całkowita>
<liczba_naturalna>
<liczba_naturalna><cyfra>
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

204 9. O lingwistyce matematycznej

Zauważmy, że niekońcowy symbol <liczba_naturalna> nadal znaj-


duje się w przepisanym ciągu. Nic nam nie zabrania zastosowania tej samej
reguły 6 jeszcze raz. Zastąpmy więc ponownie symbol <liczba_natural-
na> przez parę <liczba_naturalna><cyfra>, nie zapominając o przepisa-
niu „starego” symbolu <cyfra> z poprzedniego wiersza:
<liczba_całkowita>
<liczba_naturalna>
<liczba_naturalna><cyfra>
<liczba_naturalna><cyfra><cyfra>
Powtórzmy tę czynność jeszcze – na przykład – następne trzy razy pod
rząd:
<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>
Powiedzmy, że po tych pięciu razach stosowanie rekursywnej reguły 6 nam
już się znudziło i wybieramy „zwyczajną” produkcję 5:

<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

Poziom leksykalny i składniowy (syntaktyczny) gramatyki 205

W omawianym przykładzie rekursywną regułę 6 zastosowaliśmy


pięciokrotnie, za szóstym razem zakończyliśmy rekursję regułą 5. Ale mo-
żemy przecież ją wykorzystać dowolnie wiele razy. W każdym przypadku
ostatecznym efektem procesu generowania jest ciąg cyfr (zawsze bez zera
na początku, poprzedzony znakiem +, - lub nie) o pewnej skończonej dłu-
gości, wynikającej z tego, ile razy zastosowaliśmy rekursję. Każda konkret-
na „liczba całkowita” składa się więc ze skończonej liczby symboli koń-
cowych, ale – skoro liczba możliwych zastosowań rekursji nie jest ogra-
niczona – takich skończonych ciągów (będących w myśl podanej składni
poprawnymi „liczbami całkowitymi”) jest nieskończenie wiele. Tak więc
język generowany przez podaną wyżej gramatykę jest istotnie nieskończo-
ny, mimo że liczba samych reguł podstawiania jest skończona, a nawet
– stosunkowo niewielka.

Poziom leksykalny i składniowy (syntaktyczny)


gramatyki
W dotychczasowych przykładach wszystkie reguły podstawiania tworzyły
jeden, niepodzielny zbiór produkcji P. W praktyce okazuje się jednak, że
często wygodnie jest podzielić produkcje na dwie grupy: reguł leksykalnych
i reguł składniowych (syntaktycznych). Podział taki, choć zazwyczaj arbitral-
ny i teoretycznie niekonieczny, ułatwia rozumienie zasad danej gramatyki,
a potem – badanie poprawności otrzymanych do przetwarzania sekwencji.
Rolę reguł leksykalnych i syntaktycznych można wyjaśnić poprzez
analogię do języków naturalnych. Reguły leksykalne opisują w nich zasady
tworzenia poprawnych wyrazów. Określają one na przykład zasady ortogra-
fii, reguły odmiany rzeczowników i czasowników itd. Reguły składniowe
definiują natomiast sposoby budowania z tych (już poprawnych) jednostek
leksykalnych – konstrukcji wyższego poziomu: fraz, zdań prostych, zdań
złożonych itd. Tę samą zasadę możemy zastosować także w sztucznym ję-
zyku, którego gramatykę sami definiujemy.
Dla przykładu popatrzmy na następujący zestaw produkcji pewnej
(wymyślonej tu ad hoc) gramatyki, w której symbolem początkowym jest
<wyrażenie>.

<wyrażenie> ::= <czynnik> | <wyrażenie> <operator> <wyrażenie> ;


<czynnik> ::= <zmienna> | <liczba> ;
<operator> ::= + | – | * | / ;
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

206 9. O lingwistyce matematycznej

<zmienna> ::= <nazwa> | <nazwa><numer>;


<nazwa> ::= <litera> | <litera><litera> ;
<litera> ::= a|b| ... |x|y|z;
<numer> ::= <liczba>;
<liczba> ::= <cyfra_niezerowa>|<liczba><cyfra>;
<cyfra> ::= 0 | <cyfra_niezerowa> ;
<cyfra_niezerowa> ::= 1|2|3|...|8|9;

Pierwszy wiersz mówi nam, że <wyrażenie> jest to zarówno pojedynczy


<czynnik>, jak i dłuższa konstrukcja zbudowana (rekursywnie) z wyrażeń
przedzielonych operatorami. Czynnikami mogą być (według wiersza drugie-
go) zmienne lub liczby.
Przyjmijmy, że właśnie <zmienna>, <liczba> oraz <operator> są
tu jednostkami leksykalnymi (a więc jak gdyby „wyrazami” wyrażenia).
Powyższy zbiór produkcji rozdziela się wtedy umownie na dwie części.
Wiersze od trzeciego w dół (zawierające właśnie szczegóły budowy zmien-
nych, liczb i operatorów) tworzą poziom leksykalny, zaś pierwsze dwa wier-
sze – poziom składniowy gramatyki wyrażeń. Jest on jak gdyby nadbudo-
wany nad poziomem leksykalnym.
Zapoznawanie się z tą gramatyką łatwiej jest zacząć od poziomu lek-
sykalnego. Reguły tego poziomu mówią, że <operator> jest jednym zna-
kiem (+, -, * lub /). <zmienna> może być reprezentowana przez jedno- lub
dwuliterową nazwę albo przez takąż nazwę z numerem. Litery pochodzą
z konwencjonalnego łacińskiego alfabetu (ograniczonego tu do małych li-
ter), natomiast ewentualny <numer> zmiennej jest zdefiniowany dokładnie
tak samo jak liczby naturalne w poprzednim przykładzie (ciąg cyfr o dowol-
nej długości, ale bez zera na początku). Tak więc poprawnymi (leksykalnie)
zmiennymi są np. a, x, ab, x2 czy ab350, poprawnymi liczbami są np. 432
lub 40 000, podczas gdy np. iks2 nie jest poprawną zmienną (bo nazwa
zawiera więcej niż dwie litery), podobnie jak np. x02 (bo numer nie może
zaczynać się od zera).
Podział reguł na leksykalne i składniowe wykorzystuje się w procesie
analizy poprawności zadanej sekwencji znaków. Pomysł polega na tym, by
również tę analizę podzielić na dwie fazy: analizę leksykalną oraz analizę
składniową. W pierwszej fazie bada się, czy wszystkie jednostki leksykalne
są zbudowane zgodnie z regułami leksykalnymi. Jeżeli którakolwiek z nich
nie spełnia tego warunku – cały ciąg jest dyskwalifikowany i kierowany
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Poziom leksykalny i składniowy (syntaktyczny) gramatyki 207

do poprawki. Dopiero jeżeli wszystkie jednostki leksykalne są poprawne


– przystępuje się do analizy składniowej.
Zilustrujmy tę zasadę przykładem. Powiedzmy, że ktoś napisał nastę-
pujący ciąg symboli:

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:

zmienna operator 201*+y2


Posuwając się w ten sposób dalej i zastępując leksykalnie poprawne jed-
nostki nowymi symbolami końcowymi, dostaniemy w końcu ciąg:

zmienna operator liczba operator operator zmienna


gdzie nowe symbole końcowe zmienna, liczba i operator reprezentują
atomy leksykalne tej prostej gramatyki.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

208 9. O lingwistyce matematycznej

Otrzymaliśmy w ten sposób jak gdyby ogólny „schemat konstrukcyj-


ny” zadanego ciągu, pozbawiony (już sprawdzonych) szczegółów leksykal-
nych.
Czas na analizę składniową. Wszystkie jednostki leksykalne są już na
pewno poprawne (inaczej nie dotarlibyśmy w ogóle do etapu analizy skła-
dniowej), toteż reguły poziomu składniowego możemy teraz uprościć, wsta-
wiając zamiast symboli niekońcowych <zmienna>, <liczba> oraz <opera-
tor> – nowe symbole końcowe: zmienna, liczba oraz operator. Reguły
poziomu składniowego wyglądają więc teraz tak:

<wyrażenie> ::= <czynnik> | <wyrażenie> operator <wyrażenie> ;


<czynnik> ::= zmienna | liczba ;
Niestety, w naszym przykładzie otrzymana sekwencja nie jest zgodna z tymi
regułami składniowymi, bowiem dwa operatory występują tuż po sobie.
Tak więc cały oryginalny ciąg znaków też nie jest wyrażeniem poprawnym
w świetle założonej gramatyki.
Doszukiwanie się ogólnego „schematu konstrukcyjnego” danej se-
kwencji – to jej rozbiór (ang. parsing). Podobnie rozumowaliśmy na lek-
cjach języka polskiego w szkole, dokonując rozbioru złożonego zdania.
Identyfikowaliśmy w nim poszczególne wyrazy (tj. jednostki leksykal-
ne), ocenialiśmy ich poprawność, a następnie próbowaliśmy odgadnąć ich
rolę w zdaniu, po to, by ustalić składniową „zasadę konstrukcyjną” zdania
i upewnić się, czy jest ona zgodna z regułami języka polskiego.
Rozbiór logiczny zadanej konstrukcji językowej jest czynnością
odwrotną w stosunku do jej generowania zgodnie z regułami gramatyki.
Dokonując rozbioru, usiłujemy w istocie stwierdzić, czy istnieje taki ciąg
podstawień (produkcji), który od aksjomatu danej gramatyki doprowadziłby
do tej właśnie sekwencji, przedstawionej nam do analizy.
W powyższym rozumowaniu prześliznęliśmy się nad samą techni-
ką sprawdzania, czy dany ciąg znaków jest zgodny z podaną gramatyką.
Przykłady, którymi się wspieraliśmy, były tak proste, że wystarczył do
tego rzut oka na kilka reguł podstawiania. W rzeczywistości są znane
sposoby systematycznego, algorytmicznego dokonywania rozbioru (opie-
rające się na tworzeniu tzw. drzewa rozbioru, ang. parsing tree), istnie-
ją programy ( parsery), które tego dokonują itd. Są one wykorzystywane
we wszystkich komputerowych programach tłumaczących teksty pro-
gramów, napisane przez programistów (czyli programy źródłowe) – na
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Języki bezkontekstowe i kontekstowe 209

postać wprawdzie niezbyt zrozumiałą dla człowieka, lecz wykonywalną


(ang. executable) przez układy sterowania komputerowego procesora. Są
to jednak zagadnienia wykraczające poza tematyczny zakres niniejszej
książki.

Języki bezkontekstowe i kontekstowe


W idei gramatyki kombinatorycznej Chomsky’ego kryją się jeszcze inne
możliwości. Oto we wszystkich przytoczonych do tej pory przykładach, po
lewej stronie metasymbolu podstawienia (::=) figurował zawsze jeden sym-
bol niekońcowy. Dlatego na kolejnym etapie tworzenia konstrukcji języko-
wej mogliśmy zawsze dokonać podstawienia prawej strony odpowiedniej
produkcji w miejsce dowolnie wybranego pojedynczego symbolu niekoń-
cowego, nie biorąc w ogóle pod uwagę tego, jakie symbole stoją obok nie-
go z prawej lub lewej strony. Języki i gramatyki, które tę właściwość mają
– nazywamy bezkontekstowymi (ang. context-free languages, context-free
grammars).
Tymczasem model Chomsky’ego dopuszcza możliwość umieszczenia
po lewej stronie metasymbolu ::= całego ciągu składającego się wielu sym-
boli, pod warunkiem że co najmniej jeden z nich jest niekońcowy. Co to by
oznaczało? Właśnie możliwość uwzględnienia kontekstu, w jakim występu-
je dany niekońcowy symbol.
Dla przykładu, wyobraźmy sobie, że w pewnej gramatyce występują
następujące produkcje:
aa <bb> ::= aa cc;
xx <bb> ::= xx dd;
Znaczy to, że jeżeli symbol <bb> następuje zaraz po aa, to można
w jego miejsce podstawić cc, ale jeśli <bb> następuje po xx, to w miejsce
<bb> należy podstawić dd. Wybór podstawienia zależy więc od kontekstu,
w którym symbol <bb> występuje. W tym konkretnym przypadku jest to
bezpośredni kontekst lewostronny, ale moglibyśmy uwzględniać także kon-
tekst prawostronny, obustronny, o głębokości nie jednego, lecz kilku sym-
boli itd.
Innym prostym przykładem podstawienia zależnego od kontekstu
może być następująca produkcja:

.<litera> ::= . spacja <duża_litera> ;


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

210 9. O lingwistyce matematycznej

Taka produkcja (użyta np. w hipotetycznej gramatyce akapitów tekstu)


oznaczałaby, że po kropce powinna występować duża litera poprzedzona
spacją.
Kontekstowość znacznie wzbogaca możliwości opisywania wła-
ściwości języka. Możemy w ten sposób definiować dość złożone związki
frazeologiczne, wyjątki, zwroty o charakterze idiomów itd. Jednocześnie,
w przypadku języków kontekstowych (ang. context-sensitive languages)
analiza poprawności jest jakościowo znacznie bardziej złożona niż w języ-
kach bezkontekstowych.
Chomsky przedstawił klasyfikację gramatyk formalnych (a co za tym
idzie – również generowanych przez nie języków) w zależności od tego, ja-
kie matematyczne narzędzie jest w danej klasie języków niezbędne do ana-
lizy poprawności sekwencji symboli. Wynika z niej między innymi, że do
analizy języków kontekstowych niezbędna jest maszyna Turinga, podczas
gdy w przypadku języków bezkontekstowych wystarczą tzw. automaty ze
stosem (ang. pushdown automata), a w jeszcze bardziej ograniczonej klasie
tzw. języków regularnych (ang. regular languages) – automaty skończone
(ang. finite state machines, finite automata).
Ta klasyfikacja, zwana Chomsky’ego hierarchią gramatyk lub hie-
rarchią języków (ang. Chomsky hierarchy of grammars, hierarchy of langu-
ages) stanowi naukowy rezultat podstawowy dla lingwistyki matematycz-
nej. Nie będziemy tu go jednak szerzej omawiali. Wspomnijmy jedynie,
że językom regularnym, najniższym w owej hierarchii, poświęcimy nieco
uwagi w następnym rozdziale, dotyczącym właśnie automatów skończo-
nych.

A jak się to ma do języków naturalnych?


W całym niniejszym rozdziale kilkakrotnie odwoływaliśmy się do analogii
z językami naturalnymi, do intuicji czytelnika, wyniesionej ze szkolnych
lekcji gramatyki itd. Choć takie analogie są uprawnione i użyteczne dla zro-
zumienia idei lingwistyki matematycznej, to warto sobie zdawać sprawę,
że w językach naturalnych sytuacja wcale nie jest ani prosta ani łatwa do
formalnego opisania.
Języki naturalne mają przede wszystkim charakter niezwykle kon-
tekstowy. Objawia się to już w przypadku bardzo prostych, bezpośrednich
związków frazeologicznych. Dla przykładu, w języku polskim zwycięstwo
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

A jak się to ma do języków naturalnych? 211

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

212 9. O lingwistyce matematycznej

udolne i daleko przekraczają granice śmieszności. Podobnie znane edytory


tekstu, chlubiące się jakoby automatyczną kontrolą poprawności tego, co
piszemy – ostrzegają prawie wyłącznie przed błędami leksykalnymi (pisow-
nia poszczególnych wyrazów), a z błędów składniowych zauważają jedynie
nieliczne, najbardziej prymitywne, takie jak powtórzenie wyrazu czy użycie
małej litery na początku zdania lub akapitu.
Ta sytuacja może jednak ulec zmianie. Badania nad modelami języ-
ków naturalnych, mechanizmami językowej komunikacji itd. – są bardzo
intensywnie prowadzone w wielu ośrodkach naukowych i działach badaw-
czych wielkich firm komercyjnych. Szczególnie ciekawie wyglądają prace
nad semantycznymi modelami języka (np. sieci semantyczne, ang. seman-
tic network model, czy międzynarodowy projekt Semantic Web). Zmierzają
one do tworzenia formalnego modelu powiązań nie między słowami czy
zdaniami, lecz między pojęciami. Potem, po utworzeniu takiego schematu
powiązań, przystępuje się do przypisywania im struktur językowych, które
tym pojęciom (i ich powiązaniom) odpowiadają.
Sam pomysł sieci semantycznych i wiązania z nimi struktur grama-
tycznych nie jest nowy: powstał już dobre pół wieku temu. Jednak w ostat-
nich latach nabrał szczególnego, praktycznego znaczenia. Zainteresowały
się nim wielkie (i dysponujące ogromnymi środkami) firmy informatyczne,
zwłaszcza działające na rynku usług internetowych i baz danych.
Łatwo wyobrazić sobie, jakie korzyści może im (i jednocześnie nam)
przynieść wzbogacenie struktur językowych o dobrze zdefiniowaną seman-
tykę. Dziś wpisujemy w okienko wyszukiwarki pewien konkretny ciąg zna-
ków i jako wynik wyszukiwania otrzymujemy wykaz stron, na których taki
właśnie (lub prawie taki) ciąg znaków występuje. Gdyby wyszukiwarka
dysponowała odpowiednim modelem semantyki – zaoferowałaby nam rów-
nież strony, na których występują informacje o podobnym znaczeniu, choć
reprezentowane przez inne słowa. Znalazłoby to zastosowanie zarówno
w internetowych usługach udostępniania informacji, jak i w marketingu, re-
klamie itd. Kryją się za tym potencjalnie ogromne zyski.
Taka przyziemna, komercyjna motywacja z pewnością nie zaszkodzi,
lecz pomoże badaniom nad powiązaniem gramatyki języka z jego seman-
tyką. Wpłynie to z pewnością również na badania nad algorytmicznym tłu-
maczeniem z języka na język, językowym porozumiewaniem się człowieka
z maszynami itd.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Intelekt i lewicowość 213

Jest bardzo prawdopodobne, że będziemy wkrótce świadkami – trud-


nych dziś do przewidzenia – sukcesów w dziedzinie przetwarzania języków
naturalnych.
Natomiast w dziedzinie sztucznych języków, tworzonych dla celów
informatyki – o sukcesie lingwistyki matematycznej możemy mówić już
teraz. Warto uświadomić sobie, że każdy z milionów programów, wykony-
wanych w tej chwili przez miliony komputerów, był kiedyś po raz pierwszy
napisany (a później po wielekroć poprawiany i modyfikowany) w jakimś
języku programowania. Ten język musiał być przedtem formalnie zdefi-
niowany, zapewne – z użyciem notacji BNF (lub pokrewnej). Ktoś musiał
przygotować dla tego języka program tłumaczący (translator), wyposażony
w odpowiedni parser i inne mechanizmy służące konstruowaniu programu
wynikowego. Program źródłowy musiał przebyć analizę leksykalną i skła-
dniową, itd.
Podobnie, każdy z obrazów, wyświetlanych w tej chwili na ekranach
dziesiątków milionów komputerów, każdy z odtwarzanych plików muzycz-
nych czy multimedialnych – jest strukturą danych, szczegółowo zdefiniowa-
ną tak, jak wcześniej w naszych przykładach definiowaliśmy budowę „liczb
całkowitych” czy „wyrażeń”. Dzięki lingwistyce matematycznej wiemy,
dlaczego (i jak) powinniśmy się wystrzegać kontekstowości, niejednoznacz-
ności itd. Dzięki lingwistyce matematycznej i wyrosłym na jej gruncie po-
mysłom umiemy to wszystko wyrazić i zrobić.
Dlatego możemy powiedzieć, że cała współczesna informatyka jest
jednym wielkim potwierdzeniem sukcesu nie tylko elektroniki czy techno-
logii układów scalonych – lecz również lingwistyki matematycznej.

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

214 9. O lingwistyce matematycznej

Fot. 9.1. Noam Chomsky w domu w Cape Cod, USA. Zdjęcie otrzymane dzięki
uprzejmości rodziny Noama Chomsky’ego

W świecie naukowym jednym ze szczególnie cenionych mierników


wpływu uczonego na środowisko naukowe jest liczba cytowań, to znaczy
liczba artykułów, książek, podręczników, w których ktoś inny cytuje jego
pracę lub powołuje się na jego nazwisko. Duża liczba cytowań pozwa-
la przypuszczać, że ten uczony potrafił zainspirować innych nowymi po-
mysłami, że wskazał nowe drogi rozumowania, po których inni podążyli
i cytując go teraz w swoich pracach uznają, jego prekursorską rolę. Istnieją
instytuty naukoznawcze, śledzące na bieżąco literaturę naukową ze wszyst-
kich dziedzin, notujące wzajemne cytowania i prowadzące bazy danych,
na podstawie których można ocenić wpływ poszczególnych osób, a nawet
pojedynczych artykułów, książek i czasopism – na postęp badań w wielu
dziedzinach wiedzy.
Z tworzonych przez te instytucje rankingów wynika, że Noam
Chomsky od lat mieści się jeśli nie na pierwszym miejscu, to w pierwszej
trójce czy dziesiątce najczęściej cytowanych żyjących uczonych i w podob-
nie ścisłej czołówce ludzi żyjących w ogóle kiedykolwiek, a cytowanych
w ciągu ostatnich dwudziestu czy trzydziestu lat. Co mogło wpłynąć na taki
stan rzeczy?
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Intelekt i lewicowość 215

Bez wątpienia przyczyniła się do tego najpierw idea matematycznej


formalizacji gramatyki i późniejsze prace Chomsky’ego na ten temat. Jej
znaczenie dla wielu dziedzin wiedzy jest oczywiste. W informatyce – po-
trafimy dzięki niej konstruować i precyzyjnie definiować sztuczne języki,
a także mówić o ich właściwościach: skończoności, regularności, kontek-
stowości, badać leksykalną i składniową poprawność programów i opisów
struktur danych itd. Lingwistyka języków naturalnych zyskała z kolei poję-
ciową podstawę (i odpowiedni formalizm) do zapisywania reguł leksykal-
nych i składniowych. Otwiera to nowe możliwości porównywania języków,
odkrywania różnic, podobieństw i wzajemnych wpływów między nimi,
śledzenia ewolucji języków na przestrzeni wieków, badań nad językami
archaicznymi itp., a także – badań nad rozpoznawaniem i generowaniem
mowy, przekształcaniem tekstu mówionego na pisany i odwrotnie, nad al-
gorytmicznym tłumaczeniem z jednego języka na inny itd.
Drugą dziedziną, na którą Noam Chomsky wywarł wpływ – jest psy-
chologia. W strukturach językowych znajdują odbicie struktury pojęciowe,
według których człowiek postrzega, nazywa i porządkuje sobie świat. Mówi
się, że dla przeciętnego Europejczyka piasek – to piasek, ale Beduin rozróż-
nia (i potrafi nazwać) kilkanaście czy kilkadziesiąt rodzajów piasku. Podobnie
każdy z nas nieco inaczej rozpoznaje, grupuje, kojarzy i nazywa fakty, uczucia,
wartości i pojęcia abstrakcyjne. Znajduje to odbicie w strukturach językowych
i wpływa na relacje człowieka z samym sobą i innymi ludźmi, z rodziną, grupą
społeczną, naturą – a więc kształtuje psychikę człowieka.
Ale intrygujący jest również sam proces nabywania sprawności w po-
sługiwaniu się językiem. Małe dziecko zdumiewająco szybko (wydaje się,
że szybciej niż w przypadku uczenia się innych umiejętności, np. rucho-
wych) przechodzi od pojedynczych dźwięków i pojedynczych rzeczowni-
ków (najczęściej we własnym, jeszcze dziecięcym języku) poprzez najprost-
sze frazy i zdania – do zdań złożonych i wreszcie całkiem już dorosłych
wypowiedzi.
Chomsky postawił hipotezę, że ludzki mózg jest jakoś wstępnie (ana-
tomicznie? ewolucyjnie? genetycznie?) przygotowany do zapamiętywania
struktur językowych, a następnie – posługiwania się nimi w mowie i pi-
śmie. Innymi słowy, dzieje się tak, jak gdyby każdy z nas, przychodząc na
świat, miał już wbudowaną w mózgu uniwersalną gramatykę (ang. univer-
sal grammar), którą potem jedynie wypełnia słowami i frazami z języka,
który go otacza.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

216 9. O lingwistyce matematycznej

Ta kontrowersyjna teza wywołała wiele dyskusji, pobudziła do wielu


badań i przyczyniła się do powstawania nowych teorii funkcjonowania ludz-
kiego mózgu. Zrodziła też nowe pytania, na przykład to, czy przynajmniej
niektóre zwierzęta też posiadają takie właściwości? A może ta umiejętność
jest zjawiskiem poza- czy ponadbiologicznym, wyraźnie wyróżniającym
człowieka spośród innych stworzeń? To w gruncie rzeczy spór o istotę czło-
wieczeństwa, o rolę czynników materialnych i niematerialnych w naturze
i pochodzeniu człowieka... To już nie lingwistyka czy psychologia, lecz an-
tropologia i filozofia. Dlatego Chomsky uważany jest też za jednego z waż-
nych współczesnych filozofów.
Jest jednak jeszcze jeden, zupełnie inny powód, dla którego nazwisko
Noama Chomsky’ego jest tak często cytowane. Profesor Chomsky jest bo-
wiem znany z tego, że od wielu lat głosi poglądy zdecydowanie lewicowe,
żeby nie powiedzieć – lewackie (choć sam woli mówić, że jest anarcho-
-syndykalistą). W licznych artykułach, książkach, wywiadach i wystąpie-
niach bezkompromisowo gromi kapitalizm, zachodni model demokracji,
działalność wielkich międzynarodowych korporacji, globalizację i świa-
towy system finansowy, którego uosobieniem jest dziś Bank Światowy
i Międzynarodowy Fundusz Walutowy. Co najmniej od lat sześćdziesiątych
XX wieku (wojna w Wietnamie!) kieruje słowa bezlitosnej krytyki pod ad-
resem rządu USA i innych krajów kapitalistycznych, które – według niego
– pod pretekstem szerzenia wolności, sprawiedliwości i demokracji manipu-
lują społeczeństwami, działając w interesie nie zwykłych ludzi, lecz swych
podstępnych, ukrytych mocodawców.
Przy okazji zjazdów przywódców krajów grupy G-8 czy G-20, mię-
dzynarodowych forów ekonomicznych itd. odbywają się zwykle burzliwe
demonstracje anty- i alterglobalistów, ekologów i anarchistów, nierzadko
brutalnie rozpędzane przez policję. Można być wtedy pewnym, że profesor
Chomsky, dopóki mu sił starczy, zabierze głos w obronie demonstrantów,
przeciwko mrocznym spiskom międzynarodowej finansjery i globalnych
korporacji oraz przemocy ze strony opresyjnego państwa.
Jego wiek, autorytet naukowy i niekwestionowana pozycja intelektu-
alna sprawiają, że trudno mu zarzucić młodzieńczą naiwność czy polityczną
głupotę. Dlatego każde jego wystąpienie jest przez młodych demonstrantów
traktowane jako ważny głos w obronie ich racji, a na zwolenników establi-
shmentu i osoby o prawicowych poglądach – działa jak czerwona płachta na
byka. Tak czy inaczej, jego bezkompromisowe opinie prowokują do gorą-
cych dyskusji, a głosy zarówno politycznych zwolenników, jak i oponentów
– wciąż przysparzają sędziwemu uczonemu światowego rozgłosu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

10. Automaty skończone

Podstawowa definicja automatu skończonego


Automaty skończone (ang. finite state automata), inaczej – maszyny skoń-
czenie stanowe (ang. finite state machines, w skrócie FSM), są jednym
z najbardziej podstawowych teoretycznych modeli wykorzystywanych
w wielu gałęziach informatyki. Służą jako pojęciowa podstawa zarówno
dla wielu zagadnień lingwistyki matematycznej, jak i przy projektowaniu
urządzeń cyfrowych i tworzeniu modeli zachowań układów sterowania,
programów itp.
Zapoznając się z koncepcją automatu skończonego, wygodnie jest
wyobrażać sobie taki automat po prostu jako pewien graf, który opisuje za-
chowanie zgodne z kilkoma dalej omówionymi nieskomplikowanymi regu-
łami. Posłużmy się przykładem z rysunku 10.11.

Rys. 10.1. Przykładowy automat skończony (Mealy’ego)

1 Dlaczego napisano tam, że jest to automat Mealy’ego – wyjaśni się dalej.


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

218 10. Automaty skończone

Graf składa się ze skończonej liczby węzłów (kółek) i łączących je


strzałek. Ponieważ strzałki wskazują kierunek – jest to graf skierowany.
Ponadto, przy węzłach i krawędziach widnieją jakieś napisy, jak gdyby ety-
kietki dolepione do poszczególnych elementów grafu. Jest to więc skończo-
ny, skierowany graf etykietowany.
Węzły grafu symbolizują stany automatu, strzałki – przejścia między
stanami. Stan 1 jest opatrzony dodatkowo małą strzałką z czymś w rodzaju
kulki na drugim końcu. Nie oznacza to żadnego przejścia, wskazuje jedynie,
że ten stan jest wyróżnionym stanem początkowym, od którego rozpoczyna
się całe „życie” tej abstrakcyjnej maszyny.
Choć sam rysunek 10.1 tego nie pokazuje, zakłada się, że automat
działa w pewnym środowisku (rysunek 10.2). Otoczenie to produkuje pewne
sygnały wejściowe (odbierane przez automat) i odbiera produkowane przez
automat sygnały wyjściowe.

Rys. 10.2. Współpraca automatu z otoczeniem

Zakładamy, że otoczenie nadsyła do automatu symbole wejściowe sekwen-


cyjnie, to znaczy pojedynczo, jeden po drugim. Pochodzą one ze zdefinio-
wanego alfabetu wejściowego automatu. Oznaczmy go przez A. W naszym
przykładzie alfabet ten składa się z dwóch liter: A = {a, b}. Jest to więc
tak, jak gdyby na wejściu automatu pojawiała się niepusta taśma wejściowa,
zapisana dowolnym ciągiem znaków z alfabetu A. Stosując terminologię
wprowadzoną w poprzednim rozdziale, powiemy, że jest to dowolny ciąg
nad alfabetem A.
Automat czyta tę taśmę znak po znaku i w odpowiedzi stopniowo pro-
dukuje na wyjściu sekwencję wyjściową. Repertuar symboli wyjściowych,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Podstawowa definicja automatu skończonego 219

którymi posługuje się automat, tworzy jego alfabet wyjściowy B. W naszym


przypadku B = {x, y}. Graf automatu mówi natomiast dokładnie, jak to się
odbywa.
Interpretacja grafu jest prosta i naturalna. Przy strzałkach (przejściach)
grafu widnieją etykiety takie jak np. a/x, b/y. Napis a/x mówi, że „jeśli na
wejściu jest a, to na wyjściu trzeba napisać x”. Podobnie b/y oznacza, że „je-
żeli pojawiła się wejściowa litera b, to na wyjściowej taśmie należy napisać y”
i tak dalej. Sama strzałka wskazuje natomiast, do którego stanu automat rów-
nocześnie ma przejść. Oczywiście, może to być także ten sam stan: na przykład
automat z rysunku 10.1, będąc w stanie 1 i odczytując symbol wejściowy a,
ma wyprodukować symbol wyjściowy x i znaleźć się znowu w stanie 1.
Zakładamy przy tym, że przejścia między stanami dokonują się bły-
skawicznie, w czasie równym zeru. Jeśli tak, to w każdym momencie au-
tomat przebywa w jakimś stanie, i to – co ważne – zawsze tylko w jednym,
nigdy w kilku naraz.
Znalazłszy się w nowym stanie, automat zabiera się do odczytywania
następnego symbolu wyjściowego i znów, w zależności od tego, co odczy-
tał, produkuje symbol wyjściowy i przechodzi do następnego stanu. I tak te
czynności powtarzają się aż do końca taśmy wejściowej.
Proszę teraz – dla zabawy i nauki – potraktować rysunek 10.1 jak coś
w rodzaju planszy do gry. Weźmy jakiś mały przedmiot, który będzie w tej
grze pionkiem i ustawmy go w stanie początkowym 1. Teraz zachowajmy
się jak automat, który dostał na wejście taśmę, na przykład taką jak na ry-
sunku 10.2. Zgodnie z podanymi wyżej regułami notujmy kolejne symbole
wyjściowe i przestawiajmy pionek tak, jak nakazują strzałki. Powinniśmy
otrzymać taką sekwencję wyjściową, jak na rysunku 10.2, a w pokazanej
tam sytuacji pionek powinien stać w stanie 1.
Decyzję co do kolejnego symbolu wyjściowego i następnego stanu
podejmujemy więc w zależności od dwóch przesłanek, mianowicie od aktu-
alnego symbolu wejściowego i aktualnego stanu. Nie jest natomiast ważne,
jaką drogą automat dotarł do tego stanu, jak długo nasz pionek wędrował
przedtem po grafie, jakie symbole wejściowe automat odebrał czy wypro-
dukował dawniej itd. Moglibyśmy powiedzieć, że automat „ma krótką pa-
mięć”: wszystko, co pamięta – to jego aktualny stan. Po przejściu do następ-
nego stanu znów tylko on (ten nowy stan) będzie – wraz z nowym symbo-
lem wejściowym – podstawą decyzji, a stan poprzedni zaginie zapomniany
w przeszłości. Niemniej, pamiętanie aktualnego stanu – to jest pewien ele-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

220 10. Automaty skończone

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

Podstawowa definicja automatu skończonego 221

Spośród nich do zdefiniowania przykładowego automatu wybraliśmy


pewien podzbiór, a mianowicie tylko siedem z tych 16 możliwych par:

next = {(1,1), (1,2), (2,1), (2,4), (3,1), (3,2), (4,3)}

i to one właśnie są narysowane na grafie jako strzałki oznaczające przejścia.


Zapis next ⊆ S × S oznacza właśnie tyle, że next jest podzbiorem S × S.
Po tym wyjaśnieniu jest chyba jasna również formalna postać funkcji
wyjścia. Istotnie, funkcja wyjścia out: next × A → B przypisuje parom o po-
staci (przejście, symbol wejściowy) – elementy alfabetu wyjściowego B. Jest
to funkcja, ponieważ jednej takiej parze przypisuje dokładnie jeden symbol
wyjściowy, na przykład parze ((1,1), a)) symbol x, parze ((1,2), b)) symbol
y i tak dalej.
Istnieje inna możliwość zdefiniowania funkcji wyjścia automatu.
Do tej pory symbole wyjściowe przypisywaliśmy przejściom w automacie.
Zamiast tego, symbole wyjściowe można przypisać stanom automatu. Wtedy
funkcja wyjścia ma postać out: S → B. W pierwszym przypadku (wyjście
związane z przejściami) mówimy, że jest to automat Mealy’ego, w drugim
(wyjście przypisane do stanów) – że jest to automat Moore’a. Nasz dotych-
czasowy przykład z rysunku 10.1 jest więc automatem Mealy’ego2.
Interpretacją grafu automatu Moore’a jest prawie identyczna z tą, jaką
opisaliśmy poprzednio, dla automatu Mealy’ego. Zgodnie z nią, automat
„budzi się do życia” w chwili, kiedy otrzymuje pierwszy znak wejściowej
taśmy. Znaczy to, że będąc w stanie początkowym, nie produkuje żadnego
symbolu wyjściowego i zaczyna działać dopiero wraz z pojawieniem się
pierwszego symbolu na jego wejściu. Wtedy wybiera przejście, przechodzi
do następnego stanu i produkuje taki symbol wyjściowy, jaki jest temu na-
stępnemu stanowi przypisany. Potem czyta kolejny symbol wejściowy, wy-
konuje przejście, produkuje symbol wyjściowy właściwy dla tego nowego
stanu... i tak dalej, aż do końca wejściowej taśmy.
Oba podejścia są sobie dokładnie równoważne, w tym sensie, że
dla każdego automatu Mealy’ego da się zbudować taki automat Moore’a
(i odwrotnie, dla automatu Moore’a taki automat Mealy’ego), że oba będą

2 Nazwy automatów pochodzą od nazwisk Edwarda F. Moore’a i George H.


Mealy’ego, którzy je zaproponowali w połowie lat pięćdziesiątych XX wieku.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

222 10. Automaty skończone

produkowały identyczne sekwencje wyjściowe w odpowiedzi na dowolną


sekwencję wejściową.
Na rysunku 10.3 pokazano grafy dwóch automatów: z lewej strony
automatu Mealy’ego (skopiowanego z rysunku 10.1), z prawej – równoważ-
nego mu (w opisanym sensie) automatu Moore’a.

Rys. 10.3. Równoważne automaty Mealy’ego i Moore’a

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

Podstawowa definicja automatu skończonego 223

Wróćmy na chwilę do rysunku 10.2. Z tego, co wyżej powiedzieliśmy,


wynika, że nie jesteśmy w stanie odgadnąć z zewnątrz, która z równoważ-
nych wersji automatu znajduje się w środku pudełka z napisem „Automat
skończony”. Możemy wypróbowywać dowolne sekwencje wejściowe,
a po otrzymanych w wyniku sekwencjach wyjściowych nie zgadniemy, czy
w środku znajduje się automat Mealy’ego, czy równoważny mu automat
Moore’a.
Do tej pory zasady funkcjonowania automatów skończonych ilustro-
waliśmy za pomocą grafów, które są dla człowieka wygodną, obrazkową
formą opisu. Te same zależności można także przedstawić w formie tabel,
może nieco mniej dla człowieka przyjaznych, ale łatwiejszych do serializa-
cji (czyli przekształcenia do postaci szeregowego ciągu) i wygodniejszych
z punktu widzenia ewentualnego algorytmicznego przetwarzania.
W tabeli 10.1 przedstawiono dla przykładu tabelę przejść i wyjść dla
automatu Mealy’ego z rysunku 10.1 (albo z lewej części rysunku 10.3).

Tab. 10.1. Tabela przejść i wyjść dla automatu Mealy’ego

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

Podstawowa część tabeli, obwiedziona grubszym obramowaniem, składa się


z czterech wierszy i dwóch kolumn. Reszta – to opisy dodane dla objaśnienia.
Wiersze odpowiadają poszczególnym stanom automatu, a kolumny – sym-
bolom alfabetu wejściowego. Wewnątrz tabeli, w każdym jej polu, podane
są dwie informacje, a mianowicie następny stan oraz produkowany symbol
wyjściowy. Tak więc na przykład w wierszu 1 i kolumnie a jest napisane
(1, x). Znaczy to, że jeśli automat jest aktualnie w stanie 1 (wiersz) i na wej-
ściu pojawi się a (kolumna), to automat ma przejść do stanu 1 i wyprodu-
kować wyjściowy symbol x. Podobnie, jeśli automat jest np. w stanie 3 i na
wejściu pojawi się b, to z okienka znajdującego się w wierszu 3 i kolumnie b
odczytamy, że następnym stanem ma być 2, a symbolem wyjściowym y, i tak
dalej. Jest to więc dokładnie to samo, co w grafie, tylko inaczej zapisane.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

224 10. Automaty skończone

Analogiczna tablica (tabela 10.2) dla równoważnego automatu


Moore’a nieco różni się od poprzedniej. Są to w gruncie rzeczy dwie sklejo-
ne ze sobą tabelki o tak samo opisanych wierszach. Jedna określa przejścia
w automacie, druga – sposób przypisania symboli wejściowych poszczegól-
nym stanom automatu.

Tab. 10.2. Tablice przejść i wyjść dla automatu Moore’a


Funkcja przejść
Funkcja
Symbole wejściowe
wyjścia
a b
P P R x
Q P R y
Stan aktualny R T P y
S P R x
T S Q y

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.

Niezupełność i niedeterminizm automatu


Automat zarówno Moore’a, jak i Mealy’ego może być zupełny albo niezu-
pełny, a także deterministyczny albo niedeterministyczny. Te pojęcia są zilu-
strowane na rysunku 10.4. Widzimy na nim znany już automat Mealy’ego,
ale nieznacznie zmodyfikowany na dwa sposoby.
Automat z lewej strony rysunku jest niezupełny. Jeżeli znajdzie się
w stanie 4 i otrzyma na wejściu symbol b – to „nie wie”, co ma dalej zro-
bić. W jego relacji przejścia i funkcji wyjścia nie przewidziano takiej moż-
liwości. Automat nie może dalej działać. Automat jest natomiast zupełny,
jeżeli dla żadnego z jego stanów taka sytuacja nie ma miejsca. Innymi sło-
wy, w automacie zupełnym dla każdego jego stanu i dla każdego symbolu
wejściowego jest przewidziany jakiś następny stan (choćby ten sam) i jakiś
symbol wyjściowy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Niezupełność i niedeterminizm automatu 225

Rys. 10.4. Automat Mealy’ego niezupełny (a) i niedeterministyczny (b)

Zauważmy, że gdybyśmy opisali zachowanie tego samego automatu nie za


pomocą grafu, lecz tablicy przejść i wyjść – to jego „niezupełność” natych-
miast rzuciłaby się nam w oczy: niektóre z „okienek” tablicy świeciłyby
pustką.
Oczywiście, automat Moore’a też może być zupełny albo niezupełny,
na podobnej zasadzie. Również i w tym przypadku obecność pustych miejsc
w tablicach przejść i wyjść świadczy o niezupełności automatu.
W praktycznych sytuacjach dążymy zazwyczaj do tego, by automat
był zupełny: niezupełność jest pewną ułomnością automatu. Z tym zagad-
nieniem będziemy mieli jeszcze do czynienia w rozdziale 13, kiedy spróbu-
jemy sami zaprojektować pewien sprzętowy automat.
Automat z prawej strony rysunku 10.4 jest zupełny, natomiast jest nie-
deterministyczny. Przejawia się to tym, że automat, znalazłszy się w stanie 2
i odebrawszy wejściowy symbol b, ma więcej niż jedną możliwość dalszego
postępowania. Może albo wyprodukować symbol wyjściowy x i przejść do
stanu 1, albo wyprodukować y i przejść do 4. Obie te możliwości są prze-
widziane w relacji przejścia i funkcji wyjścia, a więc są „legalne” i dopusz-
czalne. Ponieważ jednak automat może być zawsze tylko w jednym ze sta-
nów – musi koniecznie dokonać wyboru i zdecydować się na jedno z tych
możliwych przejść, a potem może najzwyczajniej kontynuować działanie.
Oczywiście, przy ponownej bytności w stanie 2 decyzja musi być podjęta
od nowa, i to niezależnie od wyniku poprzednich wyborów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

226 10. Automaty skończone

Automat jest deterministyczny, jeżeli w żadnym z jego stanów ten


sam symbol wejściowy nie powoduje takiej konieczności wyboru jednego
spośród kilku możliwych następnych stanów. Zauważmy, że w automacie
deterministycznym relacja przejścia (next) jest funkcją, to znaczy, że każ-
dej parze, należącej do jej dziedziny, przypisuje dokładnie jeden stan na-
stępny.
W tablicy przejść i wyjść (zarówno automatu Mealy’ego, jak
i Moore’a) opisana sytuacja (tj. niedeterminizm automatu) przejawiałaby się
tym, że żadne z pól nie byłoby puste (jeżeli automat jest zupełny), ale w jed-
nym lub kilku z nich byłyby wymienione nie jedna, lecz dwie (lub więcej)
możliwości.
Definicja automatu nie określa w żaden sposób, na jakich kryteriach
opiera się taki niedeterministyczny wybór następnego stanu. Nie mówi się
ani dlaczego należałoby wybrać jedno albo drugie rozwiązanie, ani z jakim
prawdopodobieństwem są one wybierane, ani jakie przesłanki przemawiają
za jednym, a jakie za drugim z nich. Niedeterministyczny wybór jest więc
konieczny, ale jego przyczyny nie są określone.
O ile niezupełność automatu może uniemożliwiać mu działanie,
to niedeterminizm często jest pożądaną cechą, decydującą o sile modelu.
Oczywiście, jeśli projektujemy pewne techniczne urządzenie i opisujemy
jego zachowanie za pomocą automatu skończonego – to zapewne będziemy
wymagali, by taki konkretny automat był zupełny i deterministyczny. Jednak
teoria automatów skończonych jest wykorzystywana także przy tworzeniu
i badaniu teoretycznych modeli, na przykład współpracujących ze sobą ele-
mentów oprogramowania, protokołów komunikacyjnych itp. W takim przy-
padku niedeterminizm pozwala w formalny sposób powiedzieć, że „dalej
może być tak, albo też tak i nie wnikajmy dlaczego”. Dzięki temu „niewnika-
niu dlaczego” można znacznie zredukować teoretyczny model, pozbywając
się z niego dużej ilości szczegółów, które nie są akurat dla danego fragmentu
rozumowania istotne.

Automat skończony a badanie poprawności


składniowej
Wspomnieliśmy o tym, że pojęciowe kategorie związane z automatami
skończonymi są szeroko wykorzystywane w lingwistyce matematycznej.
Istotnie, teoria automatów zrosła się z lingwistyką matematyczną bardzo
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Automat skończony a badanie poprawności składniowej 227

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

228 10. Automaty skończone

Jak wszystkie automaty skończone, ma on skończony zbiór stanów,


wyróżniony stan początkowy, alfabet wejściowy i relację przejścia. Nie ma
natomiast alfabetu wyjściowego i nie produkuje żadnych symboli wyjścio-
wych. Niepotrzebna jest więc również funkcja wyjścia i nie ma problemu,
czy jest to automat Moore’a, czy Mealy’ego. Za to w zbiorze stanów wy-
różniony jest pewien podzbiór stanów, zwanych stanami akceptującymi.
Zakładamy przy tym, że automat jest zupełny.
Automat Rabina–Scotta, jak każdy automat skończony, czyta swo-
ją wejściową taśmę znak po znaku i (zaczynając od stanu początkowego)
przechodzi od stanu do stanu zgodnie ze swoją relacją przejść. Jeżeli po
przeczytaniu ostatniego symbolu wejściowego znajdzie się w jednym ze sta-
nów akceptujących – to powiemy, że zaakceptował tę taśmę. Jeżeli koniec
taśmy wejściowej zastanie automat w jednym ze stanów nienależących do
podzbioru stanów akceptujących – to powiemy, że tę taśmę odrzucił (lub że
jej nie zaakceptował)3.
Ponieważ automat przebywa zawsze w jakimś (i to dokładnie w jed-
nym) stanie, a nie ma innych stanów, niż albo akceptujące, albo nieakcep-
tujące – każda skończona sekwencja jest w końcu albo akceptowana, albo
nie. Innej możliwości nie ma. Każdy automat Rabina–Scotta dzieli więc na
dwie części nieskończony zbiór wszystkich możliwych skończonych se-
kwencji nad jego alfabetem. W jednej znajdują się sekwencje akceptowane,
w drugiej – nieakceptowane. Sekwencje akceptowane tworzą język danego
automatu.
Na rysunku 10.5 jest pokazany automat Rabina–Scotta, który akcep-
tuje jedynie ciągi znaków zgodne z przytoczoną wyżej składnią „wyrażeń”.
Jego alfabetem wejściowym A jest zbiór symboli końcowych tej gramatyki:
A = {zmienna, liczba, operator}
Na wejściu automatu może pojawić się dowolna sekwencja symboli z tego
alfabetu. Automat jest zupełny, więc nie grozi mu, że odczyta z wejścia znak,
na który nie będzie umiał zareagować. Zbiór stanów liczy cztery elementy,
stanem początkowym jest 1, a jedynym stanem akceptującym jest stan 2.

3 W wielu podręcznikach teorii automatów i lingwistyki matematycznej automa-


tem jest nazywany po prostu automat Rabina–Scotta. Nasze automaty Moore’a
i Mealy’ego są (z punktu widzenia lingwistyki) jego nieco egzotycznymi, niepo-
trzebnie skomplikowanymi wersjami.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Automat skończony a badanie poprawności składniowej 229

Rys. 10.5. Automat Rabina–Scotta dla przykładowej gramatyki

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

230 10. Automaty skończone

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

Automat skończony jako model zachowania fizycznego urządzenia 231

Gdyby jeszcze liczba zagnieżdżeń była ograniczona (na przykład


gdyby były dopuszczalne cztery poziomy zagnieżdżania, albo czterysta,
albo tysiąc) – to mając odpowiednio dużo cierpliwości potrafilibyśmy skon-
struować automat skończony o dostatecznie dużej liczbie stanów, który po-
radziłby sobie z orzekaniem, czy w zadanym ciągu tak sformułowana reguła
jest przestrzegana. Jeżeli jednak zagnieżdżeń dopuszcza się dowolnie wiele
– to język nie jest regularny i nie ma takiego skończonego automatu, który
by z tym zadaniem się uporał.
Tymczasem w gramatyce bezkontekstowej regułę nawiasową wyrazić
jest bardzo łatwo. Dla przykładu, w rozważanej wyżej (regularnej) gramaty-
ce „wyrażeń” wystarczy dopisać jedną rekurencyjną produkcję:
<wyrażenie> ::= (<wyrażenie>);
Wprawdzie bez dodatkowych ulepszeń reguła ta umożliwia produkowanie
„wyrażeń”, w których nawiasy będą jak gdyby w nadmiarze, ale będą one
rozmieszczone w ciągu znaków zgodnie z zasadami notacji nawiasowej.
Tak zmodyfikowana gramatyka przestaje być regularna, ale pozostaje
bezkontekstową, podobnie jak generowany przez nią język. Po lewej stronie
metasymbolu podstawienia (::=)jest bowiem nadal pojedynczy symbol nie-
końcowy, co jest warunkiem bezkontekstowości.
Lingwistyka matematyczna (a w szczególności Chomsky’ego hierar-
chia gramatyk i języków) pozwala na stwierdzenie (na podstawie budowy
produkcji tej gramatyki), do jakiej klasy dany język należy i jaki rodzaj abs-
trakcyjnej maszyny jest potrzebny do badania poprawności sekwencji z tego
języka. Są to jednak zagadnienia wykraczające poza zakres tej książki, dla-
tego poprzestaniemy na powyższych kilku uwagach i przykładach.

Automat skończony jako model zachowania


fizycznego urządzenia
Już na początku niniejszego rozdziału pokazany tam graf automatu
Mealy’ego zapewne wielu czytelnikom nasunął skojarzenie z grafami stero-
wania maszyn Turinga, takimi, jak np. na rysunku 8.2 czy 8.4. Rzeczywiście,
skojarzenie jest uzasadnione, ale trzeba jednocześnie pamiętać, że automaty
skończone znacznie się różnią od maszyn Turinga.
Najważniejsza różnica polega na tym, że automat skończony ma dwie
taśmy: wejściową i wyjściową, automat Rabina–Scotta tylko wejściową, ale
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

232 10. Automaty skończone

po żadnej z nich nie może przesuwać się w obu kierunkach. Nie ma on


w ogóle wpływu na swoje położenie względem obu taśm, tak, jak gdyby po
każdym kroku musiał przesunąć się o jedną komórkę na obu taśmach i to
zawsze tylko w jednym kierunku (na przykład zawsze w prawo), albo tak
jak gdyby automat się w ogóle nie poruszał, a jego taśmy „same” przesuwa-
ły się zawsze o jedną komórkę w lewo.
Taka właściwość sprawia, że automaty skończone są modelem teo-
retycznym słabszym, bardziej ograniczonym, o mniejszych możliwościach
niż maszyny Turinga. Jednak okazuje się, że dzięki tej samej właściwości
automaty skończone bardzo dobrze nadają się do opisywania zachowań róż-
nych urządzeń, które funkcjonują w czasie. Czas bowiem biegnie „sam” i to
(niestety...) w jednym tylko kierunku, tak właśnie, jak przesuwają się taśmy
automatu.
W przypadku fizycznych urządzeń to, co się pojawia na ich wejściu,
nie musi mieć charakteru prawdziwej taśmy, z napisanymi na niej symbola-
mi wejściowymi. Zamiast nich mamy zwykle do czynienia z pewnymi zda-
rzeniami, które dzieją się w otoczeniu i wpływają na zachowanie urządzenia.
Ono samo też produkuje pewne zdarzenia wyjściowe. Jeżeli repertuar takich
zdarzeń jest skończony, to mogą one być uważane za alfabet (wejściowy lub
wyjściowy) danego urządzenia.
Czas szereguje zdarzenia. Dzięki czasowi wiemy, które zdarzenie jest
wcześniejsze, a które późniejsze. Możemy założyć, że zdarzenia są jak gdy-
by krótkimi „szpilkami” na osi czasu, a więc trwają – teoretycznie – tak
krótko, że praktycznie nigdy nie występują dokładnie jednocześnie. Przy
takim założeniu zdarzenia układają się w dobrze uporządkowane sekwencje,
a my możemy do opisywania zachowania fizycznych urządzeń w czasie wy-
korzystywać pojęcia z dziedziny teorii automatów skończonych.
Dla ilustracji, pomyślmy o urządzeniu, które w potocznym języku lu-
dzie często i tak nazywają właśnie automatem. Niech to będzie maszyna do
sprzedaży – powiedzmy – batoników i gumy do żucia. Oczywiście, ma ona
wiele mechanizmów i podzespołów, lecz zarządza nimi układ sterowania,
który jest technicznie zrealizowanym automatem skończonym.
Dla uproszczenia załóżmy, że maszyna przyjmuje tylko jeden rodzaj
monet (na przykład pojedyncze złotówki) i że guma kosztuje jedną zło-
tówkę, a batonik – dwie. Klient, stając przed maszyną, widzi szczelinę do
wrzucania monet oraz trzy przyciski z napisami guma, baton oraz anuluj.
Instrukcja poucza go, że powinien wrzucić odpowiednią sumę, a potem na-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Automat skończony jako model zachowania fizycznego urządzenia 233

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

234 10. Automaty skończone

Rys. 10.6. Automat sterujący maszyną do sprzedaży

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

11. System dwójkowy

Dlaczego właśnie dwójkowy?


Już w poprzednich rozdziałach wspominaliśmy, że w praktyce posługujemy
się wieloma różnymi alfabetami, które powstawały ewolucyjnie, na prze-
strzeni tysięcy lat, w różnych kręgach cywilizacyjnych. Tymczasem wszyst-
kie dzisiejsze komputery w swojej fizycznej konstrukcji wykorzystują jeden,
i to najprostszy, alfabet dwójkowy (binarny), który zawiera jedynie dwa
umowne symbole, zapisywane najczęściej jako 0 i 1. Co więcej, nie spo-
sób nie zauważyć, że rozwój w dziedzinie technicznych urządzeń do celów
przetwarzania informacji gwałtownie przyspieszył właśnie od chwili, kiedy
zastosowano w nich zasadę dwójkowego kodowania danych i instrukcji.
Istotnie, tak było. Aż do lat czterdziestych XX wieku wszelkie urzą-
dzenia i maszyny, które miały pomóc ludziom w rejestrowaniu, przesyłaniu
i przetwarzaniu informacji, starano się budować tak, by ich fizyczna konstruk-
cja były bezpośrednio dostosowana do „ludzkiego” alfabetu. Komplikowało
to znacznie budowę tych urządzeń i w istocie hamowało postęp.
Mechaniczne arytmometry i rejestratory kasowe wykorzystywały al-
fabet dziesiętny. Miały one zespół kilku bębnów, każdy o dziesięciu moż-
liwych położeniach, opisanych cyframi od 0 do 9. Cyfry te pojawiały się
w okienku rejestratora, gdy sprzedawca czy barman ustawiał (za pomocą
odpowiednich dźwigienek) kwotę do zapłaty. Wewnątrz znajdował się dość
złożony mechanizm, który potrafił sumować cały ciąg wprowadzanych
w ten sposób liczb dziesiętnych. A przecież całe to przywiązanie do systemu
dziesiętnego ma za jedyne uzasadnienie fakt, że posiadamy dziesięć palców
u rąk. Gdybyśmy mieli po sześć palców u każdej ręki – najprawdopodobniej
liczylibyśmy w systemie dwunastkowym.
Podobnie rzecz się miała z maszynami do pisania, bez których nie
mógł się obejść żaden urząd, firma, redakcja, a także indywidualny pisarz
czy tłumacz. Każda taka maszyna „obsługiwała” wybrany alfabet (polski,
angielski, cyrylicę...) złożony z kilkudziesięciu znaków, dużych i małych.
Każda para znaków miała swój klawisz i swoją czcionkę, do tego jakieś
dźwigienki, sprężynki, przeguby i zapadki. Do tego jeszcze mechanizmy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

236 11. System dwójkowy

przesuwania i podnoszenia wózka, zmiany wiersza, powrotu do początku


wiersza, ustawiania tabulacji... Jeśli ktoś ma jeszcze taką maszynę w domu
– niech zajrzy do środka, a zadziwi się złożonością i zmyślnością zastoso-
wanych tam mechanicznych rozwiązań.
Niezależnie od uznania dla pomysłowości konstruktorów musimy
przyznać, że łatwiej byłoby taką maszynę zbudować, gdyby alfabet (na przy-
kład polski) wymagał nie trzydziestu kilku klawiszy, tylko – powiedzmy
– piętnastu. Albo nie piętnastu, tylko pięciu. Albo nie pięciu, tylko dwóch.
Im mniej – tym byłoby prościej. Jednak mniej niż dwa klawisze już być nie
może: wiemy, że alfabet musi mieć co najmniej dwa symbole, w tym przy-
padku – choćby jakiś znak i spację, żeby za jego pomocą dało się cokolwiek
interesującego wyrazić.
Dotyczy to również podzespołów, służących do wykonywania ope-
racji arytmetycznych i logicznych. Weźmy na przykład zmorę dzieciń-
stwa: tabliczkę mnożenia. Nauczenie się na pamięć zasad mnożenia jed-
nocyfrowych liczb dziesiętnych – wymaga zapamiętania stu zależności.
Tymczasem dla systemu dwójkowego cała tabliczka mnożenia jednocyfro-
wych liczb sprowadza się do następujących czterech reguł:
0 ⋅ 0 = 0;
0 ⋅ 1 = 0;
1 ⋅ 0 = 0;
1 ⋅ 1 = 1.
Przeciętnie bystremu uczniowi opanowanie takiej tabliczki mnożenia po-
winno zająć piętnaście minut, a nie wiele miesięcy. Nawet nie znając zasad
budowy arytmometru, można też zgadnąć, że zbudowanie mechanicznego
czy elektrycznego dwójkowego układu mnożącego, działającego zgodnie
z tymi czterema regułami, jest zadaniem znacznie prostszym niż konstrukcja
analogicznego urządzenia dziesiętnego, które musi się stosować do tamtych
tradycyjnych stu reguł.
Alfabet dwójkowy daje także stosunkowo największą pewność, że
zapisane (zapamiętane) informacje zostaną potem prawidłowo odczytane.
Każda maszyna do przetwarzania tych danych musi być bowiem wypo-
sażona w „pamięć”, a więc zestaw urządzeń, zbudowanych tak, żeby każ-
de z nich było w stanie zapamiętać jeden, i to dowolny spośród symboli
alfabetu. Jeżeli alfabet liczy n symboli, to elementarna komórka pamięci
musi mieć również n wyróżnionych stanów, umownie odpowiadających
tym symbolom. Na przykład mechaniczny bęben w rejestratorze kasowym,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dlaczego właśnie dwójkowy? 237

„pamiętający” jedną cyfrę dziesiętną, ma dziesięć wyróżnionych położeń,


opisanych cyframi od 0 do 9. Podczas nastawiania bęben powinien szybko
przeskakiwać pomiędzy tymi położeniami, zatrzymując się tylko na owych
wyraźnie przewidzianych dziesięciu pozycjach. Wymaga to zapewne kółka
zębatego o dziesięciu zębach, jakiejś zapadki, sprężynki... Podobnie hipo-
tetyczna dziesięciostanowa elektroniczna komórka pamięci musiałaby mieć
np. dziesięć wyróżnionych poziomów napięcia wyjściowego itd.
Jednak na skutek zużycia elementów, zewnętrznych zakłóceń, zmian
temperatury itd. (co jest szczególnie wyraźnie widoczne w przypadku urzą-
dzeń elektronicznych) rzeczywisty stan układu nie jest w praktyce nigdy
dokładnie równy zamierzonemu, lecz waha się gdzieś w jego okolicach,
w pewnych dopuszczalnych granicach tolerancji. Tymczasem, im więcej
symboli w alfabecie, tym sąsiednie stany mniej się różnią od siebie: war-
tością kąta obrotu bębna, napięcia wyjściowego itp., więc dopuszczalne
granice tolerancji też są mniejsze. I odwrotnie: im mniej symboli zawiera
alfabet, tym sąsiednie stany komórki pamięci różnią się od siebie bardziej,
a dopuszczalny przedział zmienności jest większy. Układ jest wtedy bar-
dziej odporny na zakłócenia, a więc stwarza mniejsze zagrożenie tym, że
zapisany symbol zostanie później błędnie odczytany. Alfabet dwójkowy ma
również i tutaj przewagę nad innymi alfabetami.
Jednak przewaga systemu dwójkowego opiera się na jeszcze jed-
nym, zupełnie niespodziewanym i niezwykłym fakcie. Okazało się bowiem,
że jest on ściśle powiązany z dwuwartościową logiką. Jej początki sięgają
czasów Arystotelesa, który żył przecież ponad dwa tysiące lat temu. Potem,
przez całe stulecia opracowywano i doskonalono metody i formułowano pra-
wa logiki, reguły poprawnego wnioskowania itp., które dwudziestowieczni
inżynierowie mogli potem wprost, natychmiast zastosować do projektowania
dwustanowych urządzeń. Kiedy i jak odkryto tę możliwość i na czym te me-
tody polegają – opowiemy bardziej szczegółowo w następnym rozdziale.
Wadą systemu dwójkowego było natomiast to, że nie odpowiadał on
wielowiekowym ludzkim przyzwyczajeniom: znanym powszechnie abeca-
dłom, dziesiętnym liczbom itd. Szybko jednak okazało się, że w praktyce
nie jest to żadna wada. Tłumaczeniem naszych znaków, liczb, obrazów itd.
na postać dwójkową (i odwrotnie) – zajmuje się sam komputer, a my nie
musimy się o to martwić.
Niemniej, warto wiedzieć, na czym polegają obliczenia na dwójko-
wych liczbach i ciągach znaków.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

238 11. System dwójkowy

Dwójkowe liczby całkowite i podstawowe


arytmetyczne operacje na nich
Pierwszym działem matematyki, z którym mamy do czynienia na samym
początku szkolnej edukacji, jest arytmetyka. Poznajemy tam najpierw liczby
naturalne, potem – dowiadujemy się, że jest też zero, liczby ujemne i ułam-
ki. Uczymy się je dodawać, odejmować, mnożyć i dzielić, potem – także
podnosić je do potęgi i wyciągać z nich pierwiastki.
W przypadku dziesiętnego systemu zapisywania liczb używanym al-
fabetem jest zbiór zawierający trzynaście znaków, wśród których są:
– cyfry (arabskie): 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
– dwa symbole znaku (+ oraz – ), pozwalające odróżniać liczby dodatnie
i ujemne,
– przecinek dziesiętny1, służący do oddzielania części całkowitej od ułam-
kowej.
Mając do dyspozycji alfabet dwójkowy, a więc tylko zbiór {0, 1},
musimy znaleźć sposób na wyrażanie przy jego użyciu wszystkich tych in-
formacji, które przyzwyczailiśmy się zapisywać – w przypadku liczb dzie-
siętnych – za pomocą wymienionych trzynastu symboli.
Zacznijmy – podobnie jak w szkole – od liczb naturalnych, bez znaku
i części ułamkowej.
Najprostszy sposób dwójkowego kodowania liczb naturalnych jest
skonstruowany bardzo podobnie jak konwencjonalny, wszystkim znany
dziesiętny system przedstawiania liczb. Oba opierają się na zasadzie zapisu
systematycznego, wagowo-pozycyjnego. Znaczy to, że każda liczba ma pew-
ną liczbę pozycji, ponumerowanych w systematyczny sposób od prawej do
lewej. Pozycja znajdująca się na prawym krańcu liczby ma numer 0, na-
stępna w lewo – numer 1, następna – numer 2 i tak dalej. Każdej pozy-
cji odpowiada pewna waga. W systemie dziesiętnym, pozycji 0 odpowiada
waga w0 = 1, pozycji 1 – waga w1 = 10, pozycji 2 – waga w2 = 100 itd.
Wagi są więc kolejnymi potęgami liczby 10, która jest tu podstawą syste-

1 W pisowni anglosaskiej, a także w językach programowania, część całkowitą


liczby oddziela się najczęściej od części ułamkowej kropką (ang. point), a nie
przecinkiem.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dwójkowe liczby całkowite i podstawowe arytmetyczne operacje na nich 239

... 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

mu. Ogólnie, pozycji o numerze i odpowiada waga wi = 10i. Powstaje w ten


sposób pewien szablon, w który wpisujemy arabskie cyfry tak, że na każdej
pozycji stoi dokładnie jedna z dziesięciu możliwych cyfr.
Jeżeli wpiszemy tam ciąg, składający się z pięciu symboli: 53 712
(jak w powyższym przykładzie) i wiemy, że ma to być liczba dziesiętna,
to rozumiemy, że zapisaliśmy liczbę, na którą składają się (czytając zgod-
nie z kierunkiem narastania numerów pozycji, a więc od strony prawej do
lewej) dwie jednostki, jedna dziesiątka, siedem setek, trzy tysiące oraz pięć
dziesiątek tysięcy. Razem pięćdziesiąt trzy tysiące siedemset dwanaście.
Na marginesie warto zauważyć, że systematyczny, wagowo-pozy-
cyjny sposób zapisywania liczb dziesiętnych wynaleziono w Indiach, zaś
Arabowie, poprzez których dotarł on w średniowieczu do Europy – posłu-
gują się zupełnie innymi symbolami graficznymi do oznaczenia cyfr od 0 do
9. Znaki, które my nazywamy cyframi arabskimi, w rzeczywistości w ogóle
w ich pisowni nie występują. Jednak narody arabskie piszą (i czytają) swo-
je teksty od strony prawej do lewej. Prawdziwa „arabskość” omówionego
wyżej sposobu zapisywania liczb polega więc nie na wyglądzie graficznych
symboli cyfr, lecz na tym, że pozycje w ramach liczby są numerowane właś-
nie w tym kierunku. Podobnie przebiega dodawanie, odejmowanie czy mno-
żenie dwóch liczb. Zaczynamy te operacje od jednostek, poprzez dziesiątki,
setki itd., a więc posuwając się od prawego końca w lewo. Tak też prze-
mieszcza się przeniesienie w dodawaniu (lub pożyczka w przypadku odej-
mowania). Dla pisowni arabskiej – to naturalne, dla języków europejskich
– dziwaczna niekonsekwencja, tyle że się do niej przyzwyczailiśmy.
Skonstruujmy teraz analogiczny (również wagowo-pozycyjny) dwój-
kowy system do zapisywania liczb naturalnych. W systemie dwójkowym
podstawą jest 2, więc w tym przypadku i-tej pozycji będzie odpowiadać
waga wi = 2i. Analogicznie zbudowany szablon do zapisywania liczb dwój-
kowych wygląda tak, jak na rysunku 11.2.
Podobnie jak poprzednio, pozycje są numerowane od strony prawej do le-
wej, kolejnym pozycjom odpowiadają wagi, będące kolejnymi potęgami
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

240 11. System dwójkowy

... 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

Rys. 11.2. Zasada dwójkowego systemu zapisywania liczb naturalnych

liczby 2, natomiast w powstałe w ten sposób okienko wpisujemy cyfry 0


albo 1, tak że na każdej pozycji stoi dokładnie jedna z tych dwóch moż-
liwych cyfr. Ten sposób binarnego zapisywania liczb naturalnych nazywa
się kodem NB (ang. Natural Binary) albo – w Polsce – NKB (Naturalny
Kod Binarny).
Łatwo policzyć, że wpisany w powyższym przykładzie ciąg 01110011,
interpretowany jako liczba w kodzie NB, ma w zapisie dziesiętnym wartość
równą 115:
(01110011)NB = (1 · 1 + 1 · 2 + 0 · 4 + 0 · 8 + 1 · 16 + 1 · 32 + 1 · 64 + 0 · 128)10
= 11510
W praktyce, aby obliczyć wartość liczby, jaką dany ciąg zer i jedynek repre-
zentuje w kodzie NB, wystarczy obliczyć sumę wag tych pozycji, na których
stoi jedynka2.
Przypomnijmy (choć to wszyscy już dziś chyba wiedzą), że pozycje
dwójkowe nazywamy bitami (w języku angielskim to skrót od binary digit,
czyli cyfra dwójkowa, a jednocześnie bit – to mały kawałek, cząstka, kęs).
Liczba (01110011)NB jest to więc ośmiobitowa liczba w kodzie NB.
Zapewne ze względu na wartość odpowiadających im wag, bit znaj-
dujący się najdalej z prawej strony (na pozycji nr 0) jest nazywany bi-
tem najmniej znaczącym (ang. least significant bit), a bit o największym
numerze pozycji – najbardziej znaczącym bitem danej liczby (ang. most si-
gnificant bit). Te określenia są stosowane także w odniesieniu do ciągów bi-

2 Nawet na palcach można liczyć w systemie dwójkowym, wystarczy trochę poćwi-


czyć. Przyjmijmy, że palec zagięty – to zero, wyprostowany – jeden. Dłoń zwinięta
w pięść – to zero (00000). Wyprostowany kciuk – jeden (00001). Wyprostowany
palec wskazujący (ale kciuk zwinięty) – to dwa (00010). Wyprostowany kciuk
i palec wskazujący – to trzy (00011). Ze względu na dobre obyczaje liczby cztery
nie pokażemy. Pięć – to 00101 i tak dalej. Za pomocą jednej ręki da się w ten
sposób liczyć od 0 do 31, za pomocą dwóch rąk – od 0 do 1023. Dlaczego nikt
na to nie wpadł ze dwa tysiące lat temu?
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dwójkowe liczby całkowite i podstawowe arytmetyczne operacje na nich 241

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

242 11. System dwójkowy

dziesiętną dzielimy przez 2, iloraz zapisujemy w postaci liczby całkowitej


i reszty z dzielenia, a następnie powtarzamy te czynności aż do uzyskania
w wyniku 0. W przykładzie z rysunku 11.3 zadaną liczbą jest 57, więc ko-
lejno piszemy:
– 57 : 2 = 28 z resztą 1,
– 28 : 2 = 14 z resztą 0,
– 14 : 2 = 7 z resztą 0,
– i tak dalej, aż do 1 : 2 = 0 z resztą 1.

Rys. 11.3. Przekształcanie liczby dziesiętnej na dwójkową

Teraz wystarczy przewrócić kolumnę reszt w prawo – i otrzymuje się za-


daną liczbę w kodzie NB. Na wszelki wypadek sprawdźmy. Dopiszmy nad
poszczególnymi pozycjami ich wagi i policzmy. Istotnie, zgadza się: 32 +
+ 16 + 8 + 1 = 57.
Najprostszą operacją na liczbach w kodzie NB jest dodawanie.
Przebiega ono bardzo podobnie jak w znanym nam systemie dziesiętnym,
ale jest nieporównanie łatwiejsze. Wystarczy zapamiętać następujące cztery
reguły dodawania dwóch jednobitowych liczb dwójkowych:
0 + 0 = 0,
0 + 1 = 1,
1 + 0 = 1,
1 + 1 = 10.
Pierwsze trzy są tak banalne, że nie warto ich komentować. Czwarta niesie
równie rewelacyjną informację, że jeden plus jeden to dwa. Rzeczywiście
(10)NB = 210. W tym ostatnim przypadku wynik dodawania dwóch jednocy-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dwójkowe liczby całkowite i podstawowe arytmetyczne operacje na nich 243

frowych liczb jest jednak dwucyfrowy. Znaczy to, że powstało przeniesienie


(o wartości 1), które propaguje się w lewo, na następną pozycję.
Dla ujednolicenia zapisu przyjmijmy, że przeniesienie powstaje za-
wsze, lecz w pierwszych trzech przypadkach ma wartość 0, a jedynie
w ostatnim przypadku ma wartość 1. Wtedy powyższe cztery reguły przyj-
mą następującą postać:
0 + 0 = 00,
0 + 1 = 01,
1 + 0 = 01,
1 + 1 = 10.
Dodając do siebie dwie wielobitowe liczby w kodzie NB, rozpoczynamy
po arabsku, od pozycji najmniej znaczącej. Wyznaczamy jednobitowy wy-
nik na tej pozycji oraz przeniesienie, które powędruje na pozycję następną
(w lewo). Na tej następnej pozycji będziemy musieli dodać do siebie już trzy
jednobitowe liczby: dwie będące pierwotnymi argumentami operacji doda-
wania (na tej pozycji) oraz owo przeniesienie. Możemy to zrobić w dwóch
krokach: najpierw dodać dowolne dwie z tych trzech jednobitowych liczb,
potem – do wyniku dodać trzecią, stosując w obu tych dodawaniach te same
podane wyżej trzy proste reguły. Wykonawszy to, znów zapisujemy wynik
na tej pozycji oraz przeniesienie na następną i posuwamy się w ten sposób
dalej, w lewo, aż do końca słowa.
Na rysunku 11.4 zestawiono w postaci jednej tabelki wartości wyni-
ku (wi) oraz przeniesienia (ci + 1) dla i-tej pozycji liczby (i = 0, 1, ..., n – 1),
w zależności od trzech jednobitowych liczb: przeniesienia ci, które przyszło
na daną, i-tą pozycję, oraz wartości argumentów ai, bi na tejże i-tej pozycji.

Rys. 11.4. Dodawanie dwójkowych liczb naturalnych


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

244 11. System dwójkowy

Przykład, przytoczony na rysunku 11.5, powinien rozwiać wszelkie wąt-


pliwości.

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

Rys. 11.5. Przykład dodawania w kodzie NB

Zauważmy, że w trakcie dodawania na najbardziej znaczącej pozycji (pozycja


numer 7) powstało przeniesienie c8 = 0. Formalnie, wylatuje ono w lewo poza
granicę słowa, ale nie jest to sytuacja groźna: w przypadku kodu NB zerowe
końcowe przeniesienie oznacza, że wynik zmieścił się w ramach słowa.
Na rysunku 11.6 przytoczono inny przykład, w którym tak się nie dzie-
je. W tym dodawaniu, na najbardziej znaczącej pozycji powstaje niezerowe
przeniesienie: c8 = 1, które wybiega poza lewą granicę słowa. W kodzie
NB jest to ostrzeżenie, że wynik nie mieści się w słowie o założonej dłu-
gości, w tym przypadku 8 bitów. Istotnie, dodawane liczby to A = 11510,
B = 21810, wynik A + B = 33310, podczas gdy największą liczbą, jaką można
zapisać w ośmiobitowym słowie, jest (11111111)NB = 25510.

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

Rys. 11.6. Wynik dodawania nie mieści się w słowie

W podobny sposób można zanalizować i opracować zasady odejmowania


liczb w kodzie NB. W tym przypadku przeniesienie przekazywane na następ-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dwójkowe liczby całkowite i podstawowe arytmetyczne operacje na nich 245

ną pozycję będzie miało wartość ujemną, ponieważ ma charakter pożyczki


i na tej następnej pozycji powinno być odjęte od bezpośredniego wyniku.
Również i w tym przypadku ewentualne pojawienie się niezerowego
ujemnego przeniesienia (pożyczki) poza lewą granicą słowa oznacza ostrze-
żenie, że wynik (gdyby go odczytać tak, jak się odczytuje ośmiobitową liczbę
NB) nie jest poprawny. Widocznie odejmowaliśmy liczbę większą od mniej-
szej i rezultat „chce być” mniejszy od zera. To byłoby także przekroczenie
dozwolonego zakresu liczb, ponieważ najmniejszą liczbą w kodzie NB jest
(tutaj: ośmiobitowa) liczba 00000000, czyli zero.
Warto dla ćwiczenia samodzielnie opracować tabelę analogiczną do tej
z rysunku 11.4, ale dla odejmowania, a następnie sprawdzić jej poprawność
na paru przykładach.
Skoro poznaliśmy już zasadę kodu NB, rozszerzymy teraz tę konwen-
cję tak, by móc zapisać liczby całkowite zarówno dodatnie, jak i ujemne.
Sposobów, by to osiągnąć, można wymyślić dowolnie wiele, my ograni-
czymy się tu do dwóch najczęściej stosowanych metod kodowania liczb ze
znakiem:
– metoda znak–wartość (inaczej znak–moduł),
– metoda uzupełnień do 2 (U2).
Notacja znak–wartość (ang. sign–value) lub znak–moduł, w skrócie
ZM, polega na tym, by zarezerwować w n-bitowym słowie jeden bit (po-
wiedzmy, że najbardziej znaczący) na znak liczby i umówić się, że bitowi
temu nie odpowiada żadna waga: będzie się tam znajdował tylko umowny
kod znaku. W pozostałej części słowa zapisuje się w zwyczajnym kodzie
NB wartość bezwzględną liczby (czyli jej moduł, nazwijmy go M). Jeśli bit
znaku ma wartość 0, to liczba M, zapisana na pozostałych pozycjach sło-
wa, ma być traktowana jako nieujemna, natomiast jeśli bit znaku jest równy
1 – to jako liczba ujemna (–M) (rysunek 11.7).

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)

Rys. 11.7. Przykładowe liczby przeciwne w kodzie ZM


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

246 11. System dwójkowy

Liczby, które dają się zapisać w n-bitowym słowie w kodzie ZM,


mieszczą się w zakresie od (111...111) ZM = –(2n –1 – 1) do (0111...111)ZM =
= + (2n – 1 – 1), np. w słowie ośmiobitowym – od (–127)10 do (+127)10. Zero
ma dwie możliwe reprezentacje: zarówno (000...000), jak (100...000) mają
wartość równą zeru, podobnie jak w systemie dziesiętnym +0 oraz –0.
W metodzie uzupełnień do 2, w skrócie U2 (ang. two’s complement),
wagi wszystkich pozycji pozostają na swoim miejscu. Jednak największą
wagę, odpowiadającą pozycji najbardziej znaczącej (i tylko ją), traktuje się
jako liczbę ujemną.
Na rysunku 11.8 pokazano dwie przykładowe liczby w kodzie U2.
Liczba A (już nam znana) jest dodatnia i ma wartość:
(01110011)U2 = 64 + 32 + 16 + 2 + 1 = 11510.
Natomiast liczba B jest ujemna:
(11110011) U2 = –128 + 64 + 32 + 16 + 2 + 1 = (–13)10.

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

Rys. 11.8. Przykładowe liczby całkowite w kodzie U2

Tu również bit najbardziej znaczący (ten o ujemnej wadze) nazywany jest


bitem znaku. Rzeczywiście, podobnie jak w notacji znak–moduł (choć z in-
nego powodu), jeśli jest on równy 0, to liczba jest nieujemna, a jeśli 1, to
ujemna.
Dodawanie dwóch liczb w kodzie U2 przebiega według dokładnie
takich samych reguł, jak w kodzie NB, i to dla wszystkich pozycji słowa,
łącznie z tą najbardziej znaczącą, o ujemnej wadze. Nieco inne są jedynie
zasady ostrzegania o przekroczeniu zakresu liczb mieszczących się w sło-
wie. Tym razem nie wystarczy (jak w NB) obserwowanie przeniesienia cn
wychodzącego poza granicę n-bitowego słowa. Należy jeszcze uwzględnić
przeniesienie cn – 1, a więc to, które pojawia się na pozycji najbardziej zna-
czącej w rezultacie dodawania na pozycji poprzedniej, o numerze n – 2.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dwójkowe liczby całkowite i podstawowe arytmetyczne operacje na nich 247

Wynik dodawania mieści się w zakresie właściwym dla n-bitowego


słowa, jeżeli oba wymienione przeniesienia mają wartość 0 albo oba mają
wartość 1. Jeżeli natomiast różnią się od siebie – to znaczy, że wynik doda-
wania wyszedł poza zakres liczb, jakie można zapisać (w kodzie U2) w ra-
mach n-bitowego słowa. W informatycznym żargonie mówi się wtedy, że
pojawił się nadmiar (ang. overflow).
Ta zasada jest zilustrowana na rysunku 11.9. Pokazane są tam trzy
przykładowe dodawania par ośmiobitowych liczb. W pierwszych dwóch
przypadkach oba wspomniane przeniesienia (wyróżnione zacieniowanym
polem) mają identyczną wartość: albo 1 i 1, albo 0 i 0, więc wynik jest
poprawny, bo mieści się w granicach ośmiobitowego słowa. W trzecim
przypadku przeniesienia różnią się od siebie, a wynik (odczytany na ośmiu
bitach) poprawny nie jest: wynosi 4210 , podczas gdy spodziewaliśmy się, że
uzyskamy (–214)10. Dzieje się tak dlatego, że największą ośmiobitową licz-
bą w U2 jest (01111111)U2 = 12710, natomiast najmniejszą (10000000)U2 =
= (–128)10, więc spodziewany wynik wypada poza ten zakres. Zauważmy
jednak, że gdybyśmy zastosowali dłuższe słowo, na przykład dziewięciobi-

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 ???

Rys. 11.9. Trzy przykłady dodawania w kodzie U2


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

248 11. System dwójkowy

towe, to najbardziej znacząca pozycja miałaby numer 8 i ujemną wagę rów-


ną (–256). Wtedy ten błędny wynik (tu równy 4210) nabrałby sensu, gdyż
(–256) + 42 = –214, czyli dokładnie tyle, ile się spodziewaliśmy.
Zmiana liczby na przeciwną (tj. o tej samej wartości bezwzględnej,
lecz o przeciwnym znaku) jest w systemie U2 nieco trudniejsza niż w syste-
mie znak–moduł. Tam (w ZM) wystarczyło zanegować jeden bit znaku (tzn.
jeśli był równy 0 – to zamienić na 1, a jeśli był 1 – to zamienić na 0). Tutaj
(w U2) wykonuje się to w dwóch krokach:
– trzeba zanegować wszystkie bity danego słowa (zamienić zera na jedynki
i odwrotnie, jedynki na zera),
– do powstałej w ten sposób liczby dodać 1.
Na rysunku 11.10 pokazano przykład takiej operacji, i to wykonanej
(z dobrym skutkiem) dwukrotnie, tam i z powrotem.

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.10. Zamiana liczby na przeciwną w kodzie U2

W przykładach z rysunku 11.10, w operacji dodawania jedynki, zamiast do-


dawać słowo zawierające liczbę 00000001, wykorzystaliśmy znany nam już
mechanizm przemieszczenia się przeniesienia. W poprzednich przykładach
dodawania przeniesienie c0 (na najmniej znaczącej pozycji) nie pojawiało
się, ponieważ nie miało gdzie się zrodzić. Równie dobrze mogliśmy zało-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Dwójkowe liczby całkowite i podstawowe arytmetyczne operacje na nich 249

żyć, że jest tam zawsze zero. Jednak przeniesienie c0 = 1 (zaznaczone na


rysunku 11.10 zacieniowanym polem) można tam sztucznie, celowo wpro-
wadzić, a rezultat będzie taki, jak gdybyśmy wymusili zwiększenie wartości
tej liczby o jeden. Ten sposób zwiększania liczby o jeden jest często wyko-
rzystywany w jednostkach arytmetyczno-logicznych wchodzących w skład
prawdziwych procesorów.
Skoro już umiemy zamienić liczbę na przeciwną, zyskujemy możli-
wość wykonania odejmowania (np. A – B) w kodzie U2 na dwa sposoby:
– bezpośrednio, w oparciu o tabelkę odejmowania, której wprawdzie nie
pokazaliśmy, ale sugerowaliśmy jej samodzielne opracowanie przez ana-
logię do tabelki dodawania (rysunek 11.4),
– przez zamianę odjemnika (tzn. liczby B) na liczbę przeciwną (–B) i wy-
konanie dodawania A + (–B).
Inną często występującą w praktyce operacją na liczbach jest ich
arytmetyczne przesuwanie o jedną pozycję w lewo lub w prawo, co oznacza
mnożenie i dzielenie przez dwa. Przy przesuwaniu wszystkich cyfr danej
liczby o jedną pozycję w lewo (rysunek 11.11) każda cyfra wskakuje na
pozycję o wadze dwukrotnie razy większej niż dotąd (a zwolniony, najmniej
znaczący bit zapełnia się zerem). Łatwo policzyć, że wartość całej liczby
wzrośnie dwukrotnie.
Oczywiście, dwukrotne przesunięcie w lewo oznacza mnożenie przez
4, trzykrotne – przez 8, i tak dalej. Jednak również i w tym przypadku na-
leży się wystrzegać nadmiaru (tj. przekroczenia zakresu liczb dających się

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

250 11. System dwójkowy

zapisać w słowie o założonej długości). W systemie U2 nadmiar nie wystą-


pi, jeśli dwa najbardziej znaczące bity liczby (przed przesunięciem) mają
taką samą wartość, to znaczy są równe 0 i 0 dla liczb nieujemnych albo 1
i 1 dla liczb ujemnych. W przeciwnym przypadku cyfra wchodząca (w wy-
niku przesunięcia) na najbardziej znaczącą pozycją zmieniłaby na przeciw-
ną dotychczasową wartość tego najbardziej znaczącego bitu, a wiemy, że
w U2 to jest bit znaku, o ujemnej wadze. Wynik byłby więc błędny, poza
zakresem i do tego przeciwnego znaku. W obu przypadkach pokazanych na
rysunku 11.11 żadnej z otrzymanych liczb (+102 oraz –126) nie można więc
jeszcze raz przesunąć arytmetycznie w lewo: ich dwa najbardziej znaczące
bity mają różną wartość. I rzeczywiście, podwojenie każdej z nich dałoby
rezultat (+204 oraz –252) wykraczający poza zakres ośmiobitowych liczb.
Operacja dzielenia przez 2 w kodzie U2 przebiega analogicznie, tylko
liczbę przesuwa się w przeciwnym kierunku, w prawo. Również i tu najbar-
dziej znaczący kraniec słowa należy potraktować szczególnie. Przy każdym
przesunięciu w prawo, najbardziej znaczący bit (znaku) pozostaje na swoim
miejscu, mało tego, jego wartość kopiuje się na zwolnione miejsce (pozycja
tuż obok bitu znaku). Pozostała część słowa przesuwa się w prawo o jedną
pozycję, a dotychczasowy bit najmniej znaczący wypada poza granicę sło-
wa i ginie. Jeśli miał wartość zero – niewielka strata, jeśli jeden – jest to
równoważne zaokrągleniu liczby. Oszczędzimy sobie oddzielnego przykła-
du. Opisane zasady zobaczymy na rysunku 11.11, wystarczy tylko odwrócić
zwrot wszystkich strzałek.
W praktyce obliczeń na binarnych danych występują także dwa inne
rodzaje operacji przesuwania słowa o jedną pozycję w lewo i w prawo,
a mianowicie przesuwanie logiczne oraz cykliczne. W tych przypadkach
słowo nie jest interpretowane jako liczba (w której bit najbardziej znaczą-
cy wymaga szczególnego traktowania, ponieważ jest bitem znaku), lecz po
prostu jako pewien ciąg zer i jedynek.
Przy przesuwaniu logicznym cała zawartość jest przesuwana w lewo
albo w prawo o jedną pozycję, bit wypychany poza granicę słowa ginie4,
a na zwolnione miejsce na drugim krańcu słowa wpisywane jest zero.

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

Inne sposoby przedstawiania liczb całkowitych 251

W przypadku przesuwania cyklicznego bit wypychany wchodzi na zwol-


nione miejsce na drugiej stronie słowa, tak jak gdyby słowo było zawinięte
w okrągły obwarzanek.
Nie będziemy nużyć czytelnika dalszymi przykładami arytmetycz-
nych operacji na całkowitych liczbach dwójkowych. Darujemy sobie rów-
nież omawianie szczegółów mnożenia i dzielenia dwóch liczb przez siebie.
Ktoś, kto potrafi ręcznie, na papierze pomnożyć lub podzielić przez siebie
dwie liczby dziesiętne – potrafi również wyobrazić sobie, jak operacje te
przebiegają w systemie dwójkowym. W obu systemach mnożenie polega
na wielokrotnym arytmetycznym przesuwaniu mnożnej o jedną pozycję
w lewo, mnożeniu jej przez kolejną cyfrę mnożnika (a więc w tym przypad-
ku – tylko przez 0 albo 1) i dodawaniu wyniku do stopniowo tworzącego się
iloczynu. Dzielenie – na analogicznym odejmowaniu od dzielnej stopniowo
przesuwanego w prawo dzielnika.
Oczywiście, każda z tych operacji ma pewne specyficzne zasady,
związane z samym sposobem kodowania liczb, rozpoznawaniem przekro-
czenia zakresu, postępowaniem w przypadku argumentów różniących się
znakiem itd. Ich omawianie niepotrzebnie wystawiłoby na próbę cierpli-
wość czytelników. Najważniejsze, by uświadomić sobie, że:
– na liczbach dwójkowych można wykonać wszystkie podstawowe opera-
cje arytmetyczne, które znamy z działań na liczbach dziesiętnych,
– co więcej, są one technicznie znacznie prostsze, co wynika z prostoty
samego alfabetu dwójkowego,
– zasady tych działań zależą od sposobu dwójkowego kodowania liczb, ale
zawsze można je przedstawić, posługując się zero-jedynkowymi zależno-
ściami, takimi na przykład, jak tabela przytoczona na rysunku 11.4.

Inne sposoby przedstawiania liczb całkowitych


Warto choćby wspomnieć o tym, że sposobów przedstawiania liczb dodat-
nich i ujemnych można wymyślić wiele i rzeczywiście, w historii kompute-
rów posługiwano się wieloma systemami zapisu innymi niż tylko NB, znak–
–moduł czy U2. Dotyczy to zwłaszcza początkowego okresu współczesnej
informatyki, czyli lat czterdziestych i pięćdziesiątych XX wieku.
Wraz z upływem lat stopniowo wykruszały się pomysły mniej fortun-
ne, a pozostawały w użycie te, które okazały się najwygodniejsze. Proces
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

252 11. System dwójkowy

ten przybrał na sile wraz ze standaryzacją sprzętu i oprogramowania (a co


za tym idzie – formatów danych), a zwłaszcza wraz z opanowaniem se-
ryjnej produkcji układów scalonych. Teraz, i to już od dobrych czterdzie-
stu lat, producenci systemów komputerowych nie wymyślają samodzielnie
własnych układów arytmetyki dwójkowej, tylko kupują je od producenta
układów scalonych jako integralną część procesora, karty dźwiękowej czy
graficznej.
Układy arytmetyczne współczesnych procesorów są najczęściej oparte
na systemie U2. Niemniej, w niektórych zastosowaniach można spotkać inne
systemy dwójkowego zapisu. W szczególności, na wzmiankę zasługują:
– system uzupełnień do 1 (w skrócie U1, ang. one’s complement), gdzie
najbardziej znaczącej pozycji odpowiada waga wn – 1 = –(2n – 1 – 1),
a więc np. dla słowa ośmiobitowego (–127), a nie, jak w U2, (–128);
– systemy zapisu „z przesunięciem” (ang. bias), gdzie liczbę np. A zapi-
suje się w n-bitowym słowie tak, jak gdyby to była liczba (A + 2 n – 1)
w kodzie NB. W ten sposób np. wszystkie ośmiobitowe liczby z zakresu
[–128, +127] wyglądają jak nieujemne, bo dodano do nich 27 = 128;
– zapis dziesiętnych liczb naturalnych nie w kodzie NB, lecz w kodzie
BCD, to znaczy jako ciąg cyfr dziesiętnych kodowanych dwójkowo (ang.
binary-coded decimals).
W tej ostatniej metodzie dziesiętną liczbę koduje się tak, że każdą jej
cyfrę oddzielnie przedstawia się za pomocą czterobitowej liczby, zapisanej
w kodzie NB lub jakiejś jego odmianie. Na przykład przedstawiona w ten
sposób dziesiętna liczba 7654 ma postać:

0111 0110 0101 0100.

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

Liczby stałoprzecinkowe i zmiennoprzecinkowe 253

ELWRO, przy czym seryjna, przemysłowa produkcja oznaczała w tamtych


czasach wykonanie 25 sztuk tych maszyn w ciągu około dwóch lat.
W maszynie UMC-1 słowa miały długość 36 bitów (o ośmiobitowych
bajtach jeszcze się wtedy nie mówiło). System zapisywania liczb był sys-
tematyczny, wagowo-pozycyjny, lecz „minus-minus-dwójkowy”. Oznaczało
to, że pozycje były ponumerowane standardowo, po arabsku, od prawej do
lewej strony, ale i-tej pozycji odpowiadała waga (–(–2)i ). Tak więc kolejne
wagi wynosiły (czytając od prawej do lewej) nie 1, 2, 4, 8, 16, ... itd. (jak
w kodzie NB czy U2), ale –1, +2, –4, +8, –16, … i tak dalej. Była to zresztą
modyfikacja systemu minus dwójkowego (wagi o postaci (–2)i ), zastosowa-
nego nieco wcześniej w maszynie cyfrowej EMAL, jednym z pierwszych,
eksperymentalnych polskich komputerów, zaprojektowanym w Polskiej
Akademii Nauk.

Liczby stałoprzecinkowe i zmiennoprzecinkowe


Pozostał nam jeszcze jeden problem: dwójkowe kodowanie ułamków
oraz liczb mieszanych, składających się z części całkowitej i ułamkowej.
Oczywiście, wiąże się to z użyciem przecinka (lub – w anglosaskiej pisowni
– kropki), oddzielającej część całkowitą od ułamkowej.
Warto sobie uświadomić, jaką rolę odgrywa przecinek w każdym sys-
tematycznym zapisie wagowo-pozycyjnym. Do tej pory zakładaliśmy, że
poszczególne pozycje w ramach liczby są numerowane od zera, a kolejne
numery pozycji narastają w kierunku od strony prawej do lewej. Ale prze-
cież można tę numerację kontynuować również w prawą stronę, wykorzy-
stując w numeracji pozycji liczby ujemne, jak na rysunku 11.12.

 3 
 23 
 16 10
Rys. 11.12. Rola przecinka w liczbach dwójkowych

Interpretacja wag przypisanych poszczególnym pozycjom pozostaje do-


kładnie taka sama, jak poprzednio: pozycji o numerze i nadal odpowiada
waga wi = r i, gdzie r jest podstawą danego systemu zapisywania liczb.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

254 11. System dwójkowy

W systemie dziesiętnym, pozycji i = –1 odpowiada więc waga w–1 = 10–1 =


= 0,1 (pozycja dziesiątych części), pozycji i = –2 – waga w–2 = 10–2 = 0,01
(setne części) itd. Analogicznie, dla systemu dwójkowego, wagi dla pozycji
o ujemnych numerach wynoszą odpowiednio w–1 = 1/2, w–2 = 1/4, ... i tak
dalej. Przecinek jest więc niczym innym niż umownym symbolem, zazna-
czającym, gdzie zaczynają się pozycje o ujemnych numerach.
Aby ominąć konieczność jawnego umieszczania w słowie liczbowym
znaku przecinka, stosuje się dwa sposoby zapisywania liczb: liczby stało-
przecinkowe (ang. fixed point numbers) oraz zmiennoprzecinkowe (ang. flo-
ating point numbers).
Pierwszy ze sposobów polega na tym, by przecinka w ogóle nie wpi-
sywać, tylko przyjąć założenie, że wszystkie liczby biorące udział w obli-
czeniach mają przecinek umieszczony na stałe, w umówionym miejscu, jed-
nakowym dla wszystkich liczb. Najczęściej stosuje się dwa rodzaje takich
stałoprzecinkowych liczb: liczby całkowite oraz ułamki właściwe.
Liczby całkowite już znamy: przecinek (gdybyśmy koniecznie chcieli
go zapisać) znajdowałby się zaraz za (w prawo) najmniej znaczącym bitem
słowa. W przypadku ułamków domniemane miejsce przecinka jest zaraz za
najbardziej znaczącym bitem liczby, tzn. w słowie n-bitowym między po-
zycjami o numerach (n – 1) i (n – 2). Jeżeli wiemy, że mamy do czynienia
z ułamkiem – łatwo odtworzymy układ wag i wartość tej liczby.
Notacja zmiennoprzecinkowa opiera się z kolei na spostrzeżeniu, że
każdą liczbę można przedstawić jako ułamek pomnożony przez podstawę
systemu (liczbę 10 dla dziesiętnego, 2 dla dwójkowego) podniesioną do
pewnej całkowitej potęgi. Dla przykładu:
– dziesiętną liczbę +1234,567 można zapisać jako +0,1234567 ⋅ 10+4,
– dziesiętną liczbę –0,0001234567 można zapisać jako –0,1234567 ⋅ 10–3,
itd.
W takim zapisie biorą udział dwie stałoprzecinkowe liczby, a mia-
nowicie ułamek (inaczej – mantysa) liczby (ang. mantissa lub significand)
oraz wykładnik (ang. exponent), który jest liczbą całkowitą. Dla uproszcze-
nia zapisu można pominąć elementy stałe: znak mnożenia, podstawę (w tym
przypadku liczbę 10), konieczność pisania wykładnika ponad zwykłym po-
ziomem tekstu, a nawet zero oraz przecinek rozpoczynające (z lewej strony)
każdy ułamek. Ktoś, kto zna tę konwencję, i tak bez trudu odtworzy rzeczy-
wistą wartość całej liczby.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Liczby stałoprzecinkowe i zmiennoprzecinkowe 255

Przypuśćmy dla przykładu, że posługujemy się (wciąż jeszcze w sys-


temie dziesiętnym) dziesięciocyfrowymi ułamkami i trzycyfrowymi wykład-
nikami, zapisywanymi w kodzie znak–moduł. Wtedy przykładowe liczby
+1234,567 oraz –0,0001234567 przybrałyby zmiennoprzecinkową postać,
taką jak na rysunku 11.13.

Rys. 11.13. Dziesiętne liczby z przecinkiem i ich odpowiedniki zmiennoprzecinkowe

Zauważmy, że wykładnik niesie informację o tym, o ile pozycji od domyśl-


nego położenia przecinka w ułamku należy przesunąć ten przecinek, jeśli się
chce daną liczbę zapisać w konwencjonalnej postaci. W pierwszym z przy-
kładów na rysunku 11.13 wykładnik ma wartość +4 i przecinek (w zapisie
z przecinkiem) znajduje się o cztery pozycje w prawo od jego domyślnego
położenia w ułamku. W drugim – znajduje się o „minus trzy pozycje w pra-
wo”, to znaczy – o trzy pozycje w lewo.
Sama konwencja, że liczba – to U ⋅ 10W (U – ułamek, W – wykład-
nik), nie wystarcza, by jednoznacznie ustalić sposób zapisywania liczby.
Rzeczywiście, tę samą przykładową liczbę 1234,567 można zapisać jako
+0,1234567 ⋅ 10+4 albo jako +0,01234567 ⋅ 10+5, albo jako +0,001234567
⋅ 10+6...i tak dalej. Wprowadza się więc pojęcie ułamka znormalizowanego.
W dziesiętnym systemie znak–moduł ułamek znormalizowany rozpoczy-
na się (po domyślnym przecinku) od niezerowej cyfry, jest więc jak gdyby
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

256 11. System dwójkowy

maksymalnie „dosunięty do lewej strony”, bez zbędnych zer po przecinku.


Oczywiście, odpowiada temu najmniejszy możliwy wykładnik. Cała licz-
ba zmiennoprzecinkowa jest znormalizowana, jeśli jej ułamek i wykładnik
spełniają te warunki. Tak więc na przykład liczba +0,1234567 ⋅ 10+4 jest
znormalizowana, a równa jej liczba +0,01234567 ⋅ 10+5 nie jest.
Notacja zmiennoprzecinkowa umożliwia operowanie zarówno liczba-
mi bardzo wielkimi, jak bardzo małymi co do wartości bezwzględnej. Dla
przykładu, w dziesiętnym formacie jak na rysunku 11.13 możliwe jest za-
pisanie liczby tak wielkiej, jak +0,9999999999 ⋅ 10+999 lub tak małej, jak
+0,1000000000 ⋅ 10–999. Wątpliwe, czy komuś starczyłoby cierpliwości
(i miejsca), żeby zapisać te liczby na papierze, w konwencjonalnej postaci
„z jawnym przecinkiem”.
Te same zasady możemy łatwo przenieść na grunt systemu dwójkowe-
go. Podobnie jak poprzednio, binarna liczba zmiennoprzecinkowa jest parą
dwójkowych liczb stałoprzecinkowych. Jedna z nich jest ułamkiem, druga
– wykładnikiem. Każda z nich jest liczbą ze znakiem (a więc może być
ujemna lub nieujemna). Cała liczba zmiennoprzecinkowa musi być znorma-
lizowana, co oznacza, że ułamek musi być przesunięty (arytmetycznie) jak
najdalej w lewo.
Nie będziemy szczegółowo omawiali podstawowych czterech działań
arytmetycznych na liczbach zmiennoprzecinkowych. Wystarczy, jeśli po-
wiemy, że są to łatwe do uzasadnienia operacje na dwóch parach liczb sta-
łoprzecinkowych: ułamkach oraz wykładnikach obu argumentów. Tak więc,
aby pomnożyć przez siebie dwie zmiennoprzecinkowe liczby, należy:
– pomnożyć przez siebie (stałoprzecinkowo) ułamki obu argumentów,
– dodać do siebie (stałoprzecinkowo) wykładniki obu argumentów,
– znormalizować wynik, to znaczy przesuwać ułamek arytmetycznie
w lewo dopóty, dopóki jeszcze pozostaje ułamkiem, odejmując przy każ-
dym przesunięciu od wykładnika jeden.
Podobnie, aby wykonać dzielenie A : B, należy:
– podzielić (stałoprzecinkowo) ułamek liczby A przez ułamek liczby B,
– odjąć (stałoprzecinkowo) wykładnik liczby B od wykładnika liczby A,
– znormalizować wynik.
Nieco bardziej skomplikowane jest dodawanie i odejmowanie liczb
zmiennoprzecinkowych. Najpierw trzeba doprowadzić do tego, by oba argu-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Liczby stałoprzecinkowe i zmiennoprzecinkowe 257

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

258 11. System dwójkowy

Notacja heksadecymalna (szesnastkowa)


Kilkakrotnie podkreślaliśmy, że prostota systemu dwójkowego prowadzi do
łatwej technicznej realizacji układów pamięci, arytmometru komputera itp.
Nie da się jednak zaprzeczyć, że dla człowieka zapisy dwójkowe są rozwle-
kłe, nieprzyjazne i monotonne.
Spróbujmy skądś przepisać albo komuś podyktować choćby szesna-
stobitowy ciąg zer i jedynek, na przykład 1011001100111100. Jeden zero je-
den jeden zero zero jeden jeden zero zero jeden jeden jeden jeden zero zero.
Ani to zapamiętać, ani zapisać bez pomylenia się ze trzy razy. Dlatego w ta-
kich przypadkach informatycy posługują się często notacją szesnastkową
(inaczej: heksadecymalną, ang. hexadecimal), zamieszczoną w tabeli 11.1.
Jej celem jest kodowanie każdej czwórki bitów za pomocą jednego znaku
z konwencjonalnego, „ludzkiego” alfabetu.

Tab. 11.1. Kod szesnastkowy (heksadecymalny)


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

W pierwszej kolumnie tabeli pokazano szesnaście możliwych czterobito-


wych kombinacji zer i jedynek. W drugiej kolumnie pierwszym dziesięciu
kombinacjom przypisano dziesiętne cyfry (od 0 do 9). Odpowiadają one
po prostu numerycznej wartości poszczególnych czwórek, odczytywanych
jako krótkie liczby naturalne w kodzie NB (poszczególnym pozycjom odpo-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Kodowanie znaków alfanumerycznych 259

wiadają wagi 8421). Gdyby w podobny sposób odczytywać również dalsze


czwórki – ich dziesiętne odpowiedniki musiałyby być już dwucyfrowe: 10,
11, 12, 13 itd. Dlatego pozostałym sześciu kombinacjom przypisano pierw-
sze litery łacińskiego alfabetu: od A do F.
Teraz, mając długi ciąg zer i jedynek, wystarczy go podzielić na
czwórki bitów i każdą z nich zakodować za pomocą jednego znaku, według
powyższej tabeli. Nasz przykładowy binarny ciąg 1011001100111100 przy-
bierze wtedy postać D33C. Prawda, że tak łatwiej?
W taki skrótowy sposób zapisuje się najczęściej (dla ludzkiej wy-
gody) binarne ciągi ośmio-, szesnasto- czy trzydziestodwubitowe, bo takie
zwykle występują w komputerowej praktyce. Dzielą się one łatwo na pełne
czwórki bitów. Gdyby jednak ktoś chciał zakodować ciąg o innej długo-
ści (np. siedmio- czy jedenastobitowy), to powinien rozpocząć dzielenie go
na czwórki od najmniej znaczącego końca, a ostatnią niepełną grupę bitów
uzupełnić z lewej strony domyślnymi zerami.
Sama zasada notacji heksadecymalnej jest prosta i pożyteczna. W prak-
tyce trzeba jednak uzupełnić ją pewnymi dodatkowymi oznaczeniami, aby
odróżnić grupę znaków interpretowanych jako zapis heksadecymalny od in-
nych, oznaczających coś innego. Czy 1011 to ciąg czterech bitów (jeden
zero zero jeden), czy też może ciąg szesnastu bitów: 0001000000010001,
zapisany w notacji szesnastkowej? A może dziesiętna liczba tysiąc jedena-
ście? Czy BACA, to, no..., baca, czy też 1011101011001010?
Stosując konwencję, której używaliśmy już wyżej w tym rozdziale,
dla zaznaczenia, że chodzi o kod heksadecymalny, powinniśmy pisać np.
101116, BACA16 itd. Użycie dolnego indeksu jest jednak typograficznie nie-
wygodne i kłopotliwe. Dlatego w różnych językach programowania i języ-
kach opisu danych wprowadza się inne konwencje, polegające na poprze-
dzeniu (lub zakończeniu) heksadecymalnego kodu umówionym znakiem
lub grupą znaków, np. H, h, &H, 0x, #, 16#, %, U+ itd. Niestety, nie ma na
to jednej, powszechnie obowiązującej zasady.

Kodowanie znaków alfanumerycznych


Potrzeba kodowania wiadomości alfanumerycznych (a więc tekstów, składa-
jących się z liter konwencjonalnego alfabetu, cyfr, znaków przestankowych
itd.) powstała na długo przed tym, zanim zaczęto w ogóle mówić o informa-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

260 11. System dwójkowy

tyce. Jest równie stara, jak próby przekazywania wiadomości na odległość,


a więc – jak telekomunikacja.
Pomińmy prehistorię tej dziedziny: sygnalizację za pomocą sygnałów
dymnych, tam-tamów, umieszczonych na wzgórzach zwierciadeł albo sema-
forów o kilku ramionach, których ustawienie oznaczało poszczególne zna-
ki alfabetu. Współczesna era telekomunikacji rozpoczęła się na dobre wraz
z uruchomieniem w roku 1837 elektrycznego telegrafu Samuela Morse’a.
Towarzyszyło temu opracowanie alfabetu Morse’a (ang. Morse code), a więc
ustandaryzowanego sposobu kodowania liter angielskiego alfabetu i dziesię-
ciu cyfr (a później także znaków przestankowych itp.) przy użyciu krótkich
i długich impulsów elektrycznych, dźwiękowych lub optycznych.
W kodzie Morse’a litery i cyfry mają postać ciągów, składających
się z kilku elementarnych symboli: kropki (kreski krótkiej), kreski (długiej)
oraz trzech rodzajów przerw (o różnej długości), dla zaznaczania odstępów
między kreskami wewnątrz litery, między literami oraz między słowami
tekstu. Budowa tych ciągów jest dość arbitralna, podobnie jak sposób ich
przypisania poszczególnym literom alfabetu. Jedyna zasada, której starano
się przestrzegać, polegała na tym, że litery częściej występujące (w języku
angielskim) mają nieco prostsze odpowiedniki kodowe.
Umiejętność biegłego porozumiewania się przy użyciu kodu Morse’a
była obowiązkowa w środowisku telegrafistów, radiotelegrafistów, lotni-
ków, marynarzy, radioamatorów itd. przez blisko 160 lat. Jednak już dość
wcześnie zorientowano się, że zmienna długość kodowych odpowiedników
poszczególnych znaków alfabetu utrudnia konstruowanie urządzeń teleko-
munikacyjnych, które pracowałyby w tym kodzie. Dlatego już w końcu
XIX wieku zaczęto opracowywać inne kody (np. kod Baudota), w których
znaki alfanumeryczne były kodowane za pomocą grup impulsów o tej sa-
mej, stałej liczbie elementów, tj. impulsów i przerw między nimi. W latach
dwudziestych i trzydziestych XX wieku zaczęto budować międzynarodową
sieć dalekopisową i teleksową, w której znaki alfanumeryczne kodowano
za pomocą pięciobitowych grup kodowych, zgodnie z pewną modyfikacją
kodu Baudota. Wkrótce sieć ta objęła swym zasięgiem cały ówczesny cy-
wilizowany świat.
W tej sytuacji, konstruktorzy pierwszych komputerów (z lat czter-
dziestych i pięćdziesiątych, a nawet sześćdziesiątych XX wieku) często wy-
korzystywali pocztowe dalekopisy (ang. teletype, teletypewriter) jako pod-
stawowe urządzenie wejścia-wyjścia. Odgrywały one – z punktu widzenia
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Kodowanie znaków alfanumerycznych 261

operatora i użytkownika maszyny – taką rolę, jak dziś klawiatura, monitor


ekranowy i drukarka jednocześnie. Były to urządzenia wprawdzie stosunko-
wo drogie, wolne i hałaśliwe – ale już gotowe, rozpowszechnione i produ-
kowane w dostatecznie dużych ilościach.
Niestety, repertuar ograniczony do 58 znaków i sposób ich kodowania
w Międzynarodowym Kodzie Telegraficznym nr 2 (lub ITA nr 2, taką nazwę
nosi ów dalekopisowy kod) nie odpowiadał potrzebom informatyki, a i dla
samej szybko się rozwijającej telekomunikacji stawał się coraz bardziej
niewystarczający. Na początku lat sześćdziesiątych XX wieku w Stanach
Zjednoczonych opracowano więc (i wprowadzono od 1963 r.) nowy kod
telekomunikacyjny o nazwie ASCII (czytane jako „aski”), który (z później-
szymi modyfikacjami) szybko się upowszechnił w obu wspomnianych dzie-
dzinach i jest z powodzeniem używany do dnia dzisiejszego.
Do czasu powszechnego zaakceptowania kodu ASCII producenci nie
tylko komputerów, ale również klawiatur, drukarek, czytników i dziurkarek
kart i taśmy perforowanej itd. – opracowywali i propagowali własne sposo-
by binarnego kodowania znaków alfanumerycznych, z oczywistą szkodą dla
wymienności zarówno urządzeń, jak i oprogramowania. Obecnie kod ASCII
jest wykorzystywany w większości systemów i urządzeń komputerowych.
Praktycznie jedynym (ale ważnym) wyjątkiem jest koncern IBM, który
w swoich wielkich komputerach (ang. mainframes) konsekwentnie trzy-
ma się własnego kodu alfanumerycznego o nazwie EBCDIC, pochodzą-
cego jeszcze z epoki kart dziurkowanych5. Tłumaczenie z kodu ASCII na
EBCDIC i odwrotnie jest jednak bardzo łatwe i nie sprawia trudności.
Skrót ASCII rozwija się jako American Standard Code for Information
Interchange (amerykański standardowy kod do wymiany informacji), co
zdradza jego telekomunikacyjne pochodzenie. Ślady tego pochodzenia wy-
raźnie widać również w repertuarze znaków, zwłaszcza w obecności i zna-
czeniu tzw. kodów sterujących, o czym dalej.

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

262 11. System dwójkowy

Podstawowa tablica kodu ASCII (tabela 11.2) zawiera 128 znaków,


rozmieszczonych w 16 wierszach i 8 kolumnach. Wiersze są ponumerowane
kolejnymi czterobitowymi numerami w kodzie NB (od 0000 do 1111), nato-
miast kolumny – numerami trzybitowymi (od 000 do 111).

Tab. 11.2. Podstawowa tablica kodu ASCII


000 001 010 011 100 101 110 111
0000 NUL DLE spacja 0 @ P ` p
0001 SOH DC1 ! 1 A Q a q
0010 STX DC2 ” 2 B R b r
0011 ETX DC3 # 3 C S c s
0100 EOT DC4 $ 4 D T d t
0101 ENQ NAK % 5 E U e u
0110 ACK SYN & 6 F V f v
0111 BEL ETB ‘ 7 G W g w
1000 BS CAN ( 8 H X h x
1001 HT EM ) 9 I Y i y
1010 LF SUB * : J Z j z
1011 VT ESC + ; K [ k {
1100 FF FS , < L \ l |
1101 CR GS - = M ] m }
1110 SO RS . > N ^ n ~
1111 SI US / ? O _ o DEL

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

Kodowanie znaków alfanumerycznych 263

– EOT (End of Transmission ) oznacza koniec transmisji,


– ACK (Acknowledgement) oznacza potwierdzenie poprawnego odebrania
bloku znaków, NAK (Negative Acknowledgement) – stwierdzenie, że blok
nie został odebrany poprawnie,
– LF (Line Feed) – rozpocznij do nowego wiersza (przy drukowaniu wia-
domości),
– CR (Carriage Return) – powróć do początku wiersza,
– FF (Form Feed) – rozpocznij od nowej strony
...i tak dalej. Nie będziemy tu omawiali ich wszystkich, ale po tych kilku
przykładach ich przeznaczenie jest już chyba jasne.
Pozostałe pola tablicy zajmują znaki, dobrze nam znane z kompute-
rowej klawiatury i z monitora ekranowego. Litery i cyfry są rozmieszczone
w logiczny i konsekwentny sposób. Cyfry od 0 do 9 są umieszczone w ko-
lumnie 011 tak, że binarny numer wiersza (odczytany jako naturalna liczba
dwójkowa) odpowiada numerycznej wartości danej cyfry. Litery również są
umieszczone w takiej kolejności, jak są one uszeregowane w łacińskim al-
fabecie: litera A w wierszu 1 (0001), B w wierszu 2 (0010), C – w wierszu
3 (0011) i tak dalej. Pozostałe miejsca w tablicy kodu ASCII wypełniają
najczęściej używane znaki przestankowe i specjalne.
Zasada dwójkowego kodowania wszystkich tych znaków sterujących
i alfanumerycznych jest prosta i łatwa do zapamiętania: binarny (siedmio-
bitowy) kod każdego znaku otrzymuje się przez napisanie dwójkowego nu-
meru kolumny, a następnie numeru wiersza, w którym dany znak stoi. Tak
więc na przykład:
– znak potwierdzenia (ACK), stojący w kolumnie 000, w wierszu 0110
– ma kod 000 0110,
– litera A (kolumna 100, wiersz 0001) – ma kod 100 0001,
– znak % (kolumna 010, wiersz 0101) – ma kod 010 0101 itd.
Kod ASCII mógłby być więc w zasadzie siedmiobitowy: tyle bowiem
bitów potrzeba do zapisania (w wyżej podany prosty sposób) wszystkich
128 znaków. Zdecydowano jednak, że binarne odpowiedniki znaków ASCII
będą ośmiobitowe, tak by każdy znak mógł być zapisany w postaci jednego
bajtu. Przed siedmiobitowym kodem znaku wstawia się więc jeszcze jeden,
ósmy bit, który staje się najbardziej znaczącym bitem bajtu.
W zastosowaniach telekomunikacyjnych ten dodatkowy bit bywa
wykorzystywany jako bit parzystości (ang. parity bit). Służy on wtedy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

264 11. System dwójkowy

jako jeden z mechanizmów pozwalających na wykrycie pewnych błędów


w transmisji. Pomysł polega na tym, by dla danego znaku bit parzystości
miał wartość 1, jeśli liczba jedynek w siedmiobitowym kodzie jest parzysta,
a wartość 0 – jeśli nieparzysta. W końcu prowadzi to do tego, że łączna
liczba jedynek w całym, ośmiobitowym bajcie jest zawsze nieparzysta7. 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. Są tu cztery jedynki, a więc bit parzystości
powinien zawierać 1, tak by łączna liczba jedynek była nieparzysta (oczy-
wiście równa 5). Ostatecznie, cyfra 9 kodowana jest (z bitem parzystości)
jako 1011 1001,
– litera T ma kod 101 0100. Liczba jedynek już jest nieparzysta (są tu trzy
jedynki), wiec bit parzystości jest równy 0 i ośmiobitowy kod litery T to
0101 0100.
Zaletą tego rozwiązania jest to, że odbiorca wiadomości może spraw-
dzić, czy w odebranym znaku jest umówiona (w tym przypadku nieparzy-
sta) liczba jedynek. Jeśli nie – znaczy to, że bajt został zniekształcony przez
zakłócenia w kanale transmisji danych i wiadomość należy powtórzyć.
Oczywiście, ta metoda upewniania się o poprawności odebranej
wiadomości jest prymitywna, w szczególności nie chroni przed występo-
waniem parzystej liczby błędów, np. zamianą dwóch zer na dwie jedynki
itd. Ale zasadę opatrywania wiadomości dodatkowymi bitami kontrol-
nymi, służącymi właśnie do kontroli poprawności, można rozszerzyć na
dłuższe (niż jeden znak) fragmenty wiadomości. Pomysł ten dał początek
teorii i praktyce kodów detekcyjnych (ang. error detecting codes), pozwa-
lających wykryć znacznie szerszą klasę błędów, a także kodów korekcyj-
nych (ang. error correcting codes), które potrafią nie tylko wykryć, ale
nawet od razu skorygować pewne rodzaje błędów. Nie będziemy ich sze-
rzej omawiali, podkreślając jedynie, że stanowią one ważną część dorobku
telekomunikacji.
Jednak w typowych zastosowaniach informatycznych ów dodatkowy,
ósmy bit wykorzystywany jest inaczej. Znaki wymienione w tabeli 11.2 są

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

Kodowanie znaków alfanumerycznych 265

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

266 11. System dwójkowy

a następnie przyjąć, że są one kodowane na podobnej zasadzie, z tym tylko


że ośmiobitowy kod znaku rozpoczyna się od jedynki, a nie – jak w przy-
padku podstawowej tabeli – od zera. Taka druga, dodatkowa tabela jest na-
zywana stroną kodową (ang. code page).
Jednak tych dodatkowych, a potrzebnych w praktyce znaków jest
wciąż więcej niż 128 i nie zmieszczą się one wszystkie na jednej stronie
kodowej. Dlatego istnieje wiele różnych wersji stron kodowych, dostosowa-
nych do różnych – najczęściej narodowych – potrzeb. Ich normalizacją zajęła
się międzynarodowa organizacja ISO (International Standards Organization),
która w normie ISO 8859 skodyfikowała 16 rodzajów stron kodowych. Jest
wśród nich 10 odmian stron kodowych dla alfabetu łacińskiego (kilka z nich
zawiera znaki polskie), a także strony rozszerzające podstawowy kod ASCII
o cyrylicę, alfabet grecki, hebrajski, arabski oraz tajski.
To jednak wszystko półśrodki. Kod ASCII, nawet rozszerzony o stro-
ny kodowe, jest w stanie zaspokoić przede wszystkim potrzeby narodów za-
mieszkujących Europę, obie Ameryki i Bliski Wschód. Jednak na świecie są
w użyciu jeszcze dziesiątki innych alfabetów, w tym takie, które co do zasad
budowy i repertuaru znaków bardzo różnią się od alfabetu łacińskiego, he-
brajskiego czy arabskiego. Przypomnijmy sobie choćby subkontynent indyj-
ski czy Azję Wschodnią: Chiny, Japonię, Koreę, całe Indochiny... Mieszka
tam kilka miliardów ludzi, mówiących setkami języków i narzeczy, chlubią-
cych się wielowiekową tradycją kulturalną, z bogatą literaturą pisaną. Jak
sobie poradzić z dwójkowym kodowaniem również ich alfanumerycznych
tekstów?
Konstruktywną próbę rozwiązania tego problemu podjęło międzyna-
rodowe konsorcjum o nazwie Unicode.

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

Projekt Unicode 267

Ustalenia konsorcjum są powszechnie respektowane w światowej


informatyce (i nie tylko w informatyce, również np. w językoznawstwie,
historiografii i wielu innych dziedzinach, posługujących się swoimi specjal-
nymi znakami), a proponowane przezeń kolejne wersje standardu znaków
są uzgadniane z międzynarodową organizacją normalizacyjną ISO (norma
ISO 10646).
Zadanie, jakie postawiło przed sobą konsorcjum Unicode, jest praw-
dziwie ambitne: zebrać i spisać w jednolity, uporządkowany sposób wszyst-
kie alfabety całego świata i każdemu występującemu w tych alfabetach
znakowi przypisać dwójkowy numer, który jednoznacznie identyfikuje ten
znak.
Dotyczy to nie tylko tych alfabetów, które są dziś w powszechnym
użyciu, ale także alfabetów archaicznych (jak pismo runiczne czy klinowe)
i systemów znaków, używanych przez narodowe mniejszości lub pewne gru-
py zainteresowań (np. symbole matematyczne i techniczne, umowne znaki
używane w meteorologii, notacja muzyczna, szachowa, karciana).
Tablica kodowa Unicode opiera się w gruncie rzeczy na tym sa-
mym pomyśle, co tablica ASCII, jednak jest znacznie, znacznie większa.
W ASCII było osiem kolumn i szesnaście wierszy, w tablicy Unicode
jest 216 = 65 536 kolumn i tyleż (216 = 65 536) wierszy. Każde „okien-
ko” tej tabeli (a więc miejsce na jeden znak) nazywane jest w terminolo-
gii Unicode punktem kodowym (ang. code point). W podstawowej tablicy
ASCII zmieściło się 128 znaków, w tabeli Unicode jest miejsce – teore-
tycznie – na 232 (a więc grubo ponad 4 miliardy!) znaków. Punktów kodo-
wych jest więc aż nadto wiele.
W roku 2003 konsorcjum Unicode zdecydowało się ograniczyć swoje
zainteresowanie do pierwszych siedemnastu kolumn, które nazywa płasz-
czyznami (ang. planes). Każda z nich ma oczywiście 216 = 65 536 wierszy,
zatem zawierają one łącznie i tak sporo ponad milion punktów kodowych.
Informatycy, językoznawcy, historycy, graficy, typografowie, pracujący dla
konsorcjum Unicode, stopniowo wypełniają owe punkty kodowe kolejny-
mi znakami, tworzącymi poszczególne alfabety. Każdy znak otrzymuje ofi-
cjalną nazwę, numer, a także podstawowy wzorzec graficzny. Towarzyszą
temu komentarze językoznawcze, historyczne i techniczne. Co pewien czas
konsorcjum podsumowuje i sankcjonuje ustalenia swoich specjalistów, pu-
blikując kolejną wersję standardu. Do tej pory skodyfikowano w ten sposób
około 100 000 znaków.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

268 11. System dwójkowy

Zasada tworzenia numerów poszczególnych znaków jest również taka


jak w ASCII. Każda kolumna i każdy wiersz ma swój numer, będący tym
razem szesnastobitową liczbą dwójkową w kodzie NB. Binarny 32-bitowy
numer znaku otrzymuje się przez napisanie numeru kolumny, a następnie
numeru wiersza punktu kodowego, w którym dany znak stoi. Podaje się
go zazwyczaj w postaci heksadecymalnej, poprzedzając go symbolami U+
i pomijając w nim ewentualne początkowe heksadecymalne zera.
Tak więc na przykład „łacińskie małe a” (oficjalnie: LATIN SMALL
LETTER A) stoi w kolumnie o dwójkowym numerze 0000 0000 0000 0000,
w wierszu 0000 0000 0110 0001. W postaci szesnastkowej pełny numer
tego znaku brzmiałby jako 00000061, w konwencji Unicode zapisuje się go
jako U+0061 lub wręcz U+61.
Z kolei np. grecka duża litera Ψ (GREEK CAPITAL LETTER PSI) ma
w Unicode numer U+03A8. Znaczy to, że jego pełna, ośmioznakowa heksade-
cymalna postać, to 000003A8. Odczytując ten numer w postaci dwójkowej, ła-
two stwierdzimy, że „greckie duże Ψ” stoi w kolumnie 0000 0000 0000 0000,
w wierszu 0000 0011 1010 1000.
Pierwsza kolumna tabeli Unicode (a właściwie zerowa, bo o dwój-
kowym numerze 0000000000000000) nosi nazwę BMP: Basic Multilingual
Plane (dosłownie: podstawowa płaszczyzna wielojęzyczna). Pierwsze 128
wierszy BMP zajmują znaki o numerach od U+0 do U+7F. To nic innego,
jak stary, poczciwy ASCII, dokładnie ten sam, co w tabeli 11.2. Następne
128 znaków – to strona kodowa zgodna z ISO 8859-1. Dalej idą kolejno
alfabet grecki, cyrylica, hebrajski, arabski, a potem praktycznie wszystkie
inne alfabety i zbiory znaków specjalnych, będące dziś w powszechnym
użyciu na świecie, łącznie ze znakami współczesnej pisowni chińskiej, ja-
pońskiej i koreańskiej (tzw. znaki CJK, od pierwszych liter nazw tych trzech
państw).
Następna kolumna (płaszczyzna) jest przeznaczona na alfabety histo-
ryczne (od hieroglifów egipskich i pisma klinowego do tajemniczych zna-
ków ze sławnego dysku z Fajstos, których znaczenia dotąd nie odczytano),
a także symbole matematyczne i muzyczne. W kolejnej kolumnie znalazło
miejsce około 40 000 rzadziej używanych ideogramów Han (pismo chiń-
skie). Co do pozostałych kolumn – to są na razie wykorzystane w niewielkim
stopniu (albo wcale) i dopiero czekają na wykorzystanie w przyszłości.
Kto zechce odwiedzić internetową witrynę konsorcjum Unicode
(www.unicode.org) – ryzykuje tym, że na długie godziny ugrzęźnie w pasjo-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Projekt Unicode 269

nujących informacjach o alfabetach i znakach używanych teraz lub w daw-


nych czasach przez ludzkie społeczności, których samego istnienia przed-
tem nawet nie podejrzewał. Może go to skłonić do refleksji nad niezwykłą
różnorodnością ludzkich kultur, pomysłowością różnych sposobów zapisy-
wania zgromadzonej przez nie wiedzy, a także – do uświadomienia sobie,
jak mało wiemy o świecie i jak bardzo zamykamy się na co dzień w naszym
rodzimym kręgu kulturowym.
Zgodnie z dewizą Unicode, nadany znakowi numer ma identyfikować
dany znak zawsze, niezależnie od platformy systemowej, języka programo-
wania albo edytora, przeglądarki czy innego programu komputerowego,
który dany znak przetwarza. Warto jednak podkreślić, że Unicode przypi-
suje binarny kod samej abstrakcyjnej idei znaku, nie zaś szczegółom jego
graficznego wyglądu. To ważne rozróżnienie. Sam znak, rozumiany jako
pewna abstrakcyjna idea – to nie to samo, co jego konkretna forma graficz-
na, czyli jego glif (ang. glyph).
Jest oto na przykład znak, który się nazywa „łacińskie małe a”
(LATIN SMALL LETTER A). Odpowiada mu oczywiście pewien pod-
stawowy graficzny symbol, którego wzorzec można znaleźć w standardzie
Unicode. Natomiast takie obrazy graficzne, jak a, a, a, a, a, a, a, a, a, a, a,
a, a, a – to przykłady różnych glifów tej samej litery. Ich repertuar zależy od
możliwości edytora, za pomocą którego pisany jest ten tekst. Każdy z tych
glifów może mieć jeszcze różną wielkość, różny kolor, może być zoriento-
wany poziomo lub pionowo itd. Zgodnie z zasadami Unicode, mają one jed-
nak wszystkie ten sam binarny numer, odpowiadający jednej abstrakcyjnej
literze: owemu „łacińskiemu małemu a”.
Dodatkowe atrybuty znaku, a więc krój czcionki (ang. font), wiel-
kość, kolor, orientacja itd., nie podlegają standaryzacji w Unicode. Sposób
ich kodowania definiuje już lokalnie, samodzielnie producent edytora,
twórca języka programowania itp. Dlatego zdarza się np. przy kopiowaniu
fragmentów tekstu z internetowej strony (utworzonej w języku HTML) do
edytora takiego jak MS Word – że sama treść skopiuje się poprawnie (bo
podstawowe kody znaków są takie same), lecz litery mają nieoczekiwaną
wielkość, czcionkę czy kolor (bo sposób kodowania tych dodatkowych atry-
butów był nieco różny w obu aplikacjach).
Trzeba podkreślić, że unikodowy binarny numer znaku – to nie to
samo, co binarna reprezentacja tego znaku w systemie komputerowym.
Gdyby każdy znak zapisywać binarnie w pamięci po prostu tak, jak brzmi
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

270 11. System dwójkowy

jego pełny 32-bitowy unikodowy numer – to na każdy znak trzeba by


było przeznaczyć całe 32-bitowe słowo, czyli aż cztery bajty w pamięci.
Tymczasem ktoś, komu wystarcza podstawowy kod ASCII (ewentualnie
z dodatkową, odpowiednią stroną kodową), potrzebuje do tego celu tylko
jednego bajtu. Konwencja Unicode byłaby więc dla wielu użytkowników
rozwiązaniem bardzo nieoszczędnym, wymagającym dużego rozmiaru pa-
mięci i dłuższego czasu na odczytywanie i zapisywanie z i do niej alfanu-
merycznych danych.
Dlatego opracowano trzy sposoby przekształcania unikodowych nu-
merów znaków na inny format, pozwalający pokonać wspomniane wyżej
trudności. Nazywają się one UTF-8, UTF-16 oraz UTF-32. Skrót UTF po-
chodzi od Unicode Transformation Format, czyli format przekształcania
Unikodu.
Format UTF-8 jest używany najczęściej, między innymi w licznych
internetowych protokołach i aplikacjach, na przykład w poczcie elektronicz-
nej i na internetowych stronach tworzonych z użyciem języka HTML. Jego
zasada przewiduje, że znaki kodowane są w grupach o różnej liczbie bajtów,
zależnie od ich położenia w tablicy.
Znaki z zerowej płaszczyzny (czyli BMP), o heksadecymalnych nu-
merach od U+0 do U+7F, są kodowane w UTF-8 za pomocą jednego baj-
tu, który ma na początku stałe zero, a po nim siedem bitów dokładnie ta-
kich, jak siedem najmniej znaczących bitów unikodowego numeru znaku.
Oczywiście, to nic innego, jak podstawowe znaki ASCII, i to kodowane
dokładnie tak, jak w tabeli 11.2, z zerem (a nie bitem parzystości) na po-
czątku.
Dalsza grupa znaków, o numerach od U+0080 do U+07FF, jest ko-
dowana za pomocą dwóch bajtów. Pierwszy (bardziej znaczący) ma na po-
czątku stałe dwie jedynki i zero (110), drugi – stałe dwa bity: 10. Pozostałe
bity są przepisane z końcowej (mniej znaczącej) części unikodowego nu-
meru znaku. Dla przykładu, wspomnieliśmy wyżej, że grecka duża litera
Ψ (GREEK CAPITAL LETTER PSI) ma w Unicode numer U+03A8. Stoi
więc ona w kolumnie numer zero (płaszczyzna BMP), w wierszu o dwój-
kowym numerze 0000 0011 1010 1000. W formacie UTF-8 jest ona re-
prezentowana przez dwie ósemki bitów o następującej budowie (stałe bity
– pogrubione):

11001110 10101000.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Projekt Unicode 271

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

272 11. System dwójkowy

Współczesne systemy operacyjne, oprogramowanie systemowe i apli-


kacyjne – muszą być więc skonstruowane tak, by można je było zainstalo-
wać w różnych wersjach językowych, uwzględniających również takie róż-
nice kulturowe. Zagadnienia te znacznie wykraczają jednak poza zakres ni-
niejszej książki, dlatego poprzestaniemy jedynie na ich zasygnalizowaniu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

12. Elementarz syntezy logicznej

Co oznacza ten tytuł?


Pod nazwą syntezy logicznej (ang. logic synthesis) rozumie się budowanie
układów logicznych, to znaczy takich cyfrowych urządzeń i podzespołów,
w których zarówno poszczególne wejścia, jak wyjścia mogą przyjmować
jedynie dwie wartości. Termin „logiczny” jest tu użyty w znaczeniu „dwój-
kowy” lub „binarny”, jest to więc synteza dwójkowa lub binarna.
Zależność między stanem wejść i stanem wyjść jest zazwyczaj
opisywana za pomocą tablic wypełnionych zerami i jedynkami, takimi
jak np. na rysunku 11.4 w poprzednim rozdziale. Układy takie wcho-
dzą w skład wszystkich współczesnych komputerów, mikroprocesorów,
cyfrowych układów sterowania, sterowników najróżniejszych cyfrowych
urządzeń itp.
Układy logiczne są zazwyczaj sieciami, zbudowanymi z odpowiednio
połączonych elementarnych cegiełek, zwanych bramkami logicznymi (ang.
logic gates). Wybór tych bramek (bo są one różnych typów) i sposób ich łą-
czenia w sieć tak, by wypełniały zadaną funkcję – jest właśnie przedmiotem
syntezy logicznej.
Sieci logiczne, składające się z samych tylko logicznych bramek, na-
zywane są także układami kombinacyjnymi lub przełączającymi (ang. com-
binatorial, switching networks lub circuits). Nie wystarczają one jednak do
zrealizowania wszelkich funkcji, których oczekujemy od podzespołu kom-
putera lub cyfrowego sterownika jakiegoś urządzenia. Nie dysponują one
bowiem możliwością pamiętania czegokolwiek, w szczególności np. pamię-
tania stanu własnego wyjścia z poprzedniej chwili.
Aby taką możliwość zapewnić – trzeba uzupełnić zestaw podstawo-
wych cegiełek, z których budowane są układy cyfrowe, o pewne dodatkowe
elementy, zwane przerzutnikami. Będzie temu poświęcony następny roz-
dział. Tam zobaczymy, jak można zaprojektować układ sekwencyjny (ang.
sequential circuit), złożony z odpowiednio połączonych bramek i przerzut-
ników, który jest zdolny do wykonywania całych sekwencji działań.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

274 12. Elementarz syntezy logicznej

Od Arystotelesa ze Stagiry do Claude'a Shannona


z Gaylord w stanie Michigan
W poprzednim rozdziale wspomnieliśmy, że jednym z argumentów przema-
wiających za dwójkowym systemem reprezentowania i przetwarzania infor-
macji jest istnienie klasycznej dwuwartościowej logiki, która – jak zauważo-
no w końcu lat trzydziestych XX wieku – może posłużyć jako gotowe, prak-
tyczne narzędzie do projektowania cyfrowych układów elektronicznych.
Istotnie, tak było. Początki logiki znajdziemy już w dziełach Arys-
totelesa (384–322 p.n.e.) i innych greckich filozofów ery starożytnej.
Wszystko wskazuje na to, że nie myśleli oni raczej o projektowaniu układów
elektronicznych, lecz uważali logikę przede wszystkim za narzędzie służą-
ce poprawnemu wnioskowaniu i formułowaniu myśli, użyteczne w sporach
filozoficznych, retoryce, dowodzeniu matematycznych twierdzeń, rozwiązy-
waniu zagadnień prawnych itd.
Już z tamtych czasów pochodzi pomysł, by złożone wypowiedzi
konstruować ze zdań prostych, połączonych spójnikami zdaniotwórczymi,
takimi jak LUB, I oraz NIE. Drugim ważnym pomysłem było założenie,
by odłożyć na bok znaczenie owych prostych i złożonych zdań, a skoncen-
trować się wyłącznie na tym, czy są one prawdziwe, czy fałszywe. Dało to
początek całej późniejszej logice formalnej.
Jeśli przyjmiemy, że treść zdania jest nieistotna, to wystarczy zdania
proste zastąpić elementarnymi zmiennymi zdaniowymi, oznaczanymi np. li-
terami: p, q, r, ... (albo x, y, z, ...). O każdej z tych zmiennych wiemy tylko
to, jak się nazywa oraz to, że może przyjmować wyłącznie dwie warto-
ści logiczne: może być tylko albo prawdziwa, albo fałszywa. W klasycznej
dwuwartościowej logice innej, trzeciej możliwości nie ma, po łacinie – ter-
tium non datur. To tak zwana zasada wyłączonego środka, sformułowana
też (choć zapewne po grecku) przez Arystotelesa.
Przy tym założeniu, zdania złożone stają się wyrażeniami, zbudo-
wanymi ze zmiennych zdaniowych, połączonych spójnikami logicznymi.
Mając tak zbudowane zdanie złożone, chcielibyśmy wiedzieć, czy jest ono
(jako całość) prawdziwe, czy fałszywe, zależnie od prawdziwości i fałszy-
wości występujących w nim zmiennych.
Najprostsze zdanie złożone składa się z jednej zmiennej zdaniowej
(na przykład p) i jednego spójnika: NIE. Jeżeli zdanie p jest prawdziwe, to
zdanie NIE p (inaczej „nieprawda, że p”) jest fałszywe. I odwrotnie, jeśli
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Od Arystotelesa ze Stagiry do Claude'a Shannona z Gaylord... 275

zmienna p ma wartość „fałszywe”, to zdanie NIE p jako całość ma wartość


„prawdziwe”.
Nieco bardziej złożone zależności pojawiają się w przypadku spójni-
ków LUB oraz I. Do ich opisania konstruuje się tzw. tabelki prawdy1 (ang.
truth tables), takie jak tabele 12.1 oraz 12.2, znane nam zapewne już ze
szkolnego kursu logiki.

Tab. 12.1. Tabelka prawdy dla spójnika LUB


p q p LUB q
Fałsz Fałsz Fałsz
Fałsz Prawda Prawda
Prawda Fałsz Prawda
Prawda Prawda Prawda

Tab. 12.2. Tabelka prawdy dla spójnika I


p q pIq
Fałsz Fałsz Fałsz
Fałsz Prawda Fałsz
Prawda Fałsz Fałsz
Prawda Prawda Prawda

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ą.

1 Lepiej chyba byłoby powiedzieć „tabelki prawdziwości” lub „prawdziwości i nie-


prawdziwości”, ale nazwa „tabelki prawdy” już się przyjęła.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

276 12. Elementarz syntezy logicznej

Mogę oto na przykład powiedzieć, że mam w tej kieszeni pięciozło-


tówkę, a także, że mam w tej kieszeni kwit z pralni. Każde z tych zdań może
być albo fałszywe, albo prawdziwe. Jeżeli powiem, że mam w kieszeni pię-
ciozłotówkę LUB kwit z pralni – będzie to prawda, jeżeli rzeczywiście mam
w kieszeni co najmniej jedną z tych dwóch rzeczy, a może obie. Jeżeli jed-
nak powiem, że mam w kieszeni pięciozłotówkę I kwit z pralni – będzie to
prawdą jedynie wtedy, gdy mam je obie jednocześnie. Jest to zgodne i z in-
tuicją, i z podanymi wyżej tabelkami prawdy.
Spójników logicznych jest więcej, niż tylko NIE, I oraz LUB, i nie
muszą one być spójnikami w gramatycznym znaczeniu. Ważną rolę odgry-
wają również na przykład operatory: implikacji (inaczej wynikania, wyraża-
jący fakt, że „z p wynika q”), alternatywy wykluczającej („prawdziwe jest
albo p, albo q, ale nie oba naraz”), a także logicznej równoważności („zda-
nie p jest logicznie równoważne zdaniu q”, to znaczy, że oba mają zawsze
taką samą wartość logiczną: albo oba są fałszywe, albo oba są prawdziwe
). Każdy ze spójników można opisać odpowiadającą mu tabelką prawdy,
o analogicznej budowie jak te, które pokazaliśmy dla spójników I oraz LUB.
Oczywiście, zarówno sumy, jak iloczyny logiczne mogą być wielo-, a nie
tylko dwuczłonowe (np. p LUB q LUB r). Można także w podobny sposób
konstruować jeszcze bardziej złożone zdania, zawierające wiele spójników
logicznych.
Streściliśmy tu w brawurowym skrócie założenia jednego z podstawo-
wych działów klasycznej logiki, a mianowicie rachunku zdań (ang. propositional
logic), wyrosłego na przestrzeni lat z rozważań dawnych filozofów. Zajmowano
się nim (no, może nie zawsze bardzo intensywnie) przez ponad dwa tysiąclecia,
a na pewno od czasów średniowiecza, kiedy to jak gdyby ponownie odkryto
dorobek Arystotelesa i innych antycznych filozofów. Znaleziono wtedy i udo-
wodniono wiele praw logiki, opracowano terminologię i notację, upraszczającą
zapisywanie zależności rachunku zdań. Równolegle rozwijały się też inne dzia-
ły klasycznej (w tym także tej nieformalnej) logiki.
W połowie XIX wieku (w 1853 r.) brytyjski matematyk George Boole
(1815–1864) przedstawił koncepcję nowego typu algebry, zwanej (jak łatwo
zgadnąć) algebrą Boole’a (ang. Boolean algebra). Jest to wyjątkowo ma-
tematycznie elegancka abstrakcyjna struktura, która znajduje zastosowanie
w wielu podstawowych działach matematyki. Z logiką ma ona taki zwią-
zek, że okazało się, iż rachunek zdań jest szczególnym przypadkiem algebry
Boole’a, jeśli tylko wartości logiczne prawdziwe i fałszywe zastąpić przez
1 oraz 0, a logiczne operatory NIE, LUB, I zdefiniować formalnie jako re-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Od Arystotelesa ze Stagiry do Claude'a Shannona z Gaylord... 277

lacje, występujące w tej algebrze. Kilka stron dalej sami zobaczymy, co to


znaczy i jak można to odkrycie wykorzystać w praktyce.
Dzięki odkryciu Boole’a rachunek zdań zyskał solidną matematyczną
podstawę i szereg dobrze określonych reguł stosowania operatorów logicz-
nych. Złożone zdania logiczne można było teraz zapisywać tak, jak mate-
matyczne formuły, a następnie przekształcać je w sposób podobny do tego,
jak w konwencjonalnej, szkolnej algebrze przekształcamy symboliczne
wyrażenia, wyciągając pewien czynnik przed nawias, mnożąc dwie sumy
zawarte w nawiasach, redukując składniki sumy itd. Dzięki temu możemy
uzyskać prostszą postać formuły boolowskiej, wykazać równoważność (albo
nierównoważność) dwóch zdań i tak dalej. W następnym podrozdziale sami
przekonamy się, jak to się robi.
W końcu XIX wieku logika przeżyła jeszcze ważniejszy, jakościo-
wy przełom. Dzięki przede wszystkim pracom niemieckiego matematyka
i filozofa Gottloba Frege (1848–1925), a także innych matematyków tam-
tej doby, logika wzbogaciła się o zupełnie nowe możliwości. Powstały całe
nowe działy logiki i matematyki, dla których rachunek zdań i wywodzą-
ce się od Arystotelesa schematy poprawnego wnioskowania są narzędziem
równie podstawowym i wstępnym, jak dla wyższego wykształcenia umie-
jętność czytania i pisania.
Prace Gottloba Fregego zainspirowały m.in. Bertranda Russella
i Alfreda Whiteheada do przemyślenia od nowa najgłębszych podstaw mate-
matyki i postawienia tezy (wyrażonej w ich kilkutomowym dziele Principia
Mathematica, wydawanym w latach 1910–1913), że cała matematyka spro-
wadza się w gruncie rzeczy do tej nowej, w nowoczesny sposób rozumianej
logiki. Nietrudno wyobrazić sobie, jak ważne dla współczesnej matematyki,
logiki i filozofii dyskusje wywołała ta teza.
Jednak wszystkie te wydarzenia, jakkolwiek ważne dla środowiska
naukowego, zdawały się bardzo odległe od tego, co wówczas (a mówimy
już o początkowych dekadach XX wieku) kształtowało w praktyce losy
milionów ludzi na całym świecie. Pierwsza wojna światowa (1914–1918),
będąca sama w sobie tragicznym przeżyciem, przynosi zmiany granic pań-
stwowych i całego światowego politycznego układu sił. Po niej następuje
powojenne ożywienie, szalone lata dwudzieste – a potem Wielki Kryzys
w 1929 roku. Azjatyckie giganty: Japonia, Chiny, Korea, mają swoje pro-
blemy i konflikty. Bolszewicka Rosja przeżywa okres brutalnej kolektywi-
zacji, industrializacji i represji okresu stalinowskiego. W Italii pojawia się
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

278 12. Elementarz syntezy logicznej

ruch faszystowski, w Niemczech – nazizm. Nasilają się niepokojące sygna-


ły, zwiastujące nadciągającą grozę nowego światowego konfliktu, który rze-
czywiście wybuchnie wkrótce, w 1939 roku i zakończy się w roku 1945.
W tej sytuacji, szerszym warstwom społeczeństwa dyskusje w gro-
nie matematyków i filozofów wydają się zapewne odległe i abstrakcyjne.
Wiele nowego dzieje się wtedy również w fizyce teoretycznej. Wprawdzie
postać Alberta Einsteina (i może kilku innych fizyków) jest nieco bardziej
znana, jednak ich osiągnięcia i dyskusje też nie wychodzą szerzej poza dość
hermetyczny krąg środowisk naukowych.
Na tym tle druga połowa lat trzydziestych XX wieku jest w Stanach
Zjednoczonych okresem względnego spokoju i optymizmu. Pod rząda-
mi administracji prezydenta Franklina D. Roosevelta kraj powoli zaczyna
dźwigać się z pokryzysowej zapaści, elektryfikuje się i pokrywa siecią dróg
i autostrad, a ludzie korzystają pełnymi garściami z dwudziestowiecznych
wynalazków, które odmieniły oblicze zachodniej cywilizacji. Automobile,
aeroplany, telefony, radio, początki telewizji, nagrania płytowe, kinemato-
graf, jazz, musicale, gwiazdy filmowe... A w tle – dobrze zorganizowany,
dynamiczny przemysł, który jest gotów w lot podchwycić każdy wynalazek
czy naukowe odkrycie, które dałoby się przekształcić w ulepszenie, uspraw-
nienie czy wręcz nowy, masowy produkt.
Wtedy właśnie, w roku 1936, pewien młody, bo zaledwie dwudziesto-
letni amerykański student uzyskuje w University of Michigan od razu dwa
dyplomy ukończenia studiów (ze stopniem bakałarza), na kierunkach inży-
nierii elektrycznej oraz matematyki. Nazywa się Claude Shannon, urodził
się w roku 1916 i pochodzi z miasteczka Gaylord w stanie Michigan. Młody
absolwent podejmuje bez zwłoki studia magisterskie w renomowanym
Massachusetts Institute of Technology (MIT) w Bostonie i już w 1937 roku
przedstawia dyplomową pracę magisterską.
Praca dotyczy metod analizy i syntezy układów przekaźnikowych
i przełączających (ang. relay and switching circuits). Może się wydawać, że
jest to tematyka ściśle techniczna, specjalistyczna, interesująca co najwyżej
wąską grupę inżynierów elektryków. To prawda, ale Shannon potrafił w niej
dostrzec i w odkrywczy sposób sformułować wnioski, które zapewniły mu
miejsce w historii.
Warto zrozumieć choćby na bardzo uproszczonych przykładach, cze-
go dotyczyło odkrycie Shannona.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Układy przełączające 279

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.

Rys. 12.1. Proste przełączające układy stykowe

W bardziej złożonych układach przełączających biorą również udział prze-


kaźniki (ang. relays), poglądowo pokazane na rysunku 12.2. Włącznik we
zamyka wejściowy obwód prądu stałego, płynącego przez cewkę magne-
tyczną. Wytworzone w niej pole magnetyczne przyciąga jeden ze styków,
ten, na którym umieszczony jest ferromagnetyczny ciężarek. W zależności
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

280 12. Elementarz syntezy logicznej

od konstrukcji przekaźnika powoduje to zwarcie styków wyjściowych na


czas działania elektromagnesu (jeśli przekaźnik jest „normalnie otwarty”,
w skrócie NO, ang. normally open) albo ich rozwarcie, jeśli w stanie spo-
czynkowym jest on zwarty (NC, ang. normally closed).

Rys. 12.2. Przekaźniki: „normalnie otwarty” (NO) i „normalnie zamknięty” (NC)

Inny (niepokazany na rysunku) rodzaj przekaźnika pełni funkcję nie wy-


łącznika, lecz przełącznika: w stanie spoczynkowym zwarta jest jedna para
styków, a zamknięcie obwodu wejściowego powoduje przełączenie się na tę
drugą możliwość. Ponadto, w polu magnetycznym jednego elektromagnesu
może być umieszczonych kilka jednocześnie uruchamianych par lub trójek
styków różnego typu.
Zaletą przekaźnika jest to, że jego obwód wejściowy (uruchamiają-
cy cewkę elektromagnesu) jest całkowicie galwanicznie odseparowany od
obwodu wyjściowego, którego elementem są sterowane elektromagnesem
styki. Połączone są one jedynie poprzez oddziaływanie pola elektromagne-
tycznego. Dzięki tej właściwości, podając w obwodzie wejściowym prąd
stały pod bezpiecznym, niskim napięciem, możemy spowodować zwarcie
potężnych styków przekaźnika o specjalnej konstrukcji, które włączą w ob-
wodzie wyjściowym linię energetyczną o napięciu wielu tysięcy woltów,
zdolną do zasilania prądem przemiennym całego miasta.
Podobnie, przekręcając kluczyk w stacyjce naszego samochodu, urucha-
miamy przekaźnik, który podaje prąd z akumulatora do rozrusznika. Ponieważ
obwody wejściowy i wyjściowy są odseparowane – prąd o natężeniu stu kilku-
dziesięciu amperów, potrzebny do uruchomienia silnika, nie musi płynąć bez-
pośrednio przez stacyjkę i kluczyk.
Przekaźniki można też łączyć ze sobą w bardziej złożone układy
przełączające. Wyjściowe styki jednego przekaźnika mogą stanowić część
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Układy przełączające 281

obwodu wejściowego innego. Dzięki temu wynalezienie przekaźnika (już


w połowie XIX wieku) umożliwiło przesyłanie sygnałów telegraficznych na
praktycznie dowolne odległości. Przedtem, zasięg elektrycznego telegrafu
był ograniczony do 20–30 km, ponieważ opór elektryczny w pojedynczym
obwodzie rośnie wraz z długością linii. Po wynalezieniu przekaźników
można było takie wzajemnie odseparowane obwody łączyć w łańcuch, jak
na rysunku 12.3 2. Ale są też inne możliwości ich łączenia, a wykorzysta-
nie przy tym połączeń szeregowych i równoległych oraz obu typów prze-
kaźników, NO i NC, umożliwia tworzenie skomplikowanych funkcjonalnie
urządzeń.

Rys. 12.3. „Sztafeta” przekaźników

Gdzie znajdowały zastosowanie takie układy? Przede wszystkim (i najwcze-


śniej) w teletechnice: dziale inżynierii zajmującej się budową urządzeń tele-
graficznych i telefonicznych. O sztafecie przekaźników już wspomnieliśmy:
pomysł ten odegrał ważną rolę już u samych początków telegrafii. Jednak
szczególnie wiele układów stykowych napotkamy w automatycznych elek-
tromechanicznych centralach telefonicznych.
Podnosząc słuchawkę w konwencjonalnym, przewodowym telefonie,
powodujemy rozwarcie pewnego styku, co w najbliższej centrali powoduje
włączenie w obwód słuchawki generatora dźwiękowego sygnału gotowości
albo zajętości centrali. Wybieranie numeru pociąga za sobą uruchamianie
innych zespołów elektrycznych styków, przekierowujących abonenta do
następnej centrali, z niej – do dalszej i tak dalej, aż do aparatu odległego
rozmówcy, gdzie zostaje zwarty kolejny styk, włączający obwód dzwonka.

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

282 12. Elementarz syntezy logicznej

Podniesienie słuchawki przez adresata rozłącza obwód dzwonka, a zamyka


obwód mikrofonu i słuchawki itd. W czasach, o których mówimy (i jeszcze
znacznie później) wszystkie te funkcje były realizowane przez elektrome-
chaniczne układy przełączające. Nietrudno wyobrazić sobie, że w każdej
centrali telefonicznej były ich tysiące, a ich projektowanie i utrzymanie
w stanie sprawności nie było sprawą prostą.
Inną dziedziną, w której wykorzystywano przekaźnikowe układy
przełączające, była przemysłowa automatyka i systemy zabezpieczeń, na
przykład w kolejnictwie i ruchu drogowym, sygnalizacji alarmowej itd.
Wyobraźmy sobie przejazd kolejowy, wyposażony z obu stron drogi w ro-
gatki (szlabany) oraz sygnalizatory z czerwonym światłem, ostrzegające
przed wjeżdżaniem na torowisko. Chcielibyśmy, by czerwone światło zapa-
lało się automatycznie w momencie, gdy dróżnik zaczyna opuszczać którą-
kolwiek z zapór. Dalej, wyobraźmy sobie, że przy kolejowym torze, w od-
powiedniej odległości przed przejazdem, znajduje się semafor. Powinien on
być podniesiony (sygnalizując maszyniście, że droga wolna) tylko wtedy,
gdy odpowiedni czujnik jednocześnie sygnalizuje, że na następnym odcinku
toru nie ma innego pociągu. Jednocześnie, obie rogatki powinny być w tym
czasie zamknięte, a ponadto powinno się palić czerwone światło dla prze-
jazdu przez tory.
Taki system zabezpieczeń można zbudować, wykorzystując przekaź-
niki i inne układy stykowe. Jeden przekaźnik będzie w swoim obwodzie
wyjściowym włączał czerwone światła, drugi – silnik podnoszący ramię
semafora. Jedna para wejściowych styków będzie sygnalizować, że lewa
zapora jest nie do końca podniesiona, druga – że prawa, trzecia para – że
na następnym odcinku toru wciąż znajduje się inny pociąg itd. Trzeba je tyl-
ko w odpowiedni sposób połączyć. Trzeba także upewnić się, że ten układ
działa poprawnie, na przykład, że w tak zaprojektowanym układzie czerwo-
ne światła palą się zawsze wtedy, gdy semafor jest podniesiony itd.
Inżynierowie elektrycy rozwiązywali ten problem tak, jak to elektry-
cy: przez projektowanie konkretnych elektrycznych obwodów. Ich działanie
sprawdzali potem, wodząc palcem po schemacie, śledząc przebieg połączeń
po to, by stwierdzić, czy analizowany obwód jest w danej sytuacji zamknię-
ty, czy nie. W podobny sposób i my analizowaliśmy działanie przełącznika
hotelowego z rysunku 12.1b.
Claude Shannon zauważył, że jeśli zamiast zamknięte i otwarte, mó-
wić prawdziwe i fałszywe (albo 1 i 0), to połączenie szeregowe styków (jak
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Układy przełączające 283

na rysunku 12.1a) realizuje tę samą funkcję, co spójnik I w zdaniach logicz-


nych: obwód jest zamknięty, jeśli oba styki a i b są jednocześnie zamknięte.
Połączenie równoległe odpowiada z kolei spójnikowi LUB. Prąd w obwo-
dzie płynie, jeśli zamknięty jest choćby jeden ze styków: c lub d. Negację
(spójnik NIE) można zrealizować, wykorzystując przekaźnik NC (normally
closed), którego styki wyjściowe są w stanie spoczynkowym zwarte. Jeśli
jego wejściowy styk zewrzemy – styki wyjściowe rozewrą się (i odwrotnie),
dokładnie tak, jak przewiduje zasada spójnika NIE.
Jeśli tak, to – zauważa Shannon – przy projektowaniu układu przełą-
czającego można wykorzystać reguły algebry Boole’a. Obwody wejściowe
i wyjściowe przekaźników trzeba projektować tak, jak zdania z rachunku
zdań albo jak równoważne im boolowskie wyrażenia, zawierające binarne
zmienne oraz operatory logiczne.
Z rachunku zdań wiadomo, że każde zdanie złożone da się wyrazić
za pomocą operatorów LUB, I, NIE, więc każdy układ przełączający moż-
na skonstruować w systematyczny sposób, wykorzystując połączenia sze-
regowe i równoległe oraz przekaźniki NO i NC. Co więcej, jeszcze przed
narysowaniem elektrycznego schematu połączeń można te wyrażenia bo-
olowskie symbolicznie przekształcać, upraszczać itd., a sprawdzenie, czy
np. pewne obwody mogą być jednocześnie zamknięte – sprowadza się do
stosunkowo prostej symbolicznej operacji, zajmującej nie więcej niż kilka
wierszy przekształceń boolowskiej formuły na papierze.
W ten sposób algebra, wymyślona ponad osiemdziesiąt lat wcześniej
przez angielskiego matematyka – stała się narzędziem pracy dwudziesto-
wiecznych inżynierów.
Cóż, zapewne wielu wzruszy ramionami. Oczywiście, to bardzo
przyjemne spostrzeżenie. Oczywiście, inżynierowie elektrycy powinni być
wdzięczni Shannonowi za to, że ułatwił im pracę. Kogóż to jednak napraw-
dę obchodzi, poza tą wąską grupą zawodową?
Okazało się, że powinno obchodzić wszystkich, i to bardzo. Shannon
zauważył bowiem rzecz znacznie głębszą i ważniejszą, a mianowicie to, że
ścisły związek między układami przełączającymi a algebrą Boole’a i logiką
działa również w przeciwnym kierunku.
Jeśli bowiem można wykorzystać logikę i algebrę Boole’a do kon-
struowania technicznych dwustanowych urządzeń, to można również wyko-
rzystać techniczne, dwustanowe urządzenia do automatycznego rozwiązy-
wania problemów logiki. Ale skoro jednocześnie – jak właśnie dyskutują
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

284 12. Elementarz syntezy logicznej

matematycy – cała matematyka do logiki się w gruncie rzeczy sprowadza,


to można spodziewać się, że nauczywszy się konstruować odpowiednio zło-
żone systemy z dwustanowych elementów, będziemy w stanie rozwiązywać
jeśli nie dowolne problemy matematyczne, to przynajmniej bardzo szeroką
ich klasę.
Doniosłość tego rozumowania natychmiast doceniono przez amery-
kańskie środowiska naukowe i techniczne. W ekspresowym jak na tamte
czasy tempie, bo już w roku 1938, rezultaty Shannona opublikowano w na-
ukowym czasopiśmie „Transactions of the American Institute of Electrical
Engineers” i stały się dość szeroko znane, a samą pracę dyplomową Shannona
uznano później za najważniejszą pracę magisterską w tamtym stuleciu, jeśli
nie w historii w ogóle.
Nie trzeba też było długo czekać na potwierdzenie prawdziwości wi-
zji młodego dyplomanta. W ciągu kilku najbliższych lat, w kilku ośrodkach
naukowych w USA powstawały (głównie na zamówienie amerykańskiej ar-
mii: pamiętajmy, że był to już czas II wojny światowej) prototypy wielkich
maszyn liczących. Pierwsze z nich, już wcześniej projektowane, pracowały
początkowo jeszcze w systemie dziesiętnym, jednak późniejsze, ulepszone
ich wersje były już oparte na zastosowaniu układów dwójkowych. Podobnie
początkowe elektromechaniczne przekaźniki zastępowane były znacznie
szybszymi dwustanowymi układami elektronicznymi, zbudowanymi z wy-
korzystaniem lamp próżniowych. Matematycy i inżynierowie zaczęli wspól-
nie opracowywać zasady konstruowania urządzeń cyfrowych, ale także ich
programowania, to znaczy – wyrażania obliczeń w kategoriach sekwencji
działań na obiektach logicznych.
Decydującą rolę odegrały przy tym tajne projekty wojskowe. W cza-
sie II wojny światowej (do której Stany Zjednoczone formalnie przystąpiły
w grudniu 1941 roku, po japońskim ataku na Pearl Harbor) były one zwią-
zane z obliczeniowymi potrzebami innych wojskowych projektów, głównie
z dziedziny balistyki, aerodynamiki i atomistyki. Podobnie było w Wielkiej
Brytanii: mimo niemieckich bombardowań, realnego zagrożenia hitlerowską
inwazją, przy reglamentacji żywności – rząd brytyjski organizował i finan-
sował tajne naukowe badania, m.in. nad systemami radarowymi, elektro-
niką i kryptografią. Wtedy to powstały (m.in. z inicjatywy Alana Turinga)
pierwsze brytyjskie specjalizowane komputery: kryptologiczne „bomby”,
a później dwójkowe maszyny Colossus. Wszystkie te projekty i związany
z nimi wysiłek intelektualny najwybitniejszych naukowych umysłów obu
krajów przyczyniły się do uzyskania przez zachodnich aliantów technicznej
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Układy przełączające 285

przewagi w kryptografii, lotnictwie, marynarce, artylerii i obronie przeciw-


lotniczej, a także – do zbudowania bomby atomowej, a później – termoją-
drowej.
Nie wszyscy wiedzą, że inne uczestniczące w II wojnie światowej
kraje były o krok od podobnych odkryć, ale nie potrafiły ich zauważyć,
docenić i wykorzystać. W prawie tym samym czasie, co Claude Shannon,
w 1938 roku, podobną pracę dyplomową o algebraicznej teorii układów
przekaźnikowych obronił na Uniwersytecie Moskiewskim Rosjanin, Wiktor
Szestakow. Praca podobno była gotowa nawet jeszcze znacznie wcześniej.
Publikacja na ten temat ukazała się jednak dopiero w roku 1941 i w ówcze-
snym Związku Radzieckim przeszła bez echa.
W hitlerowskich Niemczech inżynier Konrad Zuse nawet samodziel-
nie zbudował (i to już w 1938 roku) pierwszą dwójkową maszynę liczą-
cą Z1. Była to konstrukcja całkowicie mechaniczna (!) i właściwie nigdy
sprawnie nie działała. Ale już w czasie wojny, w roku 1940, nic oczywiście
nie wiedząc o pracy Shannona, Zuse zbudował ulepszoną wersję maszyny,
Z2, a w następnych latach Z3 i Z4, wszystkie z wykorzystaniem przekaźni-
ków i innych elementów ze zdemontowanych central telefonicznych.
Władze ówczesnego hitlerowskiego reżimu nie okazały jednak (na
nasze szczęście) zainteresowania projektami Zusego, z wyjątkiem krótko-
trwałego wsparcia, którego udzielił mu niemiecki instytut badawczy lotnic-
twa. Wniosek, by przy rządowym wsparciu zastąpić w maszynach Zusego
elementy elektromechaniczne elektronicznymi odrzucono wręcz jako dzi-
waczny pomysł bez żadnego strategicznego znaczenia. Po wojnie Konrad
Zuse założył w Szwajcarii firmę, która aż do 1967 roku budowała maszy-
ny liczące dla europejskich firm, uniwersytetów i ośrodków naukowych. Ta
działalność nigdy jednak nie osiągnęła takiego rozmachu, takiej skali i zna-
czenia, jak powojenne projekty amerykańskie, rządowe i przemysłowe, ma-
jące początki jeszcze w czasach II wojny światowej.
W końcowym okresie wojny, w 1945 roku, wybitny amerykański
matematyk John von Neumann opracował tajny raport, podsumowujący
doświadczenia z projektowania i użytkowania dziesiętnej maszyny liczą-
cej ENIAC, przy której był konsultantem. Przedstawił on w tym raporcie
nową, spójną koncepcję maszyn liczących z pamiętanym programem (ang.
stored program computer). Jeszcze przed zakończeniem prac nad budową
ENIAC-a, rozpoczęto w University of Pennsylvania projektowanie maszy-
ny EDVAC, już binarnej i opartej na raporcie von Neumanna. Niedługo po-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

286 12. Elementarz syntezy logicznej

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.

Niezwykła kariera naukowa Claude'a Shannona


Dla samego Shannona praca dyplomowa stanowiła zaledwie początek na-
ukowej kariery. Miał niesłychanie otwarty, bystry i twórczy umysł, który
pozwalał mu przemieszczać się z łatwością pomiędzy różnymi dziedzina-
mi i w każdej z nich pozostawić pionierski dorobek. Jego doktorat, obro-
niony w MIT już w roku 1940, dotyczył algebraicznych modeli w genety-
ce. Następny rok spędził jako stypendysta w Institute for Advanced Study
w Princeton, w którym pracowały wówczas takie – między innymi – zna-
komitości, jak Albert Einstein, John von Neumann czy Kurt Gödel, którzy
emigrowali tu wcześniej z zagrożonej nazizmem Europy.
W roku 1941 Shannon podjął pracę w sławnym laboratorium badaw-
czym firmy Bell, jednej z instytucji przodujących w badaniach z dziedziny
elektroniki, telekomunikacji i systemów sterowania. Tu zajmował się (rów-
nież w ramach projektów wojskowych) problemami kodowania informacji,
cyfrowym przetwarzaniem sygnałów, kryptografią, kodami korekcyjnymi,
filtrowaniem informacji i jej przewidywaniem (w tym do celów automa-
tycznego kierowania ogniem artylerii przeciwlotniczej). W roku 1943 przez
kilka miesięcy osobiście współpracował z Alanem Turingiem, który wtedy
przebywał w USA w ramach współpracy brytyjskich i amerykańskich kryp-

3 Znacznie więcej o koncepcji von Neumanna powiemy w następnych rozdzia-


łach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Niezwykła kariera naukowa Claude'a Shannona 287

tologów. Opracowywali wspólnie system cyfrowego kodowania sygnałów


mowy4, ale dyskutowali także nad uniwersalną maszyną Turinga i zagad-
nieniami kryptografii.
Później, po odtajnieniu i opublikowaniu wewnętrznych raportów
i opracowań Shannona z tego okresu okazało się, że zawierają one teore-
tyczne rezultaty zupełnie podstawowe dla wszystkich tych dziedzin, którymi
się zajmował.

Fot. 12.4. Claude Shannon z modelem sztucznej myszy w labiryncie. Zdjęcie


reprodukowane za zgodą Alcatel-Lucent, USA, Inc.

4 Chodziło o uruchomienie łącza do bezpośrednich (lecz szyfrowanych) rozmów


telefonicznych między premierem Wielkiej Brytanii Winstonem Churchillem
a prezydentem USA Franklinem D. Rooseveltem. Projekt ten nie został wówczas
zrealizowany.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

288 12. Elementarz syntezy logicznej

Claude Shannon pracował w Bell Laboratories do 1956 roku, potem


objął stanowisko profesora w MIT i kierował tam laboratorium badawczym
elektroniki aż do przejścia na emeryturę w roku 1978. Ale jeszcze wcześ-
niej, w roku 1948, Shannon wspólnie z Warrenem Weaverem opublikowa-
li fundamentalną do dziś książkę o matematycznej teorii informacji (ang.
mathematical theory of communication). Trudno przecenić znaczenie tego
dzieła. Odegrało ono podstawową rolę w światowej skali, we wszelkich
późniejszych pracach nad przetwarzaniem i przesyłaniem cyfrowej informa-
cji. Nazwisko Shannona pojawiło się także i w naszej książce, gdy – w roz-
dziale 7 – mówiliśmy o przetwarzaniu sygnałów.
W latach 1949 i 1951 Shannon opublikował równie pionierskie artykuły
o matematycznej teorii systemów kryptograficznych oraz o teorii informacji
w odniesieniu do przetwarzania języka naturalnego. W roku 1950 jako pierw-
szy opisał zasady działania programu, który byłby zdolny do gry w szachy.
Zbudował także elektromechaniczny model myszy, która samodzielnie znajdu-
je drogę w labiryncie. To tylko niektóre z jego osiągnięć. Ta różnorodność jego
zainteresowań, pionierski charakter i waga rezultatów, które osiągnął w każdej
dziedzinie, jaką się tylko zajął – pozwalają postawić go w rzędzie najwybit-
niejszych uczonych XX wieku. Odebrał zresztą wiele oznak międzynarodo-
wego uznania, nagród i doktoratów honorowych, ma w USA pięć pomników
i tablic pamiątkowych, a kilka ważnych laboratoriów nosi jego nazwisko.
Jednocześnie ci, którzy go znali, podkreślali, że był niezwykle skon-
centrowany w czasie pracy, ale poza nią – umiał zaskoczyć nieoczekiwanym
zachowaniem i poczuciem humoru. Potrafił na przykład jeździć na jednokoło-
wym rowerze własnej konstrukcji (i to o niecentrycznie umieszczonej osi), jed-
nocześnie żonglując kilkoma piłeczkami. Co gorsza, uprawiał tę sztukę o róż-
nych porach dnia także na korytarzach laboratorium, czym doprowadzał nocne
sprzątaczki do stanu przedzawałowego, a dziennych gości wprawiał w stan
głębokiego osłupienia. Był doskonałym szachistą, grał nieźle na klarnecie,
konstruował zabawne i absurdalne gadżety. Bywało, że z jednym z kolegów
z MIT, wraz z żonami, spędzali weekendy w kasynach Las Vegas, gdzie przy
stole do blackjacka testowali w praktyce pewne rezultaty z matematycznej teo-
rii gier, wypracowane wspólnie z innym kolegą z Bell Labs w oparciu na teorii
informacji Shannona. Zarobili na tym podobno całkiem niezłe sumy.
Claude Shannon zmarł nie tak dawno, w 2001 roku, w wieku 84 lat.
Proszę pomyśleć, cóż to za niezwykły życiorys! Człowiek, który jako pierw-
szy zauważył sens stosowania systemu dwójkowego w technice i matema-
tyce, a dziesięć lat później położył teoretyczny fundament pod całą współ-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algebra Boole’a i pomysł na „automatyzację” obliczeń logicznych 289

czesną telekomunikację i cyfrowe przetwarzanie sygnałów – zdążył jeszcze


zobaczyć układy scalone, superkomputery i łącza satelitarne. Pod koniec ży-
cia mógł korzystać z Internetu, laptopa, cyfrowej telewizji...
Niestety, mógł, ale nie korzystał. Choroba Alzheimera przedwcześnie
zmąciła żywy, twórczy umysł wielkiego uczonego. To smutne, ale ostatnie
lata życia Claude Shannon spędził w ośrodku opieki, praktycznie bez kon-
taktu z rzeczywistością.
Dobrze, że choć przedtem zdążył nas obdarować tak wieloma nauko-
wymi osiągnięciami.

Algebra Boole’a i pomysł na „automatyzację”


obliczeń logicznych
Skoro już wiemy, jaką rolę odegrała (i nadal odgrywa) algebra Boole’a
w technice cyfrowej – czas na praktyczne, choćby pobieżne, zapoznanie się
z możliwościami, jakie ona oferuje.
Od strony formalnej, każda algebra (bo przecież algebr jest wiele) to
pewien zbiór, w którym są wyróżnione pewne stałe i nad którym zdefinio-
wane są pewne funkcje. Funkcje te – jak to funkcje – przypisują jednym ele-
mentom zbioru lub ich parom – inne elementy tegoż zbioru. My zaś często
uważamy te funkcje za operacje, dopuszczalne w ramach danej algebry.
Nie będziemy wdawali się tutaj w formalne definiowanie algebry
Boole’a. Choć jest to bardzo spójna i matematycznie piękna struktura, my
potraktujemy ją tu bardziej użytkowo, patrząc na nią po prostu jako na zbiór
reguł, opisujących zasady wykonywania operacji na zmiennych, z których
każda może przyjmować tylko dwie wartości: 0 albo 1. Zmienne takie nazy-
wamy oczywiście boolowskimi lub logicznymi.
W algebrze Boole’a występują też dwie stałe: 1 oraz 0. Odpowiadają
one wartościom prawdziwe i fałszywe z rachunku zdań. Tam były one zapi-
sywane często jako T i F (od ang. True, False) lub ⊤ i ⊥. My będziemy je
konsekwentnie oznaczać właśnie jako 1 i 0.
Na zmiennych logicznych zdefiniowane są trzy operacje: suma bo-
olowska (+), iloczyn boolowski (·) oraz dopełnienie (–). Jak się łatwo do-
myślić, operacja sumy boolowskiej odpowiada – w rachunku zdań – alter-
natywie, czyli sumie logicznej (ze spójnikiem LUB). Podobnie, iloczyn bo-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

290 12. Elementarz syntezy logicznej

olowski odpowiada koniunkcji (ze spójnikiem I), a boolowskie dopełnienie


– logicznej negacji (NIE).
Dla usunięcia ewentualnych niejasności wypada ustalić zasady notacji,
zwłaszcza że w tradycyjnej logice wykorzystuje się często też inne symbole
do skrótowego zapisywania spójników logicznych. Powiedzmy więc, że:
– negację (dopełnienie) zmiennej np. p będziemy zapisywali, używając kre-
ski nad zmienną (np. p ). W logice często zapisuje się ją także jako ¬p, ~p,
NOT(p), !p;
– dla sumy logicznej (alternatywy) zmiennych np. p, q będziemy używali
oznaczenia p + q (choć w literaturze można również spotkać p ∨ q, p OR q,
OR(p, q) itd.);
– iloczyn logiczny (koniunkcję) zmiennych np. p, q zapiszemy jako p ⋅ q
(inne oznaczenia to p ∧ q, p&q, pANDq, AND(p, q)). W praktyce, po-
dobnie jak w przypadku arytmetycznego mnożenia, możemy sobie po-
zwolić na pominięcie tej kropki, jeśli nie powoduje to nieporozumienia;
– dodatkowo, dopuścimy użycie nawiasów, które mogą się też zagnieżdżać
(„nawiasy w nawiasach”), przy zachowaniu konwencjonalnego ich zna-
czenia (że narzucają kolejność wykonywania operacji) i znanych reguł
składniowych (że każdy nawias otwierający musi mieć „do pary” nawias
zamykający).
Nie trzeba chyba dodawać, że nie powinniśmy mylić boolowskich ope-
racji sumy i iloczynu z dodawaniem i mnożeniem arytmetycznym (np. liczb
całkowitych czy rzeczywistych), choć zastosowane symbole (zwykły plus
oraz kropka) są u nas identyczne.
Tabelki prawdy dla boolowskiej sumy, iloczynu i dopełnienia (nega-
cji) są pokazane w tabeli 12.3. Jak łatwo się można przekonać, są one iden-
tyczne z tabelkami dla spójników LUB, I oraz NIE (tabele 12.1 oraz 12.2),
z dokładnością do zero-jedynkowej notacji.

Tab. 12.3. Tabele boolowskiej sumy, iloczynu i dopełnienia


x y x+y x y x⋅y
0 0 0 0 0 0 x x
0 1 1 0 1 0 0 1
1 0 1 1 0 0 1 0
1 1 1 1 1 1
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algebra Boole’a i pomysł na „automatyzację” obliczeń logicznych 291

W tabeli 12.4 zestawiono z kolei kilkanaście użytecznych praw, definiu-


jących reguły wykonywania i składania operacji w algebrze Boole’a. Dla
wygody powoływania się na nie dalej w tekście, ponumerowano je, od 1
do 17. W lewej kolumnie umieszczono zależności, definiujące właściwości
głównie boolowskiej sumy, w prawej – głównie iloczynu, ostatnią zależność
(negacja negacji) trudno zaliczyć do którejś z tych dwóch grup.
Każde z tych praw jest tożsamością (tautologią), czyli złożonym
zdaniem, mówiącym, że lewa strona tej równości ma dokładnie taką samą
wartość logiczną, jak prawa, i to niezależnie od wartości logicznej występu-
jących w nich zmiennych x, y, z. Tak więc na przykład tożsamość 1 mówi,
że x + 0 = x niezależnie od wartości zmiennej x. Jeśli x = 0, to 0 + 0 = 0.
Jeśli natomiast x = 1, to 1 + 0 = 1. W ten sposób tożsamości 1 i 3 właści-
wie zastępują tabelkę prawdy dla sumy boolowskiej. Podobnie, zależności 2
i 4 definiują boolowski iloczyn równie skutecznie, jak jego tabelka prawdy.
Niemniej, korzystanie z tabelek prawdy jest wygodne i warto je było wyżej
przytoczyć.

Tab. 12.4. Podstawowe tożsamości algebry Boole’a


1. x+0=x 2. x⋅0=0
3. x+1=1 4. x⋅1=x
5. x + x =1 6. x ⋅ x =0
7. x+x=x 8. x⋅x=x
9. x+y=y+x 10. x⋅y=y⋅x
11. x + (y + z) = (x + y) + z 12. x ⋅ (y ⋅ z) = (x ⋅ y) ⋅ z
13. x + y ⋅ z = (x + y) ⋅ (x + z) 14. x ⋅ (y + z) = x ⋅ y + x ⋅ z
_______ ______
15. x+ y = x ⋅ y 16. x⋅y =x+y
__
17. x =x

Niektóre z podanych tożsamości (o numerach 1, 2, 4, 9, 10, 11, 12 i 14) bar-


dzo przypominają podobne prawa łączności i przemienności operacji, które
znamy ze szkolnej arytmetyki czy algebry liczb rzeczywistych, choć tutaj
dotyczą operacji na zmiennych boolowskich. Ale pozostałe są już specy-
ficznie boolowskie. Na uwagę zasługują w szczególności ważne reguły 15
i 16, nazywane prawami de Morgana, od nazwiska dziewiętnastowieczne-
go brytyjskiego matematyka, który je wprowadził do logiki formalnej, choć
są powody, by przypuszczać, że były one w zasadzie znane już znacznie
wcześniejszym filozofom.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

292 12. Elementarz syntezy logicznej

Przypuśćmy teraz, że mamy boolowskie równanie, na przykład takie:

w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b ).

Występują w nim trzy niezależne zmienne logiczne: a, b i c, oraz zależna od


nich („wynikowa”) zmienna w. Skąd taka dziwaczna zależność? Kto cier-
pliwy, przekona się wkrótce, że ma ona bardzo praktyczny sens, jednak na
razie potraktujmy ją jako po prostu ćwiczenie, mające nas oswoić z prawa-
mi algebry Boole’a.
Pierwsze pytanie brzmi: dla jakich kombinacji wartości zmiennych
a, b i c zmienna wynikowa w przyjmuje wartość 0, a dla jakich 1? Żeby
to sprawdzić, powinniśmy rozważyć wszystkich osiem możliwych układów
wartości trzech zmiennych niezależnych. Tak więc na przykład, gdy a = 0,
b = 0, c = 0, to zmienna w ma wartość:

w = 0 ⋅ (0 ⋅ 0 + 0 ⋅ 0) + 0 ⋅ (0 ⋅ 0 + 0 ⋅ 0 ).

Ale 0 = 1, więc możemy napisać:

w = 1 ⋅ (0 ⋅1 + 1 ⋅ 0) + 0 ⋅ (0 ⋅ 0 + 1 ⋅1).

I dalej, korzystając z tablic prawdy iloczynu i sumy:

w = 1 ⋅ (0 + 0) + 0 ⋅ (0 + 1),
w = 1 ⋅ 0 + 0 ⋅1,
w = 0 + 0,
w = 0.

Podobne rozumowanie powinniśmy powtórzyć teraz dla następnych sied-


miu kombinacji wartości zmiennych a, b i c: odpowiednio (0, 0, 1), (0, 1, 0),
(0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1). To dość męczące, choć ruty-
nowe zadanie. Ale co gorsza, gdyby zmiennych niezależnych było nie trzy,
lecz cztery – to mielibyśmy do przeliczenia szesnaście przypadków, gdyby
pięć – to trzydzieści dwa, gdyby sześć – sześćdziesiąt cztery i tak dalej.
Trzeba (jak postulował już Shannon) porządnie pomyśleć nad sposobem
zautomatyzowania czy zmechanizowania tej procedury.
Do tego pomysłu wrócimy za chwilę, a na razie odpowiedzmy so-
bie na drugie pytanie: czy można tę samą boolowską zależność przedsta-
wić w postaci innej, równoważnej formuły? Oczywiście, tak. Korzystając
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algebra Boole’a i pomysł na „automatyzację” obliczeń logicznych 293

z tożsamości przedstawionych w tabeli 12.4, możemy przekształcać formułę


boolowską na właściwie nieograniczoną liczbę sposobów.
Dla przykładu, powtórzmy naszą analizowaną zależność:

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) .

Jednak na mocy drugiego prawa de Morgana (tożsamość 16) oraz


prawa podwójnej negacji (17):

( 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 .

Ale a ⋅ a = 0 oraz b ⋅ b = 0 (zgodnie z tożsamością 6), podczas gdy b ⋅ a = a ⋅ b


(zgodnie z 10), więc

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.

Okazuje się, że otrzymaliśmy w ten sposób zawartość drugiej pary nawia-


sów. Ponieważ p było zawartością pierwszego z nawiasów, a q – negacją
p, wykazaliśmy w ten sposób, że zawartość drugiego nawiasu jest negacją
pierwszego. Tak więc pierwotną zależność:

w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b )

możemy równie dobrze zapisać w równoważnej postaci:

w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

294 12. Elementarz syntezy logicznej

albo w postaci układu dwóch boolowskich równań:

p = a ⋅ b + a ⋅ b,
w = c ⋅ p + c ⋅ p,

albo jeszcze inaczej:


q = a ⋅b + a ⋅b ,
w = c ⋅ q + c ⋅ q.

Gdybyśmy w obliczeniach wartości w dla różnych kombinacji a, b, c sko-


rzystali z tak przekształconych zależności – to obliczenia byłyby nieco
prostsze. Jednak byłoby jeszcze lepiej, gdyby wykonywał je zaprojektowa-
ny do tego celu układ.
Zasadę budowy takiego układu można sobie wyobrazić dość łatwo.
Weźmy dla przykładu pierwszą parę boolowskich formuł, a mianowicie:

p = a ⋅ b + a ⋅ b,
w = c ⋅ p + c ⋅ p.

Sposób obliczania na ich podstawie wartości w możemy przedstawić gra-


ficznie, w postaci schematu jak na rysunku 12.5. Sieć na nim pokazana
obrazuje po prostu kolejność wykonywania operacji logicznych prowadzą-
cych od wartości zmiennych wejściowych a, b, c aż do końcowego wyniku,
czyli wartości w. Narysowaliśmy te operacje w postaci odpowiednio ozna-
czonych bloczków i połączyliśmy liniami czy strzałkami, pokazującymi ko-
lejność działań 5.
Na takim schemacie widać dobrze całą strukturę obliczenia. Wynik
w jest sumą dwóch iloczynów. Jeden z nich jest iloczynem p oraz c , drugi c ⋅ p .
Sama zmienna p jest też sumą dwóch iloczynów: a ⋅ b oraz a ⋅ b .
Idea jest chyba wyraźnie widoczna. Gdybyśmy tylko mieli podsta-
wowe układy, które potrafiłyby wykonywać elementarne operacje sumy,
iloczynu i negacji, to moglibyśmy je fizycznie połączyć w sieć, odpowiada-

5 Praktycznie nieuchronne jest, że te linie gdzieś się krzyżują. Dlatego miejsca,


w których mają one być celowo połączone – są wyraźnie oznaczone czarnymi
kropkami. Jeżeli na rysunku linie przecinają się bez takiej kropki – to znaczy, że
mijają się, nie wpływając na siebie wzajemnie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algebra Boole’a i pomysł na „automatyzację” obliczeń logicznych 295

Rys. 12.5. Sieć operacji, prowadzących do obliczenia wartości formuły w

jącą strukturze danego boolowskiego obliczenia. Kiedy taka sieć logiczna


jest już gotowa – wystarczy, że podamy na jej wejścia (a, b i c) interesującą
nas kombinację zer i jedynek – i możemy natychmiast na jej wyjściu (w)
odczytać odpowiadającą tej kombinacji wartość całej formuły.
Oczywiście, takie podstawowe układy realizujące elementarne ope-
racje logiczne są już od dawna znane. Noszą one nazwę bramek logicznych
(ang. logic gates). Szeregowe i równoległe połączenia układów przekaźni-
kowych też pełniły taką funkcję, ale odłóżmy je do historii: już od czasów II
wojny światowej bramki logiczne i całe ich sieci są budowane jako cyfrowe
układy elektroniczne.
Nie będziemy tu wnikali w ich wnętrze. Dokładniejsze wyjaśnienie
zasad ich działania, a także ich niezwykłej ewolucji od układów lampowych
do układów scalonych zawierających wewnątrz miliony bramek – wymaga-
łoby szerszego wprowadzenia do elektroniki, zapewne podobnego, jak cała
niniejsza książka.
Oglądane natomiast z zewnątrz bramki logiczne są elektronicznymi
układami o jednym, dwóch lub kilku wejściach oraz jednym wyjściu. Na
wejściach i na wyjściu bramki pojawia się napięcie elektryczne tylko dwoja-
kiego rodzaju: napięcie niskie L (od ang. low) oraz wysokie H (od ang. high).
To, jaką konkretną, np. mierzoną w woltach, wartość mają owe napięcia L
i H – zależy od technologii wykonania układu (np. inną w dawnych ukła-
dach lampowych, inną w półprzewodnikowych układach w technice TTL,
inną w technologii MOSFET, CMOS itp., cokolwiek te terminy znaczą).
Jak się można spodziewać, zewnętrzne zachowanie bramki jest opisy-
wane tabelką pokazującą, jakie kombinacje napięć L i H na wejściach powo-
dują pojawienie się na wyjściu napięcia L, a jakie – napięcia H. Elektronik,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

296 12. Elementarz syntezy logicznej

który projektuje wnętrze bramki, nie musi więc rozumować w kategoriach


logiki czy algebry Boole’a. Może się za to skoncentrować na problemach
elektronicznych, fizycznych i technologicznych: na wewnętrznym schemacie
układu, jego elementach składowych i ich rozmieszczeniu, na niskich i wy-
sokich napięciach i prądach elektrycznych, na tym, jaką moc będzie pobierał
dany układ, jak szybko zmiana układu napięć na wejściu układu przenie-
sie się na wyjście itd. Natomiast projektant sieci logicznej, otrzymawszy od
elektroników już gotowy układ, musi zdecydować, jak będzie interpretował
za pomocą zer i jedynek jego wejściowe i wyjściowe napięcia L oraz H.
Możliwości są oczywiście dwie: albo przyjąć, że napięcie niskie (L)
to logiczne 0, a wysokie (H) – to logiczne 1, albo odwrotnie, że L – to 1,
a H – to 0. Wpływa to na interpretację działania całego układu. Popatrzmy
na tabelki trzech układów, pokazane w tabeli 12.5.

Tab. 12.5. Tabele charakteryzujące działanie trzech układów elektronicznych


Układ 1 Układ 2
we1 we2 wy we1 we2 wy Układ 3
L L L L L L we wy
L H H L H L L H
H L H H L L H L
H H H H H H

Jeśli przyjmiemy, że napięcie niskie (L) odpowiada logicznemu zeru, a wyso-


kie (H) – logicznej jedynce, to układ 1 jest – proszę sprawdzić – bramką sumy
logicznej (o wejściach we1 oraz we2), układ 2 – iloczynu logicznego, a układ
3 – negacji. Jeśli jednak przyjąć konwencję przeciwną (że L to 1, a H to 0) – to
układ 3 pozostanie negacją, ale układ 1 stanie się bramką iloczynu logicznego,
a układ 2 – sumy logicznej. Częściej stosowana jest pierwsza z tych konwencji:
L to 0, H to 1. Ją też będziemy domyślnie przyjmowali w dalszych opisach.
Opracowano wiele rodzajów bramek logicznych. Kilka podstawo-
wych z nich zamieszczono na rysunku 12.6 wraz z graficznymi symbola-
mi, pod jakimi najczęściej występują w schematach sieci logicznych6. Dla

6 Są też stosowane inne oznaczenia, np. polegające na tym, że bramki są symboli-


zowane przez prostokątne bloczki z odpowiednim napisem w środku. Konwencja
pokazana na rysunku 12.6 ma tę zaletę, że funkcjonalnie różne bramki różnią się
też wyraźnie kształtem.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Algebra Boole’a i pomysł na „automatyzację” obliczeń logicznych 297

Rys. 12.6. Kilka podstawowych bramek logicznych

uproszczenia rysunku bramki tam pokazane mają dwa wejścia (oczywiście


negacja jest jednowejściowa), choć wytwarzane są również bramki wielo-
wejściowe.
Pierwsze trzy z pokazanych bramek to znane nam już: bramka nega-
cji (ang. NOT gate, zwana też w elektronice inwerterem), sumy (ang. OR
gate) i iloczynu logicznego (ang. AND gate). Dalsze dwie – to bramki NOR
i NAND, w których wyjście jest – w stosunku do odpowiednich podstawo-
wych bramek sumy i iloczynu – dodatkowo zanegowane, co jest symbolizo-
wane dorysowanym do wyjścia kółeczkiem.
Ostatnia z bramek pokazanych na rysunku 12.6 – to bramka XOR
(czytane jako eks-or, od ang. eXclusive OR). Realizuje ona często wystę-
pującą funkcję logiczną, mianowicie alternatywę wykluczającą (inaczej:
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

298 12. Elementarz syntezy logicznej

różnica symetryczna lub suma modulo dwa). Mieliśmy z nią niedawno do


czynienia. Występuje ona dwukrotnie w analizowanej wyżej formule:

w = c ⋅ ( a ⋅ b + a ⋅ b) + c ⋅ ( a ⋅ b + a ⋅ b) .

Czytelnik zapewne bez trudności sam narysuje prościutką sieć logiczną,


składającą się z jedynie dwóch bramek XOR, która będzie odpowiadała
tej zależności w od a, b, c. Przykład ten pokazuje, jak pojedynczą bramką
XOR można zastąpić całą podsieć, składającą się z sumy, dwóch iloczynów
i dwóch negacji.
Podobnie, występujący dwukrotnie w sieci z rysunku 12.5 iloczyn
dwóch negacji (np. a ⋅ b ) można zrealizować przy użyciu bramki NOR, gdyż
na mocy prawa de Morgana a ⋅ b = a + b itd. Ciekawe, że mając na przykład
do dyspozycji tylko bramki NOR, albo tylko NAND, albo tylko XOR – mo-
żemy przy ich użyciu zrealizować wszystkie funkcje boolowskie. Można to
wykazać, wykorzystując prawa de Morgana, stałe logiczne 0 i 1 oraz zasadę
podwójnej negacji.
Wiedząc, jakimi bramkami logicznymi dysponujemy, możemy więc
boolowską formułę przekształcać i upraszczać na wiele sposobów. Każdej
z otrzymanych w ten sposób postaci formuły odpowiada sieć o innej struk-
turze, analogiczna do tej z rysunku 12.5.

Od tabelki prawdy do sieci logicznej


Przedstawiona w poprzednim podrozdziale idea budowy sieci z bramek
logicznych opierała się na założeniu, że formułę boolowską mamy z góry
zadaną. Skąd się jednak owa formuła wzięła? Zazwyczaj potrzeba zbudowa-
nia sieci logicznej zaczyna się przecież od czegoś innego: od merytorycznej
analizy zasad wykonywania pewnej operacji na dwójkowych zmiennych.
Odwołajmy się znów do prostego przykładu. Powiedzmy, że mamy
trzy logiczne zmienne: a, b i c. Chcielibyśmy zbudować coś w rodzaju urzą-
dzenia do głosowania: układ, który by pokazywał, jaką wartość logiczną
(0 albo 1) ma w każdej chwili większość spośród tych trzech zmiennych.
Niech a, b i c będą wejściami tego układu, a d – jego wyjściem. Oczywiście,
możliwych kombinacji wartości wejść jest osiem.
Po krótkim namyśle wypełnimy tabelkę (tabela 12.6) pokazującą, jak
wyjście d ma zależeć od wartości wejść.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Od tabelki prawdy do sieci logicznej 299

Tabelka przyporządkowuje różnym trójkom wartości zmiennych a, b, c


jednoznaczne wartości wyjściowej zmiennej d, możemy więc powiedzieć, że
definiuje funkcję boolowską trzech zmiennych, a mianowicie d = f (a, b, c).
Gdybyśmy potrafili tę funkcję zapisać w postaci formuły (a więc symbolicz-
nego wyrażenia składającego się ze zmiennych, operatorów boolowskich i na-
wiasów) – to korzystając z doświadczeń poprzedniego podrozdziału, z pew-
nością doprowadzilibyśmy do zbudowania odpowiedniej sieci logicznej.

Tab. 12.6. Tabela prawdy dla układu głosującego


Wejścia Wyjście
c b a d
0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 0
1 0 1 1
1 1 0 1
1 1 1 1

Na szczęście, każdą funkcję boolowską można na podstawie jej tabeli praw-


dy zapisać (i to na dwa sposoby) w pewnej podstawowej, standardowej
formie, zwanej postacią kanoniczną funkcji boolowskiej. Zaraz przekona-
my się, jak to się robi. W zależności od wybranego sposobu postępowania,
otrzymamy tzw. postać kanoniczną sumy albo postać kanoniczną iloczynu.
Obie postaci są już boolowskimi formułami, można je więc dalej przekształ-
cać, minimalizować itd., a na końcu – przerobić na sieć logiczną.
Pomysł obu postaci kanonicznych opiera się na spostrzeżeniu, że
tabelka prawdy dowolnej boolowskiej funkcji n zmiennych ma dość prze-
widywalną budowę. Jej lewa część jest zawsze taka sama: zawiera wykaz
wszystkich 2n kombinacji zer i jedynek pojawiających się na wejściu ukła-
du. Dopiero ostatnią kolumnę wypełnia się zerami i jedynkami w zależności
od tego, jaką funkcję zamierzamy zdefiniować. Ale jeszcze przed wypeł-
nieniem tej ostatniej kolumny możemy z góry dla każdego wiersza tabeli
określić taką funkcję, która tylko w tym wierszu (i w żadnym innym) będzie
przyjmowała wartość 1, albo taką, która tylko w tym wierszu (i w żadnym
innym) będzie przyjmowała wartość 0.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

300 12. Elementarz syntezy logicznej

Zajmijmy się pierwszym przypadkiem: funkcjami, które przyjmują


w danym wierszu wartość 1. Są to iloczyny elementarne. Popatrzmy na ta-
belę 12.7, w której są one wypisane dla naszego przykładu o trzech zmien-
nych wejściowych. Kto nie wierzy – niech sam sprawdzi, że:
– iloczyn c ⋅ b ⋅ a przyjmuje wartość 1 tylko w wierszu (c, b, a) = (1, 1, 1),
– iloczyn c ⋅ b ⋅ a – tylko w wierszu (0, 0, 0),
– iloczyn c ⋅ b ⋅ a – tylko w wierszu (0, 1, 1)
i tak dalej.

Tab. 12.7. Iloczyny elementarne

Wejścia Wyjście Iloczyny elementarne


odpowiadające
c b a ? poszczególnym wierszom
0 0 0 c ⋅b ⋅a
0 0 1 c ⋅b ⋅a
0 1 0 c ⋅ b⋅ a
0 1 1 c ⋅ b⋅ a
1 0 0 c⋅b ⋅a
1 0 1 c⋅b ⋅a
1 1 0 c⋅ b⋅ a
1 1 1 c⋅ b⋅ a

Zasadę tę można łatwo uogólnić na większą liczbę zmiennych, a iloczyny


elementarne tworzy się prawie mechanicznie. To bardzo proste: w każdym
wierszu trzeba napisać iloczyn wszystkich zmiennych wejściowych, a na-
stępnie postawić kreskę negacji nad każdą taką zmienną, która w danym
wierszu ma wartość 0.
Jeżeli teraz w taką tabelę wpiszemy kolumnę zer i jedynek, odpowia-
dającą projektowanej funkcji d = f (a, b, c) (jak w tabeli 12.8), to uświa-
domimy sobie, że d można przedstawić jako logiczną sumę iloczynów ele-
mentarnych, odpowiadającą tym wierszom, w których d = 1. Rzeczywiście,
każdy z tych iloczynów dostarczy swoją jedynkę, a w wierszach, których
nie uwzględniliśmy w tej sumie – pozostaną zera.
Tak więc dla układu głosującego można napisać:

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

Od tabelki prawdy do sieci logicznej 301

Tab. 12.8. Postać kanoniczna sumy

Wejścia Wyjście Iloczyny elementarne


odpowiadające
c b a d poszczególnym wierszom
0 0 0 0 c ⋅b ⋅a
0 0 1 0 c ⋅b ⋅a
0 1 0 0 c ⋅ b⋅ a
0 1 1 1 c ⋅ b⋅ a
1 0 0 0 c⋅b ⋅a
1 0 1 1 c⋅b ⋅a
1 1 0 1 c⋅ b⋅ a
1 1 1 1 c⋅ b⋅ a

Jest to właśnie pierwsza ze wspomnianych postać kanoniczna sumy.


Jako boolowską formułę, możemy ją dalej dowolnie przekształcać, wyko-
rzystując prawa algebry Boole’a.
Na przykład weźmy pierwszy i ostatni składnik tej sumy i wyciągnij-
my z nich przed nawias b ⋅ a, a z drugiego i trzeciego składnika wyciągnij-
my przed nawias c. Otrzymamy

d = b ⋅ a ⋅ (c + c) + c ⋅ (b ⋅ a + b ⋅ a ).

Ale c + c = 1 (tożsamość 5), b ⋅ a ⋅1 = b ⋅ a (tożsamość 4), więc:

d = a ⋅ b + c ⋅ ( a ⋅ b + a ⋅ b ).

Jeśli ta postać formuły nam odpowiada, to możemy już narysować schemat


sieci logicznej jak na rysunku 12.7.

Rys. 12.7. Przykładowa sieć logiczna dla układu głosującego


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

302 12. Elementarz syntezy logicznej

Tab. 12.9. Sumy elementarne i postać kanoniczna iloczynu


Wejścia Wyjście Sumy elementarne
odpowiadające poszczególnym
c b a d
wierszom
0 0 0 0 c⋅ b⋅ a
0 0 1 0 c⋅ b⋅ a
0 1 0 0 c⋅b ⋅a
0 1 1 1 c⋅b ⋅a
1 0 0 0 c ⋅ b⋅ a
1 0 1 1 c ⋅ b⋅ a
1 1 0 1 c⋅ b⋅ a
1 1 1 1 c ⋅b ⋅a

Druga postać kanoniczna zakłada, że zadana funkcja boolowska jest ilo-


czynem sum elementarnych. Te zaś (również określone dla poszczególnych
wierszy tabeli w łatwy do odgadnięcia sposób) mają tę właściwość, że mają
dla danego wiersza wartość 0. Do iloczynu, będącego tą drugą postacią
kanoniczną, bierze się sumy, które odpowiadają wierszom, gdzie wartość
funkcji jest równa zeru (tabela 12.9).
Inaczej mówiąc, formułę boolowską dla układu głosującego można
zapisać również w następującej (drugiej) kanonicznej postaci iloczynu:

d = (c + b + a ) ⋅ (c + b + a ) ⋅ (c + b + a ) ⋅ (c + b + a ).

Zawartość każdego z nawiasów przyjmuje wartość 0, jeżeli kombinacja


zer i jedynek na wejściu odpowiada wierszowi, dla którego dana elemen-
tarna suma została zdefiniowana. Ponieważ obecność choćby jednego zera
w iloczynie wystarcza do tego, by cały iloczyn też był równy zeru – w po-
staci kanonicznej iloczynu uwzględniamy te sumy elementarne, które stoją
w wierszach, gdzie f (a, b, c) = 0. Oczywiście i tę formułę można dalej
przekształcać do woli: na przykład mnożąc zawartość nawiasów, a następnie
– wykorzystując nasz zestaw 17 praw algebry Boole’a – grupując powstałe
iloczyny zmiennych w inny sposób i upraszczając je.
Opracowano wiele metod ułatwiających przekształcanie i uprasz-
czanie boolowskich funkcji, tak by np. zmniejszyć łączną liczbę bramek
potrzebnych do realizacji sieci logicznej, zmniejszyć liczbę bramek okre-
ślonego typu itd. Jedną z nich, opartą na tablicach Karnaugha, poznamy
w następnym rozdziale, w którym znajdziemy prosty, odpowiedni przykład,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Jak zrobić trzydziestodwubitowy sumator? 303

dający się łatwo wykonać ręcznie. Oczywiście, z biegiem lat opracowano


także wiele programów komputerowych, wspomagających projektantów
sieci logicznych o tak dużej liczbie zmiennych, że zastosowanie różnych
pomysłowych, ale „ręcznych” sposobów nie jest praktycznie możliwe. Są
one dziś powszechnie wykorzystywane do projektowania komputerowego
sprzętu.
Warto zauważyć, że problemy przekształcania funkcji boolowskich są
z zasady problemami NP, tj. o nieakceptowalnie wielkiej złożoności obli-
czeniowej, które eksplodują wykładniczo wraz ze wzrostem liczby zmien-
nych. Wspomniane współczesne algorytmy wspomagające projektowanie
układów logicznych mają więc z reguły charakter heurystyczny, to znaczy
opierają się na pewnych wynalazczych pomysłach upraszczających. Dlatego
– mimo wysiłków tysięcy badaczy i upływu kilkudziesięciu lat – tematyka
syntezy logicznej stanowi wciąż żywy przedmiot badań i zapełnia treść wie-
lu międzynarodowych konferencji i czasopism naukowych. Zawsze bowiem
istnieje nadzieja, że ktoś wpadnie na pomysł jeszcze lepszy, prowadzący do
rozwiązania jeszcze skuteczniejszego w praktyce.

Jak zrobić trzydziestodwubitowy sumator?


Dziś odpowiedź na to pytanie jest w zasadzie prosta: w ogóle nie robić, tyl-
ko kupić. Nikt (poza producentami układów scalonych) już teraz nie buduje
samodzielnie sumatorów ani całych arytmometrów (inaczej: jednostek aryt-
metyczno-logicznych, ALU – z ang. artithmetic and logic unit), zdolnych do
wykonywania nie tylko dodawania, lecz i innych operacji arytmetycznych
i logicznych na wielobitowych słowach. Są one od dawna zrobione, gotowe
i możliwe do kupienia jako gotowe układy scalone lub jako nierozdzielna
część procesora w naszym komputerze.
Ale na podstawie tego, czego dowiedzieliśmy się z poprzednich roz-
działów, potrafimy przynajmniej wyobrazić sobie, jak taki wielobitowy
sumator moglibyśmy zrobić. Zbierzmy na jednym rysunku (rysunek 12.8)
zasady dodawania liczb dwójkowych w kodzie U2, które znamy z rozdzia-
łu 11.
Przypomnijmy przebieg operacji arytmetycznego dodawania n-bito-
wych liczb A i B. Poczynając od najmniej znaczącej pozycji i posuwając
się w lewo (tj. kolejno dla i = 0, 1, 2, ..., n – 1), na podstawie zawartości
bitów ai oraz bi, a także przeniesienia ci – wyznaczamy z załączonej tabelki
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

304 12. Elementarz syntezy logicznej

Rys. 12.8. Zasady dodawania w kodzie U2

wartość wyniku wi na i-tej pozycji oraz przeniesienie ci + 1, które przemiesz-


cza się na następną pozycję. Przeniesienie, powstałe na ostatniej, najbardziej
znaczącej pozycji, wybiega poza granicę słowa jako C. Dodatkowo, po za-
kończeniu dodawania, oceniamy, czy wynik mieści się w zakresie właści-
wym dla n-bitowych liczb: tak, mieści się, jeśli dwa ostatnie przeniesienia
mają tę samą wartość, nie – jeśli różnią się one od siebie. Przekroczenie
zakresu sygnalizuje zmienna V, która wtedy przyjmuje wartość 1.
Zauważmy, że tabelka prawdy opisująca dodawanie na pojedynczej
i-tej pozycji definiuje dwie boolowskie funkcje:
– wi w zależności od ai, bi, ci,
– ci + 1 w zależności od ai, bi, ci.
Zajmijmy się najpierw pierwszą z nich, wi = f(ai, bi, ci). Zapiszmy
tę funkcję w pierwszej postaci kanonicznej, tj. jako sumę logiczną czterech
iloczynów, pomijając dolne indeksy (i) dla czysto typograficznej wygody:

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

Jak zrobić trzydziestodwubitowy sumator? 305

Tak, to ta sama funkcja, na której ćwiczyliśmy zastosowanie praw algebry


Boole’a. Stamtąd wiemy już, że można ją zapisać inaczej:

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.

Rys. 12.9. Jednobitowy półsumator

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 )

tyle tylko, że teraz zmienna d nazywa się ci + 1.


Tak więc, aby otrzymać jednobitowy sumator, powinniśmy zbudować sieć
logiczną, która ma trzy wejścia: ai, bi, ci i dwa wyjścia: wi oraz ci + 1, zada-
ne powyższymi zależnościami. Zauważmy, że w obu formułach występu-
je wspólny wyraz, a mianowicie ( a ⋅ b + a ⋅ b ), który można zrealizować za
pomocą jednej bramki XOR. W rezultacie jednobitowy sumator przybierze
postać taką jak na rysunku 12.10.

Rys. 12.10. Jednobitowy sumator


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

306 12. Elementarz syntezy logicznej

Rys. 12.11. 32-bitowy sumator

Skoro już się upewniliśmy, że potrafimy zbudować jednobitowy sumator


– zastąpmy całą tę sieć jednym blokowym symbolem, jak w prawej części
rysunku 12.10. Teraz zróbmy 32 takie jednobitowe sumatory, połączmy je
ze sobą jak na rysunku 12.11 – i trzydziestodwubitowy sumator jest już
prawie gotowy.
Rzeczywiście, jeżeli na wejścia takiego zespołu jednobitowych suma-
torów podamy dwa 32-bitowe słowa:
A = (a31, a30, ..., a2, a1, a0),
B = (b31, b30, ..., b2, b1, b0),
dbając, rzecz jasna, by poszczególne pary bitów trafiły na wejścia odpo-
wiednich, swoich jednobitowych sumatorów – to na wyjściach (w31, w30, ...,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Jak zrobić trzydziestodwubitowy sumator? 307

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

308 12. Elementarz syntezy logicznej

NOR7. Jest ona jedynie symbolicznie zaznaczona na rysunku 12.11: dla


uproszczenia, połączeń między poszczególnymi bitami wyniku W a jej
wejściami nie narysowano.
I gotowe.
A gdybyśmy chcieli zbudować sprzętowy sumator 64-bitowy? Trzeba
by użyć do tego dwóch takich sumatorów 32-bitowych. Jeden obsługiwałby
mniej znaczącą połowę obu 64-bitowych argumentów dodawania (tj. bity
o numerach od 0 do 31 obu liczb, A i B), drugi – połowę bardziej znaczącą
(bity o numerach od 32 do 63). Oczywiście, wyjście C sumatora „mniej zna-
czącego” należałoby połączyć z wejściem c0 (a właściwie teraz c32) drugie-
go sumatora, tak by przeniesienie z mniej znaczącej połowy przemieszczało
się na początek połowy bardziej znaczącej. Pewnej oczywistej modyfikacji
musiałaby ulec także część produkująca bit Z dla całego 64-bitowego słowa
wyniku, natomiast bity C, V oraz N można by wziąć wprost, tak jak zostały
one zrobione dla drugiego, bardziej znaczącego sumatora.
Zaprojektowaliśmy w ten sposób sumator, czyli sieć logiczną, która
wykona arytmetyczne dodawanie wielobitowych liczb zapisanych w dwójko-
wym kodzie U2. To ćwiczenie powinno pozwolić nam przynajmniej wyobra-
zić sobie, jak można zbudować bardziej złożony dwójkowy arytmometr8.
Arytmometr taki powinien „umieć” wykonać (na polecenie układu
sterowania procesora, o czym powiemy więcej w rozdziale 16) nie tylko
arytmetyczne dodawanie, lecz również kilkanaście innych jedno- lub dwuar-
gumentowych operacji arytmetycznych i logicznych na wielobitowych sło-
wach. Do takich typowych operacji arytmetycznych należą:
– odejmowanie dwóch (np. 16-, 32-, 64-bitowych) liczb,
– zamiana liczby (np. A) na przeciwną (–A),
– operacja następnika (czyli arytmetyczne dodawanie do liczby A jedynki,
co jest często wykonywaną operacją),
– operacja poprzednika (arytmetyczne odejmowanie jedynki),

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

Inne „prefabrykowane” podzespoły logiczne 309

– arytmetyczne dodawanie/odejmowanie z jednoczesnym następnikiem/po-


przednikiem,
– arytmetyczne przesuwanie słowa o jedną pozycję w lewo (czyli mnożenie
przez 2) i w prawo (dzielenie przez 2)
itd.
Potrzebne są też podstawowe operacje nie arytmetyczne, lecz logicz-
ne na słowach. Najczęściej są to:
– negacja (polegająca na logicznym zanegowaniu zawartości każdego bitu
wskazanego argumentu),
– suma logiczna (w której każdy bit wyniku jest boolowską sumą odpo-
wiednich bitów obu argumentów na tej pozycji),
– iloczyn logiczny (analogicznie),
– różnica symetryczna (operacja XOR na odpowiednich parach bitów),
– przesuwanie logiczne i cykliczne o jedną pozycję w lewo i w prawo.
W przypadku każdej z tych operacji należałoby postąpić podobnie,
jak to zrobiliśmy wyżej: utworzyć tabelkę prawdy opisującą wykonanie tej
operacji na jednej parze bitów, zbudować odpowiednią jednobitową sieć lo-
giczną, a następnie „rozmnożyć” ją i połączyć tak, by operacja wykonywała
się na wielobitowych argumentach.

Inne „prefabrykowane” podzespoły logiczne


Typowe, często występujące sieci logiczne są produkowane przez produ-
centów układów scalonych jako gotowe, jak gdyby prefabrykowane pod-
zespoły funkcjonalne, które projektanci bardziej złożonych układów wy-
korzystują potem w swych urządzeniach, nie projektując ich samodzielnie,
od zera. Jednostka arytmetyczno-logiczna (ALU) jest przykładem tego typu
podzespołów.
Inną często używaną siecią logiczną, produkowaną jako gotowy pod-
zespół, jest multiplekser (rysunek 12.12). Ma on jedno wyjście y oraz pew-
ną liczbę wejść sterujących oraz informacyjnych. Jeśli wejść sterujących jest
k (na przykład na rysunku 12.13 są dwa: s0 oraz s1), to wejść informacyj-
nych jest 2k (więc tutaj – cztery).
Aktualny stan wejść sterujących wskazuje w każdej chwili jedno z wejść
informacyjnych, którego stan (0 albo 1) ma być skopiowany do wyjścia y. Zero-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

310 12. Elementarz syntezy logicznej

Rys. 12.12. Multiplekser

Rys. 12.13. Demultiplekser

-jedynkowy stan wejść sterujących jest po prostu dwójkowym numerem wska-


zywanego wejścia informacyjnego. Kiedy więc na przykład (s0, s1) = (0,0),
to takim wyróżnionym wejściem informacyjnym jest x0 = x00 i jeżeli x00 = 0,
to również y = 0, a jeżeli x00 = 1, to również y = 1. Jeżeli (s0, s1) = (0,1), to
na wyjście y jest kopiowany stan wejścia x1 = x01, i tak dalej.
Jako sieć logiczna multiplekser jest logiczną sumą 2k iloczynów lo-
gicznych (patrz prawa część rysunku 12.12). Każdy z tych iloczynów ma
jako jedno z wejść „swoje” wejście informacyjne, a pozostałe k wejść – to
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Inne „prefabrykowane” podzespoły logiczne 311

albo zmienne sterujące, albo ich negacje, symbolizowane kółeczkiem dole-


pionym do wejścia danej bramki iloczynu. Układ tych negacji odpowiada
oczywiście w przemyślany i łatwy do odgadnięcia sposób numerowi wej-
ścia informacyjnego.
Na przykład, bramka iloczynu „obsługująca” wejście informacyj-
ne x00 wykonuje funkcję logiczną x00 ⋅ s1 ⋅ s0 . Jeżeli więc (s0, s1) = (0,0),
to x00 ⋅ s1 ⋅ s0 = x00 ⋅1 ⋅1 = x00 i taka właśnie wartość stanie się wejściem do
końcowej bramki sumy. Jednocześnie – co łatwo sprawdzić – na wyjściach
pozostałych bramek iloczynu jest z pewnością 0, gdyż co najmniej jedno
z wejść sterujących (lub ich negacji) w każdej z tych pozostałych bramek
ma wartość 0. Dlatego, w tej sytuacji jeśli x00 = 1, to również y = 1, a jeśli
x00 = 0, to również y = 0 itd.
Inny układ, mianowicie demultiplekser (rysunek 12.13) działa „w od-
wrotną stronę”. Ma on z kolei jedno wejście informacyjne x, k wejść steru-
jących oraz 2k wyjść informacyjnych. Tutaj, stan wejść sterujących wybiera
to wyjście, na które ma być skopiowany stan jedynego wejścia informacyj-
nego. Wejście x jest argumentem wszystkich bramek iloczynu logicznego.
Podobnie, jak poprzednio, układ pozostałych wejść w każdej z bramek ilo-
czynu (zmienne sterujące albo ich negacje) gwarantuje, że jeśli na wspól-
nym wejściu x jest 1, to tylko dokładnie jedno, wybrane wyjście tę jedynkę
otrzyma.
Najbardziej typowe zastosowanie multiplekserów i demultiplekserów
polega na kierowaniu przepływem binarnych danych wewnątrz większej jed-
nostki, na przykład procesora. Pełnią one rolę analogiczną do rozjazdów czy
zwrotnic kolejowych: dzięki wejściom sterującym, układ sterowania proceso-
ra może (w przypadku multipleksera) wybrać jedno z alternatywnych źródeł
danych i przesłać jego zawartość do dalszego przetwarzania albo (w przy-
padku demultipleksera) zdecydować, którą z alternatywnych dróg posłać da-
lej otrzymaną lub wyliczoną daną. Przekonamy się o tym jeszcze w rozdziale
16, poświęconym organizacji i zasadom działania procesora.
Takich gotowych podzespołów wytwarza się oczywiście znacznie
więcej. Nie będziemy ich tu szczegółowo omawiać. Warto co najwyżej
wspomnieć jeszcze na koniec o koderach i dekoderach. Są to sieci o wielu
wejściach i wielu wyjściach, przekształcające wielobitowe dwójkowe wek-
tory wejściowe na wektory wyjściowe. Dla przykładu, gdybyśmy chcieli dla
poszczególnych cyfr dziesiętnych, naciskanych na dziesiętnej klawiaturze,
otrzymać kod BCD liczby, którą ta cyfra oznacza – zastosowalibyśmy koder,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

312 12. Elementarz syntezy logicznej

który miałby 10 binarnych zmiennych wejściowych, przy czym stan 1 danej


zmiennej oznaczałby, że dany klawisz jest naciśnięty. Na wyjściu miałby on
4 zmienne wyjściowe, których łączny stan byłby kodem BCD naciskanej cy-
fry. Tablica opisująca działanie takiego kodera musiałaby mieć – teoretycz-
nie – 210 wierszy, na szczęście nie musimy ich wszystkich wymieniać. Jeżeli
założymy, że spośród 10 klawiszy naciśnięty może być tylko co najwyżej
jeden – to postać tabelki prawdy znacznie się upraszcza. Proszę samodzielnie
się o tym przekonać.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

13. Układy sekwencyjne

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

314 13. Układy sekwencyjne

nego przeskoku, odbywającego się w możliwie krótkim czasie, a teoretycz-


nie – w czasie równym zeru. Angielska nazwa takiego układu, flip flop,
po polsku mogłaby brzmieć jak klip klap albo pstryk pstryk, co obrazowo
opisuje naturę urządzenia.
Przerzutniki dzielą się na dwie główne klasy: synchroniczne i asyn-
chroniczne (ang. synchronous, asynchronous flip flops). W przerzutnikach
synchronicznych ich działanie jest taktowane przez wspólny sygnał zegaro-
wy (w skrócie – zegar). Jest on doprowadzony do wszystkich przerzutników
w całym układzie i wyznacza wspólny dla wszystkich rytm upływu czasu.
Sygnał zegarowy (ang. clock) jest wytwarzany przez generator,
wspólny dla całego komputera. Fizycznie jest on ciągiem cyklicznie powta-
rzających się prostokątnych impulsów elektrycznych, jak na rysunku 13.1.
W rzeczywistości, impulsy te nie są prawdziwie prostokątne, choć elektroni-
cy bardzo się o to starają. Oba zbocza impulsów (zarówno zbocze narastają-
ce, jak i opadające) nie są ściśle pionowe, lecz nieco nachylone. My jednak
pozostaniemy przy wyidealizowanym założeniu o prostokątnym kształcie
impulsów zegarowych.

Rys. 13.1. Zegar

Odcinek sygnału między dwoma sąsiednimi narastającymi zboczami jest jed-


nym cyklem zegara. Liczba cykli na sekundę – to oczywiście częstotliwość
zegara. Stanowi ona jeden z podstawowych (i powszechnie znanych) tech-
nicznych parametrów, dla wielu osób świadczących o „szybkości” czy na-
wet „nowoczesności” komputera. W pierwszych komputerach była ona rzę-
du pojedynczych kiloherców (kHz), a więc kilku tysięcy cykli na sekundę.
Obecnie, częstotliwość zegara jest rzędu kilku GHz (gigaherców), czyli kilku
miliardów (tysięcy milionów) cykli na sekundę. Przy takiej częstotliwości,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Przerzutniki 315

czas jednego cyklu spada poniżej 1 ns (nanosekundy), czyli 0,000000001


sekundy1.
Przerzutniki asynchroniczne (a więc także zbudowane z nich asyn-
chroniczne układy sekwencyjne) wspólnego zegara natomiast nie mają.
Każdy z przerzutników decyduje o swoim stanie jak gdyby na własną rękę,
bez oglądania się na wspólny rytm, który w układach synchronicznych – jak
dyrygent orkiestrze – wyznacza zegar. Niestety, podobnie, jak w orkiestrze
bez dyrygenta, może to prowadzić do nieporozumień i dysonansów. Drobne
różnice w szybkości zmiany stanu (wyścigi przerzutników), różnice w dłu-
gościach dróg, jakimi elektryczny sygnał przemieszcza się przez łańcuch
bramek itd. – mogą powodować losowe pojawianie się nieprawidłowych sy-
gnałów, nieprawidłowych stanów przejściowych i nieprawidłowych reakcji
całego układu, w tym nawet zawieszanie się („utknięcie” w pewnym stanie
na zawsze) czy niestabilność (samoczynne „migotanie” między stanami).
Dlatego projektowanie dobrze działających układów asynchronicznych jest
trudniejsze i bardziej kłopotliwe niż układów synchronicznych.
Z tego właśnie powodu ogromna większość obecnie budowanych
urządzeń cyfrowych (z komputerami na czele) opiera się na zastosowaniu
układów synchronicznych, ze wspólnym zegarem. Również i my będziemy
mówili dalej wyłącznie o przerzutnikach synchronizowanych (i o zbudowa-
nych z ich udziałem synchronicznych układach sekwencyjnych), zostawia-
jąc układy asynchroniczne bardziej zaawansowanym specjalistom.
Podobnie jak w przypadku bramek, nie będziemy wnikać w fizyczną,
elektroniczną budowę „wnętrza” przerzutników. Oglądane z zewnątrz, mają
one jedno lub kilka wejść oraz – z zasady – dwa wyjścia. Na każdym z nich
może pojawiać się napięcie albo niskie (L, low), albo wysokie (H, high),
o takich fizycznych wartościach, jakie obowiązują w danej technologii ukła-
dów elektronicznych zarówno dla przerzutników, jak i współpracujących
z nimi bramek logicznych. Jak poprzednio przyjmijmy, że napięcie L – to
logiczne 0, a napięcie H – to 1.

1 Łatwo policzyć, że na przestrzeni około sześćdziesięciu lat częstotliwość zegara


zwiększyła się prawie milion razy. To dobra ilustracja postępu, jaki w tym czasie
dokonał się w technologii układów elektronicznych. Trudno byłoby znaleźć inną
dziedzinę techniki, w której postęp byłby porównywalny.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

316 13. Układy sekwencyjne

Zacznijmy od przykładu synchronicznego przerzutnika typu T (od ang.


trigger), synchronizowanego narastającym zboczem zegara (rysunek 13.2).
Ma on jedno wejście t oraz dwa wyjścia: q oraz q . Jedno z wyjść, miano-
wicie q, jest utożsamiane ze stanem przerzutnika. Jeśli q = 1, to znaczy, że
przerzutnik jest w stanie 1 (w elektronicznej gwarze mówi się często, że jest
zapalony, po angielsku – set). Jeżeli q = 0, to i przerzutnik jest w stanie 0
(czyli zgaszony lub wyzerowany, ang. reset). Drugie z wyjść jest negacją q.
Fakt, że obok stanu przerzutnika dostępna jest od razu również jego negacja
– jest bardzo wygodny przy budowaniu sieci logicznej, z którą przerzutnik
zapewne będzie współpracował.

Rys. 13.2. Synchroniczny przerzutnik typu T

Oczywiście, do przerzutnika jest również doprowadzony zegar, ale (jako sy-


gnał o charakterze technicznym) nie jest on traktowany jako jedno z jego
wejść. Co więcej, jeśli wiadomo, że cały układ jest synchroniczny i zegar
jest doprowadzony do wszystkich przerzutników, to na logicznych schema-
tach zegar i rozprowadzające go linie można (dla jasności rysunku) pominąć
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Przerzutniki 317

w ogóle. Podobnie pomijamy przecież także inne techniczne połączenia: do-


prowadzenie napięcia zasilającego, uziemienia itp. Nie można o nich oczy-
wiście zapomnieć w dokumentacji technicznej czy konstrukcyjnej, gdzie
trzeba zadbać o to, by odpowiednie przewodzące ścieżki połączyły gene-
rator zegara, źródło napięcia zasilającego, uziemienie itd. z odpowiednimi
punktami wszystkich przerzutników i bramek.
Przerzutnik typu T (jak zresztą każdy przerzutnik) jest w istocie pro-
stym, dwustanowym automatem skończonym. Jego zachowanie można opisać
za pomocą zamieszczonej na rysunku 13.2 tablicy przejść. Wiersze tej tabeli
odpowiadają stanom przerzutnika (czyli wartościom q), kolumny – warto-
ściom logicznym na jego wejściu t. W każdym z czterech środkowych pól
tabeli podano, jaki będzie następny stan wyjścia q, jeżeli wejście właściwe dla
danej kolumny zastanie przerzutnik w stanie właściwym dla danego wiersza.
Jak widać, jeżeli na wejście t podawane jest logiczne 0, to przerzutnik
nie zmienia stanu. Rzeczywiście, jeśli jest w stanie q = 0, to pozostanie dalej
w stanie q = 0. Jeśli jest aktualnie w stanie q = 1, to nadal będzie w tym
stanie. Jeżeli natomiast na wejściu t pojawi się logiczne 1 – to przerzutnik
zmieni stan na przeciwny: jeśli był w stanie q = 0, to następnym stanem jest
q = 1 i odwrotnie.
Bardzo ważna jest przy tym rola sygnału zegarowego. Ilustrują to
wykresy zamieszczone na dole rysunku 13.2. Założyliśmy, że omawiany
przerzutnik jest synchronizowany narastającym zboczem zegara. Znaczy to,
że stan wejścia t oraz wyjścia q jest odczytywany dokładnie w tej krótkiej
chwili, gdy sygnał zegarowy narasta od wartości niskiej (L) do wysokiej
(H). W tym właśnie momencie, zgodnie z tabelą przejść, podejmowana jest
nieodwołalna decyzja o tym, jaki będzie ów następny stan przerzutnika.
Obrazowo można powiedzieć, że zegar jak gdyby produkuje na początku
każdego cyklu krótki błysk flesza, oświetlając na tę chwilę stan wyjścia q
oraz wejścia t i pozwalając podjąć decyzję co do następnego stanu.
Z chwilą zakończenia zbocza sygnału zegarowego przerzutnik cał-
kowicie przestaje reagować na to, co się dzieje na jego wejściu t. Trwa to
przez całą pozostałą część cyklu zegarowego, aż do następnego „błysku”
narastającego zbocza, kiedy to stan q oraz t będą badane ponownie. W tym
okresie wejście przerzutnika nie ma żadnego wpływu na jego działanie,
a przerzutnik ma dużo czasu na ewentualną zmianę stanu.
Musimy pamiętać, że wyjścia przerzutników są zazwyczaj połączo-
ne z wejściami jakichś bramek logicznych, te z kolei (być może poprzez
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

318 13. Układy sekwencyjne

następne bramki) przenoszą logiczny sygnał do wejść innych przerzutni-


ków itd. Potrzebny jest więc czas na to, by zakończyły się wszelkie pro-
cesy przejściowe nie tylko we wszystkich przerzutnikach, lecz również
w sterowanych przez nie bramkach. Dzięki temu, że może się to bezpiecznie
odbyć, zanim nastąpi kolejne zbocze zegara – mamy pewność, że w mo-
mencie następnego badania stan wejść i wyjść wszystkich przerzutników
jest z pewnością taki, jak poprzednio zdecydowano.
Oczywiście, ktoś może zauważyć, że przerzutnik synchroniczny re-
aguje na stan wejścia z pewnym opóźnieniem: ewentualna zmiana stanu
wejścia t jest zauważana nie natychmiast, lecz dopiero w chwili najbliższe-
go narastającego zbocza zegara. To prawda. Co więcej, przerzutnik może
nawet w ogóle nie zareagować na bardzo krótki impuls, który ukryje się
między dwoma narastającymi zboczami. Jednak jest to nie wada, lecz właś-
nie zaleta, przemawiająca na korzyść synchronicznych rozwiązań. Jeżeli
sygnał wejściowy pochodzi ze świata zewnętrznego, gdzie nie obowiązuje
wspólny zegar – to najwyżej poczeka ten ułamek nanosekundy. Natomiast
we wnętrzu układu synchronicznego, gdzie wszystkie przerzutniki podej-
mują decyzję o następnym stanie dokładnie w tej samej chwili – pojawienie
się przesuniętych w czasie zmian stanu wejścia czy takich krótkotrwałych
impulsów jest prawdopodobnie rezultatem stanów przejściowych, różnic
w czasie przełączania i propagacji itd. Dlatego należy je zignorować.
Na rysunku 13.3 przedstawiono dwa inne typy często używanych
przerzutników, mianowicie przerzutnik typu D (od ang. delay, czyli opóź-
nienie) oraz typu RS (albo SR, od ang. set – reset). Pierwszy z nich ma jed-
no wejście d i dwa (wzajemnie zanegowane) wyjścia: q oraz q . Jak poka-
zuje tabela przejść – aktualny stan wejścia d przenosi się na następny stan q.
Można więc powiedzieć, że przerzutnik typu D pamięta (czy też „opóźnia”)
stan wejścia sprzed jednego cyklu zegarowego.
Przerzutnik typu RS ma dwa wejścia. Ich nazwy odpowiadają funk-
cjom, jakie one pełnią: s oznacza set, czyli „ustaw przerzutnik w stan q = 1”,
r oznacza reset, czyli „wyzeruj” przerzutnik. Szczegóły wyjaśnia tabela
przejść2. Tak więc, zgodnie z tabelą przejść przerzutnika typu RS:
– jeżeli s = 0 i jednocześnie r = 0, to przerzutnik pozostanie w dotychcza-
sowym stanie,

2 Proszę zwrócić uwagę na nietypową kolejność numerowania kolumn tej tabeli.


Nie jest ona przypadkiem, a sprawa wyjaśni się dalej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Przerzutniki 319

– jeżeli s = 0 i r = 1, to niezależnie od aktualnego stanu następny cykl ze-


garowy zastanie przerzutnik na pewno „zgaszony” (będzie q = 0),
– jeżeli przeciwnie, s = 1 i r = 0, to następny cykl zegarowy zastanie prze-
rzutnik na pewno „zapalony” (będzie q = 1).
Wymaga jeszcze rozstrzygnięcia pytanie, jak interpretować sytuację,
w której jednocześnie s = 1 oraz r = 1. Możliwości jest kilka:
– można po prostu zabronić występowania takiej sytuacji. O to, by nie poja-
wiała się ona na wejściu przerzutnika, musi wtedy zadbać projektant układu
sekwencyjnego;
– można zbudować przerzutnik tak, by był on typu RS z przewagą S, albo
typu RS z przewagą R. W pierwszym przypadku zawartość kolumny
oznaczonej 11 byłaby taka sama jak kolumny 10 (samo set), w drugim
– jak kolumny 01 (samo reset);

Rys. 13.3. Przerzutniki typu D, RS, JK


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

320 13. Układy sekwencyjne

– można zbudować przerzutnik tak, by pojawienie się kombinacji (sr) = (11)


powodowało zmianę stanu przerzutnika na przeciwny, tak jak to się dzieje
w przerzutniku typu T. O takim przerzutniku mówi się, że jest typu JK 3.
Nie będziemy nużyć czytelnika omawianiem dalszych typów prze-
rzutników synchronicznych. Lepiej zobaczmy, jak te, które już poznaliśmy,
można wykorzystać do zbudowania użytecznych układów sekwencyjnych.

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.

3 Oznaczenie JK upamiętnia wynalazcę tego przerzutnika (1958), Jacka Kilby’ego


z Texas Instruments, tego samego, który w roku 2000 otrzymał Nagrodę Nobla
z fizyki za udział w opracowaniu układów scalonych.
4 Oczywiście, poza dziedziną komputerowego hardware’u, termin rejestr jest w in-
formatyce używany również w bardziej potocznym sensie, gdzie oznacza wykaz,
spis itd.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Rejestry i liczniki 321

Podobnie jak sumatorów czy całych jednostek arytmetyczno-logicz-


nych nikt obecnie rejestrów sam nie projektuje. Gdybyśmy jednak zechcieli
to uczynić, wykorzystując np. przerzutniki typu D – to moglibyśmy to zro-
bić tak, jak pokazano na rysunku 13.4.

Rys. 13.4. Rejestr zbudowany z użyciem przerzutników typu D


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

322 13. Układy sekwencyjne

Na poziomie zero-jedynkowym traktowaliśmy taki n-bitowy rejestr


R jako „okienko”, zawierające n pozycji, ponumerowanych od r0 do rn – 1,
a w każdej z nich mogło być wpisane albo 0, albo 1. Rysunek 13.4 ujawnia
szczegóły budowy jednej, i-tej pozycji rejestru R. Pozostałe są identycz-
ne. Do pamiętania wartości bitu ri wykorzystaliśmy tu synchroniczny prze-
rzutnik typu D. Doprowadzenie zegara (zarówno do tego, jak i wszystkich
innych przerzutników) pominięto, a bramki logiczne ponumerowano, dla
wygody w objaśnianiu.
Od rejestru wymagamy, żeby można było na nim przeprowadzić co
najmniej trzy następujące operacje:
– odczytanie jego aktualnej zawartości,
– zerowanie zawartości,
– zapisanie nowej zawartości.
Wywoływaniu tych operacji służą trzy linie sterujące: odczytaj, zeruj
oraz zapisz, biorące swój początek w pewnym (niepokazanym tutaj) ukła-
dzie sterowania i dochodzące jednocześnie do wszystkich pozycji rejestru.
Jeżeli po którejś z tych linii przychodzi do rejestru logiczne 0, to znaczy,
że dana czynność ma się nie wykonywać. Jeśli jednak układ sterowania
poda logiczne 1 na linię odczytaj, to zawartość rejestru ma być ujawniona
i zapewne skopiowana gdzieś dalej, na przykład do innego rejestru albo na
jedno z wejść arytmometru. Podobnie, jeśli decyzją układu sterowania lo-
giczne 1 pojawi się na linii zeruj, to wszystkie pozycje rejestru mają przy-
jąć zawartość równą 0. Wreszcie, jeśli logiczne 1 pojawi się na linii zapisz
– to w każdej pozycji rejestru R powinna zostać zapamiętana logiczna war-
tość odpowiedniego bitu z pewnego zewnętrznego źródła, które na rysunku
oznaczyliśmy przez X. Jest to oczywiście też zespół n bitów, ponumerowa-
nych odpowiednio od x0 do xn – 1. Może nim być np. inny rejestr lub wyjście
z arytmometru.
Jeżeli zarówno zapisz = 0, jak zeruj = 0, to wyjście bramki NOR
(nr 2) ma wartość 1, natomiast wyjście bramki nr 1 (iloczynu) – wartość 0.
Wtedy aktualny stan przerzutnika (czyli q) kopiuje się z powrotem na jego
wejście d poprzez bramki nr 3 i 4. Zgodnie z zasadą przerzutnika typu D
będzie to również następny stan q. W ten sposób przerzutnik podtrzymuje
poprzednio ustawiony własny stan (0 albo 1) o jeden cykl zegara, potem
o jeszcze jeden, jeszcze jeden... – i trwa to dowolnie długo dopóty, dopóki
układ sterowania nie będzie sobie życzył wyzerowania rejestru lub zapisa-
nia nowej zawartości. W ten sposób rejestr pamięta wprowadzony do niego
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Rejestry i liczniki 323

układ zer i jedynek. Jednocześnie, stan przerzutnika można w każdej chwili


odczytać, podając odczytaj = 1. Wtedy wyjście bramki iloczynu nr 5 przy-
bierze taką wartość jak q.
Jeżeli z układu sterowania pojawi się zeruj = 1, to bramka nr 2 (NOR)
wyprodukuje na swym wyjściu logiczne 0. Przerwie to pętlę podtrzymującą
stan przerzutnika, ponieważ wyjście bramki nr 3 (iloczynu) też stanie się
równe 0. Jeżeli jednocześnie nie ma zapisu (zapisz = 0), to na wejściu d
pojawi się 0, czyli w ciągu najbliższego cyklu zegarowego przerzutnik zo-
stanie wyzerowany. Stan q = 0 będzie się dalej podtrzymywać już po ustą-
pieniu sygnału zerowania.
Pojawienie się sygnału zapisu (zapisz = 1) powoduje – podobnie jak
w przypadku zerowania – przerwanie pętli podtrzymania stanu, ale jedno-
cześnie umożliwia przedostanie się na wejście przerzutnika wartości logicz-
nej xi, pochodzącej z zewnętrznego źródła. Rzeczywiście, kiedy zapisz = 1,
to wyjście bramki nr 1 (iloczynu) ma taką samą wartość logiczną, jak xi.
Jeżeli więc xi = 1, to wyjście zarówno bramki nr 1 (iloczynu), jak i bramki
nr 4 (sumy) ma wartość 1. Ponieważ jest to jednocześnie wejście d prze-
rzutnika – przejdzie on w następnym cyklu do stanu q = 1 i w ten sposób
„zapamięta” wartość xi = 1, znów aż do chwili ponownego zerowania lub
zapisywania. Oczywiście, jeżeli xi = 0, to w taki sam sposób zostanie zapa-
miętane q = 0.
Nie będziemy komplikowali tego przykładu. Można jednak przynajm-
niej wyobrazić sobie, jak – wprowadzając dodatkowe linie sterujące i kilka
nowych bramek dla każdej pozycji rejestru – moglibyśmy rozszerzyć reper-
tuar operacji o przesuwanie logiczne, arytmetyczne i cykliczne zawartości
rejestru o jedną pozycję w lewo i w prawo. Takie rejestry przesuwne (ang.
shift register) znajdują zastosowanie w układach arytmometrów: trudno so-
bie wyobrazić bez nich sprzętową realizację operacji mnożenia i dzielenia
czy normalizacji liczb zmiennoprzecinkowych.
Oprócz rejestrów, powszechnie stosowane są też inne typowe ukła-
dy sekwencyjne, mianowicie liczniki. Na rysunku 13.5 pokazano frag-
ment takiego binarnego licznika, zbudowanego z użyciem synchronicznych
przerzutników typu T (doprowadzenie zegara, jak poprzednio, pominię-
to). Licznik ten zlicza cykle zegarowe, tak długo, jak długo na linii zliczaj
jest ustawiona wartość 1. Kiedy linia zliczaj przechodzi do wartości 0 – licz-
nik zatrzymuje się, a z jego stanu można odczytać binarną liczbę (w kodzie
NB), mówiącą, ile cykli naliczył. Ponowne ustawienie zliczaj = 1 powoduje,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

324 13. Układy sekwencyjne

Rys. 13.5. Fragment licznika zbudowanego przy użyciu przerzutników typu T

że licznik kontynuuje zliczanie od ostatniej zapamiętanej wartości, chyba że


wcześniej został wyzerowany.
Na rysunku zmieściły się jedynie dwie sekcje licznika, odpowiadają-
ce dwóm najmniej znaczącym bitom (pozycjom) licznika. Jest to więc licz-
nik dwubitowy, który potrafi liczyć („w kółko”) od binarnego (00) do (11),
czyli w systemie dziesiętnym – od 0 do 3. Jednak zasada jest jasna: żeby
skonstruować licznik dłuższy, n-bitowy, należy przygotować odpowiednią
liczbę kopii pojedynczej sekcji (obwiedzionej przerywaną linią) i połączyć
je w łańcuch, rozbudowując licznik stopniowo w prawą stronę.
Stan n-bitowego licznika – to oczywiście wektor wyjść q jego prze-
rzutników, czyli (qn −1 , qn − 2 , ..., q2 , q1 , q0 ). Należy tylko zwrócić uwagę na
kierunek numerowania pozycji licznika. Na rysunku 13.5 pokazano pozycje
najmniej znaczące (q0 oraz q1) i numer pozycji rośnie w prawo, podczas
gdy w konwencjonalnej liczbie dwójkowej (zgodnie z jej „arabską” naturą)
numery pozycji rosną w lewo, zaś najmniej znacząca pozycja znajduje się
z prawej strony.

Wrzuć monetę... czyli prosty układ sterowania


Pokazaliśmy, w jaki sposób liczniki i rejestry wykonują typowe czynno-
ści: zapamiętywanie, zliczanie, zerowanie itd. O kolejności i czasie trwa-
nia tych działań decyduje odpowiedni układ sterowania, który nadsyła do
danego podzespołu pewne polecenia. Mają one postać odpowiednich war-
tości logicznych (0 albo 1) pojawiających się na liniach sterujących. Jak
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Wrzuć monetę... czyli prosty układ sterowania 325

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.

Rys. 13.6. Otoczenie przykładowego automatu NS

5 Oczywiście, teraz graf z rysunku 10.6 należałoby nieco zmodyfikować, gdyż


pojawią się nowe sygnały służące do komunikacji ze sterownikiem NS, których
wtedy nie uwzględnialiśmy. Zostawimy to czytelnikowi.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

326 13. Układy sekwencyjne

Zadaniem naszego układu (NS) jest tylko zawiadomienie owego GUS,


że ktoś wrzucił monetę. Jeśli tak się stało, nasz układ ma ustawić logiczne 1
na linii sterującej gotowe. Jednocześnie powinien zasłonić szczelinę klapką,
która uniemożliwi wrzucenie następnej monety, aż do chwili, kiedy GUS
zasygnalizuje (pojawieniem się jedynki na linii nast), że jest już gotów do
przyjęcia następnej monety. Kiedy to nastąpi, nasz NS powinien odsunąć
klapkę ze szczeliny i czekać na kolejną monetę (rysunek 13.6).
Załóżmy także, że zamykaniem i otwieraniem klapki zajmuje się od-
dzielny mechanizm (zapewne przekaźnik, włączający elektromagnes lub
silniczek itd.), który zamyka klapkę, kiedy dostanie sygnał klapka = 1,
a otwiera, gdy klapka = 0.
O tym, że wrzucono monetę, sygnalizuje fotokomórka umieszczona
tuż za wlotem szczeliny. Produkuje ona zmienną logiczną foto, której prze-
bieg jest pokazany na rysunku. Kiedy szczelina jest pusta – na fotokomór-
kę pada światło i produkuje ona napięcie wysokie (H), czyli logiczne 1.
Włożenie monety przerywa strumień światła i napięcie staje się L (czyli 0)
na czas przetaczania się monety. Potem wartość foto wraca ponownie do 1.
Na podstawie tego opisu spróbujmy opracować model zachowania na-
szego sterownika. Właśnie zachowania, a nie jego wewnętrznej budowy: na
to jeszcze przyjdzie czas. Uzbroiwszy się w papier i ołówek, po krótkim na-
myśle i po paru nieudanych próbach prawdopodobnie narysujemy nieformal-
ny, roboczy (być może nawet niechlujny) graf, taki jak na rysunku 13.7.

Rys. 13.7. Pierwszy, roboczy szkic modelu zachowania układu NS


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Wrzuć monetę... czyli prosty układ sterowania 327

Rzeczywiście, z opisu wynika, że w zachowaniu układu można wy-


różnić trzy fazy czy też stany. Najpierw, w początkowym stanie (niech się
w skrócie nazywa Nic), nic on nie robi, czekając na pojawienie się monety.
Gdy otrzyma z fotokomórki sygnał o włożeniu monety – przechodzi do dru-
giego stanu (Moneta), w którym oczekuje na przetoczenie się całej monety
przed fotokomórką. Gdy to już nastąpi, układ przechodzi do trzeciego stanu
(Zawiadom), w którym zawiadomi Główny Układ Sterowania, że zapłata
została dokonana (ustawiając gotowe = 1), a jednocześnie zamknie klapkę
zasłaniającą szczelinę. Teraz musi poczekać na sygnał nast, który przyjdzie
od GUS. Kiedy to już nastąpi – powinien przejść z powrotem do stanu po-
czątkowego i tam znów czekać na kolejnego klienta.
Choć wygląda to jedynie na robocze bazgroły – to jest to najciekaw-
sza faza procesu projektowania. Pewnej idei sposobu działania urządzenia,
zawartej w luźnym, słownym opisie zaczęliśmy nadawać formę, ubierając
ją w kształt grafu, z jakimiś mniej czy bardziej koślawymi kółkami, strzał-
kami, napisami i komentarzami. Teraz musimy podążyć tym tropem dalej
i doprowadzić proces formalizacji modelu do końca.
W gruncie rzeczy, zaczęliśmy szkicować automat skończony. Spró-
bujmy więc opisać nasz projekt tak, jak to zrobiliśmy w rozdziale 10: posłu-
gując się pojęciami alfabetu, zbioru stanów, funkcji przejść itd.
Wejściami dla naszego automatu są wartości dwóch zmiennych: foto
oraz nast.. Automat musi być przygotowany na to, że każda z nich może być
równa albo 0, albo 1, tak więc alfabetem wejściowym dla automatu jest zbiór
czterech możliwych par wartości (foto, nast), czyli zbiór A = {(00), (01),
(10), (11)}. Podobnie, alfabetem wyjściowym jest zbiór B = {(00), (01), (10),
(11)}; ale tym razem – są to wartości logiczne par zmiennych (klapka, go-
towe), produkowane na wyjściu automatu. Zgodnie z rysunkiem 13.7, zbiór
stanów automatu to: S = {Nic, Moneta, Zawiadom}.
Rysunek zawiera też pewne pomysły, jak powinny przebiegać przej-
ścia pomiędzy stanami w naszym automacie. Oto, jeżeli automat jest w sta-
nie Nic i na wejściu foto pojawi się 0, to znaczy, że w otworze wrzutowym
pojawiła się moneta. Automat powinien wówczas niezwłocznie przejść do
stanu Czekaj. No dobrze, ale co z drugim elementem wejściowej pary, to
znaczy z sygnałem nast? Odpowiedź jest taka, że w tej sytuacji nie ma on
żadnego znaczenia, może być równy 0 albo 1: ważne jest jedynie to, że
foto = 0.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

328 13. Układy sekwencyjne

Przejście ze stanu Nic do Czekaj powodują więc dwa dwubitowe symbole


alfabetu wejściowego: przejście to dokonuje się zarówno wtedy, gdy (foto,
nast) = (00), jak i wtedy, gdy (foto, nast) = (01). W rezultacie zasadę przej-
ścia między Nic a Czekaj możemy zapisać umownie w następujący sposób:

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 .

Na naszym szkicowym rysunku powinniśmy więc dorysować strzałkę (za-


winiętą w kształt „ucha”) prowadzącą od Nic z powrotem do Nic, i dopisać
przy niej (10), (11) albo (1x).
Zapewniliśmy w ten sposób przy okazji, że automat w stanie Nic
będzie zachowywał się w sposób deterministyczny. Istotnie, dla każdego
z czterech dwubitowych symboli wejściowych po prawej stronie strzałki
znajduje się tylko jeden stan docelowy, tak, że automat nie będzie nigdy
miał wątpliwości co do wyboru następnego, docelowego stanu.
W podobny sposób powinniśmy teraz zanalizować pozostałe stany,
dorysować brakujące przejścia i podopisywać przy nich odpowiednie pary
wartości wejściowych. Sprawimy w ten sposób, że cały automat będzie zu-
pełny i deterministyczny.

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

Wrzuć monetę... czyli prosty układ sterowania 329

Pozostaje jeszcze sprawa zdefiniowania funkcji wyjścia dla naszego


automatu. I tu również nasz pierwszy szkic zawiera pewne wskazówki, ale
są one też niekompletne. Przede wszystkim: czy będziemy wiązać symbole
wyjściowe ze stanami (automat Moore’a), czy też z przejściami (automat
Mealy’ego)? Na rysunku 13.7 zaczęliśmy dopisywać wyjście do stanów, po-
zostańmy więc przy tym pomyśle i konstruujmy nasz automat jako automat
Moore’a.
Dalsze decyzje co do funkcji wyjścia nie są skomplikowane. Na
wyjściu automatu są produkowane pary bitów, będące wartościami logicz-
nymi zmiennych (klapka, gotowe). W stanie Zawiadom automat powinien
zamknąć klapkę (a więc klapka = 1) i jednocześnie wyprodukować sygnał
gotowości (gotowe = 1). Dlatego stanowi Zawiadom należy przypisać wyj-
ście (11). W dwóch pozostałych stanach wyjście powinno mieć postać (00):
klapka otwarta, gotowości nie ma.
Po wykonaniu tych wszystkich czynności i naniesieniu poprawek
otrzymamy uzupełniony graf automatu. Jego rysunkowej wersji nie będzie-
my tu przytaczali, natomiast całkowicie równoważny temu opis automatu
w postaci tabelarycznej jest zamieszczony w tabeli 13.1.

Tab. 13.1. Funkcja przejścia i funkcja wyjścia automatu NS


Funkcja przejścia
Funkcja wyjścia
(foto, nast)
00 01 11 10 klapka gotowe
Nic Moneta Moneta Nic Nic 0 0
Stan
Moneta Moneta Moneta Zawiadom Zawiadom 0 0
aktualny
Zawiadom Zawiadom Nic Nic Zawiadom 1 1

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

330 13. Układy sekwencyjne

Zapisawszy w ten sposób dotychczas podjęte decyzje projektowe, po-


winniśmy teraz popatrzeć, czy automatu nie dałoby się uprościć bez szkody
dla jego działania. Automat abstrakcyjny powstaje zwykle w toku wielokrot-
nych modyfikacji, przez dodawanie nowych stanów i przejść itd. – i może
się zdarzyć, że pewne fragmenty grafu niepotrzebnie dublują inne i można
by je usunąć. Jest więc teraz czas na to, by spróbować przeprowadzić mi-
nimalizację automatu, po to by nie brnąć dalej w realizację niepotrzebnie
skomplikowanej struktury.
Warto o tym pamiętać, ale nasz trójstanowy automat jest wyjątkowo
prosty i raczej zminimalizować się nie da. Możemy więc przejść od razu do
następnej fazy realizacji automatu, a mianowicie skonstruowania automatu
zakodowanego, to znaczy – zapisanego w postaci binarnej.
W przypadku naszego automatu tak się szczęśliwie składa, że jego
wejścia i wyjścia (foto, nast, klapka, gotowe) już są dwójkowo zakodowane.
Każda z tych zmiennych przyjmuje wyłącznie wartość 0 albo 1, zaś warun-
ki zadania dokładnie precyzują ich znaczenie. Spośród elementów figurują-
cych w tabeli 13.1 zakodowania wymagają jedynie stany automatu. Stany są
trzy, więc do ich jednoznacznej identyfikacji niezbędne są kody co najmniej
dwubitowe.
Przyjmijmy, że stanom przypiszemy dwubitowe kody w następujący
sposób:
– stan Nic zakodujemy jako (00),
– stan Moneta – jako (01),
– stan Zawiadom – jako (10).
– czwarta kombinacja dwóch bitów, czyli (11) – niech pozostanie niewyko-
rzystana.
Decyzję co do sposobu zakodowania stanów podjęliśmy tu całkowicie
arbitralnie, wybierając zupełnie bez uzasadnienia jedną z wielu możliwości.
Warto jednak dodać, że w praktyce, zwłaszcza gdy stanów jest znacznie
więcej, sposobowi kodowania stanów poświęca się więcej uwagi. Metoda
ich zakodowania wpływa później na kształt projektowanych w dalszej czę-
ści projektu funkcji boolowskich (funkcji wzbudzeń, o których za chwilę).
Istnieją heurystyczne algorytmy, pozwalające na możliwie optymalny wy-
bór sposobu kodowania stanów dla zadanej tablicy przejść. My jednak po-
zostaniemy przy powyższym, arbitralnym rozwiązaniu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Wrzuć monetę... czyli prosty układ sterowania 331

Posługując się tak ustalonymi kodami stanów zamiast ich dotychcza-


sowymi nazwami możemy teraz przepisać tabelę 13.1 i przerysować graf
automatu, otrzymując zakodowaną binarnie wersję automatu NS, taką jak
na rysunku 13.8 i w tabeli 13.2.

Rys. 13.8. Graf zakodowanego automatu NS

Tab. 13.2. Funkcje przejść i wyjść zakodowanego automatu NS


Funkcja przejścia
Funkcja wyjścia
(foto, nast)
00 01 11 10 klapka gotowe
00 01 01 00 00 0 0
(q1,q0)

01 01 01 10 10 0 0
11 xx xx xx xx x x
10 10 00 00 10 1 1

Zauważmy, że w tabeli funkcji przejść i wyjść automatu pojawił się nowy


wiersz, o kodzie (11), zawierający same znaczki x. To odpowiednik czwar-
tego, niewykorzystanego kodu stanu. W rzeczywistości nigdy się on nie po-
jawi, dlatego moglibyśmy tam bezkarnie wpisać dowolne pary zer i jedynek.
To sytuacja, którą wyżej nazwaliśmy don’t care. Ale czy warto taki wiersz
w ogóle umieszczać w tabeli? Zaraz okaże się, że warto: pozwoli to na waż-
ne ułatwienia w projektowaniu postaci funkcji boolowskich.
Wiemy już dokładnie, co chcemy zbudować, czas zastanowić się – jak
to zrobimy. Musimy poradzić sobie z trzema zadaniami:
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

332 13. Układy sekwencyjne

– 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

Wrzuć monetę... czyli prosty układ sterowania 333

Rys. 13.9. Blokowy schemat układu sekwencyjnego NS

ś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).

Tab. 13.3. Tabele funkcji wzbudzeń d1 oraz d0


Tabela dla d1 Tabela dla d0
(foto, nast) (foto, nast)
00 01 11 10 00 01 11 10
00 0 0 0 0 00 1 1 0 0
Stan (q1, q0)

Stan (q1, q0)

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

334 13. Układy sekwencyjne

a kolejne wiersze (w przypadku obu naszych tablic wzbudzeń byłoby ich


szesnaście) były ułożone po kolei, według arytmetycznej wartości kom-
binacji zer i jedynek w lewej części, odpowiadającej zmiennym wejścio-
wym. Przedstawione natomiast w takiej formie, jak w tabeli 13.3, nazywają
się tablicami lub mapami Karnaugha7 (ang. Karnaugh tables, Karnaugh
maps), od nazwiska Maurice'a Karnaugh z Bell Laboratories, który je za-
proponował w 1953 r.
Tablice Karnaugh są dwuwymiarowe. Zmienne wejściowe (u nas foto,
nast, q0, q1) są podzielone na dwie części, przy czym podział jest zostawio-
ny do zupełnie arbitralnej decyzji projektanta. Jedna grupa zmiennych (tutaj
q1, q0) numeruje wiersze tablicy, druga (foto, nast) – kolumny. W polach
wnętrza tabeli są wpisane wartości (0 lub 1) danej funkcji, które odpowiada-
ją kombinacjom zmiennych wejściowych, właściwych dla tego wiersza i ko-
lumny. Dodatkowo, zastosowano szczególny sposób numerowania wierszy
i kolumn: 00, 01, 11, 10, a nie według zwykłej, arytmetycznej kolejności:
00, 01, 10, 11. Zwróciliśmy na to uwagę już przy omawianiu tabeli przejść
przerzutnika RS, a wyjaśnimy niżej.
Właściwie moglibyśmy zignorować tę dziwną formę i postępować da-
lej tak, jak w rozdziale 12 z konwencjonalnymi tabelkami prawdy. Zgodnie
z omówionymi tam zasadami, powinniśmy teraz napisać formuły boolow-
skie dla d1 oraz d0. Na przykład, wykorzystując postać kanoniczną sumy,
możemy napisać:

d1 = q1 ⋅ q 0 ⋅ foto ⋅ nast + q1 ⋅ q 0 ⋅ foto ⋅ nast + q1 ⋅ q 0 ⋅ foto ⋅ nast + q1 ⋅ q 0 ⋅ foto ⋅ nast ,

d 0 = q1 ⋅ q 0 ⋅ foto ⋅ nast + q1 ⋅ q 0 ⋅ foto ⋅ nast + q1 ⋅ q 0 ⋅ foto ⋅ nast + q1 ⋅ q 0 ⋅ foto ⋅ nast .

Oczywiście, każdy z iloczynów logicznych w powyższych formułach odpo-


wiada jednej spośród jedynek, znajdujących się we wnętrzu odpowiedniej
tabelki. Następną czynnością byłoby upraszczanie obu formuł drogą alge-
braicznych przekształceń: znajdowanie czynników, które można by wycią-
gnąć przed nawias i tak dalej. Zobaczymy, że przy użyciu tablicy Karnaugh
robi się to znacznie łatwiej.

7 Czytane jako „Karno”, z akcentem na „o”.


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Wrzuć monetę... czyli prosty układ sterowania 335

Do kodowania numerów wierszy i kolumn w tablicy Karnaugh uży-


to kodu Greya (od nazwiska Franka Greya, również z Bell Laboratories).
W naszym przypadku do numerowania wierszy i kolumn wystarczy dwubi-
towy kod Greya, ale gdyby kolumn lub wierszy było osiem – zastosowali-
byśmy jego trzybitową wersję (rysunek 13.10)8.

Rys. 13.10. Dwu- i trzybitowy kod Greya

Kod Greya ma tę właściwość, że każde kolejne dwa oznaczenia kodowe


różnią się od siebie zawsze – proszę sprawdzić – tylko na jednej pozycji.
Co więcej, ta własność jest cykliczna: obowiązuje „w kółko”, co wyraźnie
widać na rysunku 13.10. Dzięki temu, jeżeli w tablicy Karnaugh zobaczymy
dwie jedynki, stojące tuż obok siebie (w sąsiednich kolumnach tego same-
go wiersza albo w sąsiednich wierszach tej samej kolumny), to możemy
być pewni, że odpowiadające im kombinacje wartości zmiennych wejścio-
wych różnią się tylko na jednej pozycji. Stwarza to natychmiast możliwość
uproszczenia.
Dla przykładu, w tabeli funkcji d1, w wierszu 01 są takie dwie sąsia-
dujące jedynki: w kolumnach 11 oraz 10. W postaci kanonicznej znajdują się
(między innymi) następujące dwa odpowiadające im elementarne iloczyny:

... q1 ⋅ q 0 ⋅ foto ⋅ nast + q1 ⋅ q 0 ⋅ foto ⋅ nast ...

8 Można oczywiście zbudować kod Greya o dowolnej długości. Dłuższe, n-bitowe


kody Greya znajdują zastosowanie w kilku dziedzinach techniki: w telekomuni-
kacji, przy konwersji analogowo-cyfrowej itd. Jednak w tablicach Karnaugh prak-
tycznie nie stosuje się kodów dłuższych niż trzybitowe.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

336 13. Układy sekwencyjne

Rzeczywiście, różnią się od siebie tylko na jednej pozycji (odpo-


wiadającej zmiennej nast), co pozwala na wyciągnięcie pozostałych trzech
zmiennych przed nawias:

... q1 ⋅ q 0 ⋅ foto ⋅ (nast + nast ) ...

Ale przecież (nast + nast ) = 1, więc zamiast wyżej wymienionych dwóch


iloczynów elementarnych, możemy napisać po prostu:

... q1 ⋅ q 0 ⋅ foto...

Wniosek stąd taki, że jeśli wykryjemy dwie jedynki, sąsiadujące ze sobą


w pionie lub poziomie – to możemy na pewno pozbyć się jednej zmien-
nej w tym fragmencie formuły boolowskiej. Co więcej, jeżeli uda nam się
wykryć w tabeli Karnaugh prostokąt (na przykład kwadracik), wypełniony
czterema jedynkami – to na podobnej zasadzie wyeliminujemy dwie zmien-
ne. Taki kwadracik występuje na przykład w lewym górnym rogu tabelki dla
funkcji wzbudzenia d0. Ponieważ jednocześnie są to jedyne jedynki w tej
tabeli – cała postać kanoniczna dla d0 upraszcza się – proszę to sprawdzić
– do następującej formuły:

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

Wrzuć monetę... czyli prosty układ sterowania 337

cika złożonego z jedynek – to wolno nam to bezkarnie zrobić, a w wyniku


otrzymamy nieco krótszą postać boolowskiej formuły.
Przykład takiej techniki grupowania jedynek jest zilustrowany na ry-
sunku 13.11. Jest na nim powtórzona tabela Karnaugh dla funkcji wzbu-
dzenia d1. W wierszu 11 stały przedtem cztery znaczki x, ponieważ stan
automatu kodowany jako 11 nigdy się w rzeczywistości nie pojawia, a więc
możemy się nim nie przejmować. Wstawienie tam trzech „nieprawdziwych”
jedynek (wpisane w nawiasach) pozwala na skompletowanie dwóch kwa-
dracików, które dają się zapisać w postaci iloczynów logicznych dwóch
zmiennych. Ich interpretacja jest łatwa: na przykład dla kwadratu opisane-
go formułą q0 ⋅ foto charakterystyczne jest to, że dla wszystkich czterech
jego pól q0 = 1 oraz foto = 1 (stąd taka właśnie postać formuły), podczas
gdy pozostałe dwie zmienne nie występują w tej formule, gdyż przyjmują
wartości zarówno 0, jak 1. Podobnie się dzieje dla drugiego kwadratowego
obszaru, zakreślonego przy założeniu, że lewy i prawy skraj tabeli są zle-
pione ze sobą. Oczywiście, nakładanie się tak zaznaczonych obszarów jest
dozwolone.

Rys. 13.11. Przykład grupowania jedynek w tablicy Karnaugh dla d1

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

338 13. Układy sekwencyjne

Wróćmy do naszego projektu sterownika NS. Otrzymaliśmy już obie


funkcje wzbudzeń:

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-

Rys. 13.12. Schemat logiczny sterownika NS


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Wrzuć monetę... czyli prosty układ sterowania 339

wiającego się w momencie włączenia całego urządzenia itd. Pomińmy już


te problemy: w powyższym przykładzie chodziło przede wszystkim o drogę
rozumowania.
W rozdziale tym i poprzednim poznaliśmy najbardziej podstawowe
pojęcia, techniki i sposoby rozumowania, które prowadzą od pomysłu po-
przez jego formalizację – do logicznego projektu układu. Ten wprowadzają-
cy wykład miał jednak swoje nieuchronne i oczywiste ograniczenia.
Zajmowaliśmy się na ogół przykładami, które ograniczały się do bo-
olowskich funkcji dwóch lub trzech zmiennych, choć w przypadku tablic
Karnaugh ich liczbę udałoby się zwiększyć do sześciu. I tablice, i boolowskie
formuły przekształcaliśmy ręcznie, a sieci, które w wyniku otrzymywaliśmy
– składały się z kilku zaledwie bramek. Można je było łatwo narysować
i obejrzeć. To samo dotyczyło grafów czy tablic przejść automatów. I tak
miało być: kilkustanowy graf, dająca się narysować tabela, własnoręczne
przekształcenie prostej funkcji – są nieocenione dla zrozumienia istoty za-
gadnienia i wyrobienia sobie podstawowej intuicji.
Ale projektanci prawdziwych komputerów i innych cyfrowych urzą-
dzeń już od lat czterdziestych XX wieku musieli sobie radzić w prakty-
ce z jakościowo innym rozmiarem problemów: ze znacznie większą liczbą
zmiennych i z sieciami logicznymi, które zawierały najpierw dziesiątki i set-
ki, a potem, po paru dziesięcioleciach – tysiące i miliony bramek i przerzut-
ników. Jak oni sobie z tym poradzili?
Sytuację nieco łagodzi fakt, że wiele układów kombinacyjnych i se-
kwencyjnych można utworzyć przez powielenie pewnych typowych modu-
łów. W ten sposób również i my konstruowaliśmy na przykład wielobitowy
sumator, binarny licznik czy rejestr. Analizowaliśmy działanie i budowę
jednobitowej sekcji takiego układu, a następnie powielaliśmy ją i łączyli
w większą, regularną całość. Z układami sterowania sprawa jest trudniejsza,
jednak i tu dąży się do opracowania możliwie uniwersalnych struktur, które
potem przystosowuje się do wymagań konkretnego projektu.
Dlatego już wcześnie zaczęto opracowywać algorytmy minimalizacji
funkcji boolowskich (ang. Boolean minimization), tak by można było w pro-
cesie projektowania sieci logicznych wykorzystać komputer, a nie polegać
wyłącznie na ręcznej pracy projektanta. Pierwszy szerzej znany algorytm
minimalizacji funkcji boolowskich (Willard Quine i Edward MacCluskey,
początek lat sześćdziesiątych XX w.) odtwarzał w gruncie rzeczy zasadę
postępowania, którą stosowaliśmy, operując tablicami Karnaugh: wyszuki-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

340 13. Układy sekwencyjne

wał możliwości grupowania przyległych jedynek w możliwie duże regular-


ne struktury, opisywane możliwie prostym iloczynem logicznym. Choć był
wolny od ograniczeń spowodowanych właściwościami ludzkiego wzroku
(dwuwymiarowość, ograniczenie rozmiaru tablicy w zasadzie do wymiaru
kartki papieru), jednak był NP-trudny. Wiemy, co to znaczy: czas jego wy-
konywania wykładniczo rósł wraz z liczbą zmiennych, co znacznie ograni-
czało jego użyteczność.
Dalsze dwie dekady prac nad heurystycznymi algorytmami minima-
lizacji oraz nad projektowaniem regularnych struktur bramkowych (wy-
twarzanych jako układy scalone, jak np. układy PLA – Programmed Logic
Arrays czy FPGA – Field Programmed Gate Arrays) przyniosły jednak re-
zultaty. W roku 1984, grupa badaczy z University of California w Berkeley
opracowała heurystyczny algorytm, który nazwano Espresso. Radzi on so-
bie dobrze z projektowaniem sieci o wielu dziesiątkach zmiennych wyjścio-
wych i wielu dziesiątkach zmiennych wejściowych. Algorytm Espresso jest
obecnie wykorzystywany we wszystkich programowych narzędziach wspo-
magających pracę projektantów sieci logicznych.
Ale od projektu logicznego do fizycznego zbudowania działającego
układu czy całego komputera droga jest wciąż jeszcze daleka. Aby system
mógł rzeczywiście działać – synteza logiczna musi znaleźć przedłużenie
w technologii wytwarzania cyfrowych urządzeń. W tej dziedzinie również
dokonał się ogromny postęp. Dzięki niemu zamiast pojedynczych maszyn
kosztujących miliony dolarów – mamy dziś i domowe komputery, i su-
perkomputery w ośrodkach badawczych, i ogólnoświatową sieć, i teleko-
munikację, i telewizję cyfrową, i tomografy komputerowe... Decydują one
w znacznym stopniu o obliczu współczesnej cywilizacji.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

14. Wiek informatyki

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

342 14. Wiek informatyki

Dwadzieścia lat po projekcie Schickarda, w roku 1642, francuski matema-


tyk, fizyk, a także filozof i myśliciel Blaise Pascal (1623–1662), zbudował
mechaniczny kalkulator nazwany Pascaline, który potrafił wykonywać ope-
racje dodawania i odejmowania. Pascal miał wówczas dziewiętnaście lat
i chciał w ten sposób ułatwić pracę ojcu, który był królewskim inspektorem
podatkowym w Rouen. Przez dalsze lata, Pascalowi udało się nawet wyko-
nać kilkadziesiąt sztuk tego urządzenia, a z trudem sprzedać – kilkanaście.
Był to umiarkowany sukces, ale jednak sukces.
Trzydzieści lat później, w 1673 roku, wielki niemiecki filozof i ma-
tematyk Gottfried Leibniz (1646–1716) ulepszył maszynę Pascala tak, że
wykonywała już nie tylko dodawanie i odejmowanie, lecz wszystkie czte-
ry podstawowe działania arytmetyczne. W ciągu następnych dwudziestu lat
udało się wykonać dwa egzemplarze tej maszyny, a trzeci – który przetrwał
do dnia dzisiejszego – ukończono już po śmierci uczonego, w 1720 roku.
Po dalszych stu latach, w roku 1822, brytyjski matematyk Charles
Babbage (1791–1871) podjął jeszcze ambitniejszą próbę zbudowania ma-

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

Praojcowie informatyki 343

szyny różnicowej (ang. difference engine). Miała ona służyć do sporządza-


nia tablic logarytmów i funkcji trygonometrycznych. Wartości tych funk-
cji obliczano poprzez ich przybliżenie za pomocą wielomianów. Maszyna
Babbage’a miała więc być zdolna do wykonywania nie tylko pojedynczych
operacji arytmetycznych (jak wspomniane wyżej wcześniejsze maszyny),
ale ich całych sekwencji, które na obliczenie wartości wielomianu się skła-
dają. Ponieważ ta sama maszyna miała służyć do obliczeń różnych funkcji
– miała być skonstruowana tak, by dało się ją przestawiać z jednego sche-
matu obliczeń na inny.
Według pierwotnego projektu maszyna różnicowa miała się składać
z około 25 000 części. Babbage'owi nigdy się nie udało jej zbudować i uru-
chomić. W tamtych czasach nie wprowadzono jeszcze normalizacji mecha-
nicznych elementów (kół zębatych, połączeń gwintowych, kątowników itp.),
więc każda z części była projektowana i wykonywana oddzielnie. Babbage
opracował drugą, nieco prostszą wersję urządzenia, ale jej realizacja także
okazała się zbyt kosztowna i za trudna jak na możliwości ówczesnej techni-
ki. Zachowały się jednak plany, na podstawie których po przeszło stu pięć-
dziesięciu latach, w końcu lat osiemdziesiątych XX wieku rzeczywiście ją
zbudowano, notabene posługując się przy tym (cóż to za piękny żart histo-
rii!) komputerowo sterowanymi obrabiarkami. Od roku 1991 można ją po-
dziwiać w londyńskim Muzeum Nauk (Science Museum, fotografia 14.2),
a od 2008 r. – jeszcze jedną jej replikę w Muzeum Historii Komputerów
(Computer History Museum) w Mountain View, w sławnej kalifornijskiej
Krzemowej Dolinie.
Niepowodzenie nie zraziło Babbage’a do projektów maszyn liczą-
cych. W roku 1837 rozpoczął pracę nad nową, jeszcze bardziej uniwersal-
ną maszyną analityczną (ang. analytical engine). Rewolucyjnym pomysłem
były niemal dzisiejsze zasady programowania tej maszyny. Zarówno dane,
jak i sekwencje działań miały być przygotowywane w postaci pliku kartono-
wych kart dziurkowanych, które – stopniowo wczytywane – miały sterować
działaniem mechanizmu maszyny. Przewidziano nawet możliwość operacji
warunkowych oraz rozgałęzień i pętli w programie. Było to nowatorskie za-
stosowanie technicznego pomysłu, który wówczas był już od kilkudziesię-
ciu lat znany w przemyśle tkackim. Przy użyciu podobnych dziurkowanych
arkuszy z kartonu lub metalu wykonywano skomplikowane, powtarzalne
wzory tkanin na krosnach, które wynalazł we Francji na początku XIX wie-
ku Joseph Marie Jacquard (stąd tkaniny żakardowe).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

344 14. Wiek informatyki

Fot. 14.2. Maszyna różnicowa Babbage’a. Zdjęcie ze zbioru Science & Society Picture
Library, Wielka Brytania

Nad konstrukcją maszyny analitycznej i zasadami jej programowania


Babbage pracował aż do końca życia, niestety, udało mu się zbudować jedy-
nie mały fragment prototypu. Kilkanaście lat po śmierci uczonego, w roku
1887, specjalnie powołana komisja zdecydowała o przerwaniu prac nad ma-
szyną analityczną. Jednak po dalszych kilkudziesięciu latach, na początku
lat czterdziestych XX wieku okazało się, że teoretyczne idee Babbage’a
były znane kilku specjalistom i stały się inspiracją dla projektów wykony-
wanych już w czasie II wojny światowej. Mieli oni jednak do dyspozycji już
zupełnie inną technologię, jakościowo różną od tamtej, dziewiętnastowiecz-
nej mechaniki. W każdym razie, mimo niepowodzenia technicznej realizacji
swych planów, Charles Babbage jest w środowisku informatyków uważany
za pioniera, za kogoś w rodzaju ojca założyciela całej tej dziedziny.
Z projektami maszyn Babbage’a wiąże się też warta wspomnienia,
niezwykła postać młodej angielskiej arystokratki, Augusty Ady Lovelace
(1815–1852). Była ona córką wielkiego angielskiego poety romantycznego,
lorda Byrona, lecz miała z nim w życiu mało wspólnego, gdyż jej rodzice
żyli w separacji. Mała Ada Byron, jak inne dziewczynki z arystokratycz-
nych rodzin, pobierała nauki w domu, ale – w przeciwieństwie do większo-
ści rówieśniczek – wykazywała wielkie zainteresowanie matematyką i wiel-
kie w tym kierunku zdolności. Jej nauczycielami było kilku znakomitych
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Praojcowie informatyki 345

matematyków tamtych czasów, między innymi Augustus de Morgan, tak,


ten sam, znany nam już z praw de Morgana.
Ada Byron poznała Charlesa Babbage’a przez znajomych, jako sie-
demnastoletnia dziewczyna. Wywarła na nim wrażenie swym intelektem,
a potem korespondowała z nim i okazjonalnie spotykała, interesując się
żywo jego projektami maszyn liczących. Ten kontakt utrzymał się, mimo
że wkrótce, w wieku dwudziestu lat, Ada Byron wyszła za mąż (a więc
jednocześnie zmieniła nazwisko) i w ciągu następnych czterech lat urodziła
troje dzieci.
W roku 1843 Ada Lovelace przełożyła na język angielski obszerny
traktat pewnego włoskiego matematyka o maszynie analitycznej Babbage’a,
opatrując go komentarzami i dopiskami, których objętość przekroczyła
w końcu rozmiar oryginalnego tekstu. Podała tam między innymi dokładny,
opracowany przez siebie opis algorytmu obliczeń liczb Bernoulliego, dzię-
ki czemu dziś uważa się ją za pierwszą w dziejach programistkę. Dokonała
też wielu poprawek i uzupełnień w pomysłach Babbage’a, a niektórzy uwa-
żają nawet, że sam (zapożyczony z krosna Jacquarda) pomysł wykorzysta-
nia kart dziurkowanych jako nośnika danych i programów – należy także
do niej.
Prawie półtora wieku później, dla uhonorowania tego, czego Ada
Lovelace dokonała w ciągu swego niedługiego życia, jej imieniem nazwa-
no język programowania (Ada), który w roku 1980 był wprowadzony jako
standard dla wojskowych projektów informatycznych wykonywanych dla
potrzeb Departamentu Obrony USA. Również Brytyjskie Towarzystwo
Komputerowe (British Computer Society) ufundowało w roku 1998
Lovelace Medal, przyznawany corocznie za wybitny wkład w rozwój in-
formatyki, a sama postać Ady Lovelace stanowi dziś wzór i inspirację dla
wielu stowarzyszeń kobiecych.
Niepowodzenia w realizacji wszystkich tych ambitnych projek-
tów miały źródła w ówczesnej mechanicznej technologii budowy sprzętu.
Ogromna liczba i złożoność detali (przy braku ich normalizacji), złożoność
całej konstrukcji, problemy materiałowe, konieczna precyzja wykonania,
problemy tarcia, smarowania, napędu itd. – wszystko to przekraczało moż-
liwości dziewiętnastowiecznej mechaniki, tak dumnej ze wspaniałych ma-
szyn parowych, statków, kolei i mostów. Ale w drugiej połowie XIX wieku
sytuacja zaczęła się zmieniać, odkąd w przemysłowym i powszechnym uży-
ciu pojawiły się urządzenia elektryczne.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

346 14. Wiek informatyki

I oto w końcu lat osiemdziesiątych XIX wieku, a więc prawie dokładnie


wtedy, kiedy w Anglii przerwano prace nad maszyną analityczną Babbage’a
– w USA inżynier Herman Hollerith (1860–1929) zaprojektował bardzo uda-
ny elektromechaniczny tabulator: maszynę przeznaczoną początkowo do spo-
rządzania tablic i zestawień statystycznych.
Hollerith zbudował swoje urządzenie dla potrzeb powszechnego spisu
ludności, zapowiedzianego na rok 1890 zgodnie z amerykańską konstytucją,
która nakazuje wykonywanie takiego spisu co okrągłe dziesięć lat. Ale w la-
tach osiemdziesiątych XIX wieku liczba mieszkańców USA znacznie wzro-
sła (głównie w wyniku imigracji) i obawiano się, że opracowanie wyników
nowego spisu dotychczasowymi, ręcznymi metodami zajmie lat nawet kil-
kanaście. Hollerith zbudował więc elektromechaniczną maszynę, za pomocą
której dane zebrane na arkuszach spisowych przenoszono (posługując się
odpowiednią klawiaturą) na dziurkowane papierowe karty. Pliki kart były
następnie wprowadzane do tabulatora, który odczytywał zawartość każdej
karty przy użyciu styków elektrycznych (a więc nie mechanicznych bol-
ców, jak w pianoli czy krosnach Jacquarda) i zwiększał zawartość elektro-
mechanicznych liczników, odpowiadających poszczególnym analizowanym
kategoriom statystycznym. Dzięki maszynom Holleritha udało się opraco-
wać wyniki spisu powszechnego z 1890 r. w ciągu zaledwie jednego roku,
podczas gdy opracowywanie rezultatów poprzedniego spisu, z roku 1880,
trwało aż osiem lat.
Osiągnąwszy tak spektakularny sukces, Hollerith założył firmę, pro-
dukującą kolejne modele maszyn już dla potrzeb biznesu. Niedługo potem
kilka firm o podobnym profilu (tabulatory, maszyny księgujące, dziurkar-
ki i sortery kart, maszyny do pisania itp.) połączyło się w jedną korpo-
rację. W roku 1924 przyjęła ona nazwę International Business Machines
Corporation, w skrócie IBM Corp. Pod tą nazwą jest znana każdemu użyt-
kownikowi komputerów, jako jedna z firm dominujących na rynku również
elektronicznych komputerów i oprogramowania, nieprzerwanie od końca lat
czterdziestych XX wieku aż do dnia dzisiejszego.
Elektromechaniczne dziesiętne tabulatory, sortery i maszyny księgują-
ce – były w powszechnym użyciu w biurach biznesu, administracji i maso-
wych usług, w urzędach statystycznych itd. na całym świecie, aż do końca lat
siedemdziesiątych XX wieku, a więc przez długi czas równolegle z binarny-
mi komputerami z pamiętanym programem, o organizacji opartej na idei von
Neumanna. Kres ich historii położyły dopiero programowane minikompute-
ry, a potem komputery osobiste. Również karty perforowane były przez dłu-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Potrzeby obliczeniowe okresu II wojny światowej 347

gie lata podstawowym nośnikiem do wprowadzania danych i programów do


tychże komputerów. Dopiero w latach siedemdziesiątych XX wieku wypartły
je dyskietki (ang. floppy disc). Te z kolei również już przeszły do historii,
zastąpione przez płytki CD, DVD i pamięci typu flash memory.

Potrzeby obliczeniowe okresu II wojny światowej


Kiedy na początku lat czterdziestych XX wieku potrzeby techniki wojennej
wywołały w USA i Wielkiej Brytanii nową falę zapotrzebowania na skom-
plikowane obliczenia numeryczne i kryptograficzne – inżynierowie mieli
już do dyspozycji nową technologię: elektronikę. Była ona znana już od po-
czątków XX wieku i opierała się wówczas na zastosowaniu elektronowych
lamp próżniowych. Dzięki niej, w latach trzydziestych XX wieku lampowe
wzmacniacze, nadajniki i odbiorniki radiowe (a potem także telewizyjne)
i inne urządzenia elektroniczne produkowano już na skalę masową.
Oprócz innych zalet (takich, jak np. szybkość działania, niezależność
od tarcia, bezwładności) elektronika miała nad mechaniką również tę prze-
wagą, że współpracujące ze sobą podzespoły elektroniczne mogą być łączo-
ne przewodami o dowolnej (no, prawie dowolnej) długości, podczas gdy
współpracujące podzespoły mechaniczne muszą się ze sobą fizycznie sty-
kać, połączone dźwigniami, cięgnami, przekładniami zębatymi itd. Dzięki
temu rozbudowa układu elektronicznego o nowe funkcje, dodawanie no-
wych podzespołów itd. stwarza mniej technicznych trudności niż w przy-
padku urządzeń mechanicznych, w których komplikuje to natychmiast fi-
zyczną, przestrzenną strukturę mechanizmu.
W tej sytuacji, w latach II Wojny Światowej (a więc na początku lat
czterdziestych XX wieku) dość naturalnym pomysłem było zbudowanie (na
potrzeby badań nad nowymi systemami uzbrojenia, nad prognozowaniem
pogody dla lotnictwa i marynarki itd.) czegoś w rodzaju wielkiej maszyny
księgującej, wciąż dziesiętnej, ale elektronicznej, a więc szybszej i o znacz-
nie rozbudowanych funkcjach. Taki charakter miała maszyna ENIAC
(Electronic Numerical Integrator And Computer), której budowę rozpoczęto
w 1943 roku w USA, w University of Pennsylvania, na zlecenie amerykań-
skiego wojskowego Laboratorium Badań Balistycznych (Ballistic Research
Laboratory). Kompletną, zbudowaną maszynę odtajniono w roku 1946,
a więc niedługo po zakończeniu II wojny światowej. Jej pokazanie opinii
publicznej wywarło ogromne wrażenie. ENIAC został natychmiast okrzyk-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

348 14. Wiek informatyki

nięty cudem dwudziestowiecznej techniki i gigantycznym mózgiem elektro-


nowym.
Nie była to tylko dziennikarska przesada. Budowa ENIAC-a była
rzeczywiście ogromnym przedsięwzięciem inżynieryjnym. Zespół konstruk-
torów ENIAC-a, pod kierownictwem Johna Mauchly’ego i Johna Adama
Prespera Eckerta, zbudował urządzenie zawierające blisko 17 500 lamp
elektronowych, 1500 przekaźników, 7200 diod krystalicznych oraz mnó-
stwo innych elementów elektronicznych, jak oporniki (ok. 70 000 sztuk)
czy kondensatory (ok. 10 000 sztuk). Połączenia między nimi wymagały
kilkuset kilometrów elektrycznych przewodów i wykonania ręcznie około
pięciu milionów punktów lutowania. Można sobie wyobrazić, jakie wraże-
nie wywierały wówczas te liczby na kimś, kto do tej pory uważał swój kil-
kulampowy odbiornik radiowy za cud elektroniki.
Urządzeniami wejścia i wyjścia ENIAC-a były – jak się można do-
myślić – czytnik i perforator kart dziurkowanych produkcji IBM. Wyniki
otrzymywano w postaci pliku wydziurkowanych kart, ale można było wy-
drukować je na papierze przy użyciu oddzielnej, standardowej IBM-owskiej
maszyny księgującej.
ENIAC ważył około 27 ton i pobierał moc elektryczną równą 150 kW.
Łatwo policzyć, że wystarczyłaby ona do zaświecenia 1500 sztuk stuwatowych
żarówek (albo 2500 sztuk – sześćdziesięciowatowych), a więc do oświetlenia
niemałego miasteczka. Kosztował pięćset tysięcy ówczesnych dolarów, co od-
powiada mniej więcej 6–7 milionom dolarów dziś. Ale w zamian – oferował
oszałamiającą na tamte czasy moc obliczeniową: w ciągu jednej sekundy po-
trafił wykonać 5000 dodawań i odejmowań, albo prawie 400 mnożeń, albo 35
dzieleń dziesięciocyfrowych liczb dziesiętnych.
Niestety, gorzej było z przygotowaniem ENIAC-a do tak szybkie-
go działania. Maszyna była zewnętrznie programowana, podobnie jak jej
przodkowie, wywodzący się z maszyn księgujących. Jej zaprogramowanie,
a więc ustalenie właściwej kolejności działań, polegało na ustawieniu („na
sztywno”) setek przełączników, pokręteł i wtyczek. Przygotowanie szcze-
gółowego wykazu takich ustawień, a następnie fizyczne ich wykonanie
i sprawdzenie – zajmowały całe tygodnie pracy. Niemniej, przy sukcesyw-
nym wprowadzaniu licznych ulepszeń i modyfikacji, ENIAC skutecznie
pełnił służbę w Laboratorium Badań Balistycznych aż do 1955 roku.
Inaczej rozwiązano sprawę programowania w innym projekcie, zwa-
nym Mark I. Był to również wielki dziesiętny kalkulator, zaprojektowany
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Potrzeby obliczeniowe okresu II wojny światowej 349

w tym samym czasie w USA, w Harvard University, z użyciem elektrome-


chanicznych (a więc jeszcze nie elektronicznych) liczników, połączonych
systemem przekładni i sprzęgieł. Liczbowe dane wejściowe ustawiano tu
ręcznie: za pomocą odpowiednich pokręteł i przełączników można było
ustawić („zapamiętać”) do 72 liczb dziesiętnych o długości 23 cyfr każda.
Programowanie maszyny polegało natomiast na przygotowaniu specjalnej
24-kanałowej perforowanej taśmy, która była stopniowo wczytywana pod-
czas pracy maszyny i sterowała jej działaniem. Nie przewidziano wpraw-
dzie rozgałęzień warunkowych, ale taśmę można było skleić „w kółko”
i wykonywać w ten sposób w programie pętlę, jak widać – w bardzo do-
słownym znaczeniu. Maszynę fizycznie wykonano w warsztatach doświad-
czalnych IBM i w 1944 roku przekazano Uniwersytetowi Harvarda, gdzie
służyła obliczeniom numerycznym dla amerykańskiej marynarki wojennej.
Techniczne dane tej maszyny również robiły wrażenie: waga 4,5 tony,
napędzający mechanizmy silnik o mocy 4 kW, 800 kilometrów przewodów,
3 miliony połączeń lutowanych... Jednak przestarzała już wówczas technolo-
gia mechaniczna powodowała, że maszyna Mark I była znacznie wolniejsza
niż ENIAC: trzy dodawania lub odejmowania na sekundę, jedno mnożenie
trwało 6 sekund, a dzielenie – ponad 15. Główny projektant maszyny, Howard
Aiken, zbudował później kilka nowych wersji urządzenia (Mark II, III i IV),
stopniowo przechodząc od systemu dziesiętnego do dwójkowego, wprowa-
dzając elektronikę zamiast podzespołów elektromechanicznych i zastępując
zewnętrzne programowanie z użyciem perforowanej taśmy – przez program
pamiętany w wewnętrznej pamięci maszyny1.
ENIAC i Mark I nie są właściwie przodkami, a jedynie starszymi ku-
zynami współczesnych komputerów. Kończą one bezpotomnie, jak wielkie,
samotne dinozaury, ród dziesiętnych, zewnętrznie programowanych uniwer-
salnych maszyn liczących. Ale dobrze odegrały one swą rolę w historii. Na
ich przykładzie elektronika potwierdziła swoją przewagę nad mechaniką,
a trudności z programowaniem ENIAC-a doprowadziły von Neumanna do
sformułowania pomysłu maszyny z pamiętanym programem. Jednocześnie,

1 Zachował jedynie zasadę, że dane i program są przechowywane w oddzielnych


blokach pamięci. Dla podkreślenia oryginalności pomysłu, Aiken nazwał to roz-
wiązanie architekturą harwardzką komputera, w odróżnieniu od architektury von
Neumanna. Różnica nie jest jednak tak bardzo istotna, a idea von Neumanna była
ogólniejsza.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

350 14. Wiek informatyki

koncepcje Shannona i Turinga skłoniły specjalistów, by oderwali wreszcie


wzrok od systemu dziesiętnego i czterech działań arytmetycznych, a zwró-
cili uwagę na znacznie bardziej uniwersalne zastosowania komputerów, ob-
liczenia nienumeryczne, system dwójkowy, logikę i algebrę Boole’a.
W tej sytuacji, już w 1944 roku, a więc na długo przed ukończeniem
budowy ENIAC-a, sami jego konstruktorzy: Eckert i Mauchly, zasugerowali
zbudowanie dla potrzeb tegoż Laboratorium Badań Balistycznych nowej,
ale już binarnej maszyny EDVAC (Electronic Discrete Variable Automatic
Computer). Przy poparciu ze strony von Neumanna, w latach 1946–1949
maszynę tę rzeczywiście zbudowano. Wkrótce potem Eckert i Mauchly za-
łożyli własną firmę i przystąpili do projektowania komputerów UNIVAC.
Koncern IBM, potentat na rynku maszyn biurowych na karty dziurkowane,
także przystąpił do wyścigu w dziedzinie elektronicznych komputerów, już
nie dla potrzeb wojskowych, ale dla prywatnego biznesu. Podobne projek-
ty ruszyły i w Europie, na przykład w Wielkiej Brytanii, w uniwersytecie
w Manchesterze, gdzie pracował Alan Turing, czy w Szwajcarii, gdzie swo-
ją firmę otworzył wspomniany już wcześniej Konrad Zuse. I tak rozpoczęła
się nowa gałąź drzewa genealogicznego komputerów z pamiętanym progra-
mem, ta, z której owoców korzystamy teraz.

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

Bariera niezawodnościowa 351

towania i mechaniczne styki: lamp z podstawkami, pakietów z gniazdami


itd. Nagrzewanie się maszyny podczas pracy, drgania mechaniczne, a nawet
przeciągi i trzaskanie drzwiami powodowały, że któryś z setek tysięcy sty-
ków rozłączał się, któreś z lutowań okazywało się niedokładne i cały sys-
tem przestawał poprawnie działać, tak jak łańcuch, do którego przerwania
wystarczy pęknięcie jednego z ogniw. Każde ogniwo z osobna może być
nawet bardzo niezawodne, ale na gruncie elementarnego rachunku praw-
dopodobieństwa daje się łatwo pokazać, że jeśli jest ich milion, to prawdo-
podobieństwo, iż przez pewien czas będą sprawne wszystkie jednocześnie
– jest w rezultacie niewielkie. Średni międzyawaryjny czas maszyny by-
wał początkowo rzędu kilkunastu czy kilkudziesięciu minut, a nieprzerwane
wielogodzinne obliczenia były ewenementem.
Opisane zjawiska można było opanować, ale jedynie do pewnego
stopnia. Zastosowanie bardziej długowiecznych typów lamp, naprawienie
wszystkich niepewnych połączeń, które ujawniły się w pierwszych miesią-
cach pracy danego egzemplarza maszyny, okresowa, profilaktyczna wymia-
na lamp na nowe, ulokowanie komputera w specjalnym klimatyzowanym
pomieszczeniu, wreszcie ciągła, wielodniowa praca bez włączania i wyłą-
czania (a więc bez cieplnych naprężeń spowodowanych nagrzewaniem się
i stygnięciem maszyny) – pozwalały osiągnąć stan, w którym komputer pra-
cował bez awarii nawet przez kilka dni. To z jednej strony dużo, z drugiej
– mało: zniechęcało to z pewnością do prac nad bardziej wyrafinowanym
oprogramowaniem i bardziej złożonymi algorytmami, które miały małą
szansę wykonać się w praktyce od początku do końca.
Sytuacja uległa poprawie na początku lat pięćdziesiątych XX wieku,
po opanowaniu masowej produkcji tranzystorów i innych przyrządów pół-
przewodnikowych (ang. transistors, semiconductor devices), które szybko
zastąpiły w większości zastosowań zasłużone dla elektroniki lampy próżnio-
we. Był to wielki przełom, a za odkrycie zjawisk fizycznych umożliwiają-
cych budowanie przyrządów półprzewodnikowych William Shockley, John
Bardeen i Walter Battain z Bell Laboratories otrzymali w roku 1956 Nagrodę
Nobla w dziedzinie fizyki.
Tranzystory były od lamp znacznie mniejsze, bardziej niezawodne,
a przede wszystkim – wymagały wielokrotnie mniejszej mocy zasilania. Na
ulicach i w samochodach zaczęły się pojawiać przenośne tranzystorowe ra-
dioodbiorniki, do których zasilania wystarczyło kilka małych baterii lub sa-
mochodowy akumulator. Ta rewolucja wpłynęła oczywiście również na elek-
tronikę przemysłową i technologię budowy komputerów. Budowane z uży-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

352 14. Wiek informatyki

ciem tranzystorów komputery drugiej generacji były też fizycznie znacznie


mniejsze i pobierały mniej energii, choć nie było wciąż jeszcze mowy o tym,
by mogły być przenośne i dostępne dla prywatnego użytkownika.
Co gorsza, wprawdzie ich niezawodność sporo się poprawiła, ale
główne źródło potencjalnych awarii nie znikło. W końcu lat pięćdziesią-
tych XX wieku elektroniczne układy tranzystorowe były budowane nadal
z oddzielnych elementów: tranzystorów i diod, oporników, kondensatorów,
cewek indukcyjnych. Nadal musiały być one łączone przewodami lub prze-
wodzącymi ścieżkami naniesionymi na plastikową płytkę (tzw. obwody
drukowane, ang. printed circuits). W całym komputerze wciąż potrzebne
były setki tysięcy punktów lutowania i tysiące styków, za pomocą których
osadzano poszczególne płytki w panelach maszyny. Czas międzyawaryjny
znacznie się wydłużył, ale komputer był nadal w zasadzie przywiązany do
specjalnie wyposażonego pomieszczenia, izolującego go od mechanicznych
drgań i skoków temperatury.
Komputer drugiej generacji był więc w stanie zaspokoić ówczesne
potrzeby obliczeniowe w laboratorium badawczym, wielkim banku czy
urzędzie, ale nie można go było umieścić w hali produkcyjnej czy zwy-
kłym pokoju biurowym. O prywatnym domu – choćby ze względu na cenę
– nawet nikt nie myślał. Co gorsza, nie można go było zainstalować w sa-
molocie, czołgu lub pocisku rakietowym. Nie trzeba dodawać, jak bardzo to
martwiło wojskowych specjalistów, którzy widząc, jakie sukcesy przynio-
sły finansowane przez nich wcześniej projekty – nabrali ochoty do tego, by
wyposażać również urządzenia wojskowe w coraz bardziej zaawansowaną
elektronikę i coraz bardziej złożone cyfrowe układy sterowania.

Bardzo dobry, ale bardzo drogi pomysł


Pojawił się wówczas pewien technologiczny pomysł, który – choć bardzo
trudny w realizacji – stwarzał pewne nadzieje na radykalną poprawę nieza-
wodności. Mówiono o nim od kilku lat, ale dopiero w 1958 roku Jack Kilby
(z firmy Texas Instruments) i dosłownie kilka miesięcy później Robert
Noyce (z firmy Fairchild Semiconductor) niezależnie od siebie pokazali, że
jest on technicznie wykonalny. Pomysł ten jest wart zrozumienia, ponieważ
jego skuteczna realizacja doprowadziła w końcu do nowej rewolucji, i to nie
tylko w elektronice.
Oto, żeby wyprodukować tranzystor (co już wtedy umiano robić),
trzeba w płytce bardzo czystego kryształu (na przykład germanu lub krzemu)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Bardzo dobry, ale bardzo drogi pomysł 353

umieścić odpowiednie domieszki innych pierwiastków, tak by utworzyły się


mikroskopijne obszary o różnych ładunkach elektrycznych. Dzięki odpo-
wiedniemu rozkładowi tych ładunków, prąd może płynąć przez ten frag-
ment kryształu tylko w jednym kierunku (stąd półprzewodnictwo), a jego
natężenie można kontrolować z zewnątrz. Na tym – w największym skró-
cie – podlegało uhonorowane Nagrodą Nobla odkrycie Williama Shockleya
i jego współpracowników. Techniczna realizacja tej idei wymagała opano-
wania niesłychanie subtelnych operacji na fragmentach kryształu, których
rozmiary mierzy się w tysięcznych częściach milimetra, w warunkach czy-
stości i sterylności, o jakich dotąd się nawet nie śniło nie tylko w technice,
ale nawet w medycynie. Ale to się udało i tranzystory były już wówczas
produkowane masowo.
Pomysł Kilby’ego i Noyce’a szedł konsekwentnie dalej. Typowy po-
jedynczy układ elektroniczny, niezależnie od tego, czy był to analogowy
wzmacniacz, cyfrowy przerzutnik czy bramka logiczna – składał się z kilku
tranzystorów, do których końcówek łączone były później, zgodnie z elek-
trycznym schematem układu, inne elementy: diody, oporniki, kondensatory.
A gdyby tak tych kilka tranzystorów i diod wykonać od razu obok siebie,
w tej samej krystalicznej płytce? I gdyby następnie przykryć je warstwą
odpowiednio dobranego izolującego materiału, na której by się umieściło
(może nadrukowało? może napyliło?) miniaturowe paseczki czy wężyki peł-
niące funkcję oporników, kondensatorów oraz metalizowanych, łączących je
ścieżek?
Może takich warstw dałoby się wykonać kilka? Przez odpowiednio
zaplanowane mikroskopijne otwory, napełniane przewodzącym materiałem,
elementy z różnych warstw mogłyby się łączyć ze sobą i z leżącymi pod
spodem tranzystorami, tak jak to przewiduje elektryczny schemat obwodu.
Złożony układ elektroniczny wychodziłby wtedy z procesu technologicznego
od razu cały, scalony, nie tylko ze wszystkimi potrzebnymi elementami, ale
już z gotowymi połączeniami między nimi. Pozwoliłoby to na znaczną mi-
niaturyzację układu, ale przede wszystkim radykalnie zmniejszyłoby liczbę
punktów połączeń, które do tej pory wykonywano metodą lutowania. To one
były przecież głównym źródłem kłopotów z niezawodnością systemu.
Łatwo jest się domyślić, że ta idea – to początek układów scalonych
(ang. integrated circuits, w skrócie IC) i mikroelektroniki (ang. microelectro-
nics). Trudniej jest sobie wyobrazić, jak wielkim wyzwaniem była praktycz-
na realizacja tej idei: dobór odpowiednich materiałów, opracowanie techniki
nanoszenia warstw o mikronowej grubości, wreszcie zbudowanie urządzeń,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

354 14. Wiek informatyki

które umożliwiłyby wytwarzanie struktur tak małych, że można je obejrzeć


tylko posługując się mikroskopem elektronowym. Kilby i Noyce udowod-
nili jednak doświadczalnie, że jest to możliwe, a Jack Kilby za swój wkład
w opracowanie technologii układów scalonych otrzymał później, w roku
2000, Nagrodę Nobla.
Niemniej, od laboratoryjnego prototypu do masowej produkcji droga
była jeszcze daleka. Po pierwsze, trzeba było dobrać odpowiednie materiały
i opanować ich produkcję na skalę większą, niż to było potrzebne do eks-
perymentów w laboratorium. Po drugie, seryjna produkcja samych układów
scalonych musi być w znacznym stopniu zautomatyzowana. Trzeba zapro-
jektować i zbudować technologiczne urządzenia, które zapewnią powtarzal-
ność produkowanych układów. Trzeba opracować cały ciąg technologiczny,
od projektowania poszczególnych warstw, poprzez ich fizyczne wykonanie
aż do również zautomatyzowanego, końcowego testowania, które wyeli-
minuje nieudane, wadliwe egzemplarze, których początkowo było zresztą
znacznie więcej niż nadających się do użycia. Zbudowanie i uruchomie-
nie takiego ciągu technologicznego wiąże się z ogromnymi kosztami, które
trzeba ponieść, zanim zacznie się osiągać jakiekolwiek korzyści.
Ocenia się, że dziś, na początku XXI wieku, ktoś, kto chciałby sobie
wybudować własną fabryczkę układów scalonych – musiałby zainwestować
około jednego miliarda dolarów. Potrafimy to oszacować teraz, gdy tech-
nologia jest już znana. Jednak wtedy, przy końcu lat pięćdziesiątych XX
wieku, technologia produkcji układów scalonych dopiero powstawała i nie-
wiadomych było wiele. Zarówno koszty, jak i ewentualne przyszłe korzyści
były trudne do przewidzenia, więc angażowanie się w produkcję układów
scalonych wiązało się ze sporym ryzykiem.
I znów, podobnie jak w latach II wojny światowej, w sukurs przy-
szły projekty rządowe. Przez pierwsze kilka lat (1960–1963) układy scalone
produkowano wyłącznie na potrzeby dwóch amerykańskich programów rzą-
dowych: Apollo (kosmiczne loty załogowe) oraz Minuteman (pociski rakie-
towe). Z funduszy obu tych programów sfinansowano więc też początkowy,
najtrudniejszy etap rozwoju nowej technologii.
Nie bez wpływu na to była globalna sytuacja polityczna owego prze-
łomu lat pięćdziesiątych i sześćdziesiątych XX wieku. Bez jej przypomnie-
nia i uświadomienia sobie nie zrozumiemy ani dalszej historii technologii
sprzętu komputerowego, ani ogólniejszych, długookresowych zjawisk, które
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Powojenne problemy globalnej polityki 355

rozpoczęły się już wtedy, a w konsekwencji ukształtowały także naszą dzi-


siejszą rzeczywistość.

Powojenne problemy globalnej polityki


Wówczas, w końcu lat pięćdziesiątych XX wieku, od zakończenia II woj-
ny światowej upłynęło zaledwie kilkanaście lat. Zwycięscy alianci, którzy
współpracowali ze sobą w okresie wojny, zdążyli się już podzielić na dwa
wrogie obozy, oddzielone – jak mówiono – żelazną kurtyną. Oba przewo-
dzące im mocarstwa, zarówno Stany Zjednoczone, jak i Związek Radziecki,
dysponowały arsenałami broni jądrowej, wystarczającymi do zniszczenia
nie tylko przeciwnika, ale i całego świata. Uformowały się dwa wrogie soju-
sze wojskowe: z jednej strony – NATO, z drugiej – Układ Warszawski. Nie
tylko politycy, lecz również zwykli ludzie po obu stronach żelaznej kurtyny
odczuwali na co dzień zagrożenie wojną i atomową zagładą. Ten stan na-
pięcia, nazywany zimną wojną, utrzymywał się (choć z różnym natężeniem)
jeszcze długo, do początku lat dziewięćdziesiątych.
Obawy krajów zachodnich nie były pozbawione podstaw. Podczas
gdy dawne kolonialne imperia (brytyjskie, francuskie, holenderskie, bel-
gijskie...) rozpadały się – system komunistyczny stale i wyraźnie rozsze-
rzał swoje wpływy. W wyniku II wojny światowej Związek Radziecki po-
większył strefę wpływów w Europie o kilkanaście krajów (w tym również
Polskę), tworząc w ten sposób obóz socjalistyczny. W roku 1949 do obozu
tego dołączyły Chiny, kraj jeszcze wówczas gospodarczo słaby, ale o ogrom-
nym potencjale i liczący blisko miliard ludności.
Niedługo potem ochotnicy chińscy, a także radzieccy doradcy i ra-
dziecki sprzęt wojskowy wsparli północnokoreańskie wojska w próbie roz-
szerzenia komunizmu na cały Półwysep Koreański. Wybuchła krwawa woj-
na koreańska (1950–53) między siłami komunistycznymi a wojskami ONZ
(w praktyce – przede wszystkim amerykańskimi). Trwa ona formalnie do
dziś (zawieszona jedynie rozejmem), a północnokoreańskie państwo pozo-
staje skansenem komunizmu. Zaraz potem (w 1954 r.), komuniści wietnam-
scy wyparli kolonialne oddziały francuskie ze swego kraju. Doprowadziło
to do powstania dwóch państw wietnamskich, które później ponownie star-
ły się ze sobą i w latach sześćdziesiątych wciągnęły Stany Zjednoczone
w nową (ostatecznie przez USA przegraną) wojnę wietnamską.
W styczniu 1959 roku lewicowi rewolucjoniści przejęli władzę na
Kubie. Ich brawura i elokwencja ich brodatego przywódcy, Fidela Castro,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

356 14. Wiek informatyki

zjednały im wtedy sympatię wielu młodych ludzi w wielu krajach. Ale


znaczyło to, że system komunistyczny utworzył przyczółek już na zachod-
niej półkuli, i to tuż obok granic Stanów Zjednoczonych. Wkrótce okaza-
ło się, że Związek Radziecki nie tylko pomaga Kubie budować socjalizm,
ale również potajemnie instaluje na jej terenie swoje rakiety balistyczne.
Doprowadziło to w 1962 roku do kryzysu kubańskiego, kiedy to dosłownie
kwadranse dzieliły świat od bezpośredniego, wojskowego starcia między
USA i Związkiem Radzieckim. Sytuacja międzynarodowa była więc bar-
dzo napięta.
Tej rywalizacji politycznej i militarnej towarzyszyła oczywiście walka
na polu ideologii i prestiżu. Przywódcy radzieccy i chińscy całkiem otwar-
cie głosili, że ich celem jest unicestwienie znienawidzonego kapitalizmu
i rozszerzenie systemu komunistycznego na cały świat. Co gorsza, wyko-
rzystując rzeczywiste wady zachodniego systemu i mistrzowsko posługu-
jąc się propagandą oraz dezinformacją – zdołali przekonać do swych racji
wielu zwolenników w krajach zachodnich. Wielu nie tylko bardzo młodym
ludziom, ale także działaczom związkowym i lewicowym intelektualistom
w USA i w Europie wydawało się, że system komunistyczny rzeczywiście
oferuje sprawiedliwość społeczną, braterstwo i dobrobyt, a jego zwycięstwo
jest – w historycznej skali – nieuchronne. Partie komunistyczne rosły w siłę,
zwłaszcza we Francji i we Włoszech, gdzie wydawało się nawet, że mogą
sięgnąć po władzę w wyniku demokratycznych wyborów.

Wielki program na przełomowe lata sześćdziesiąte


W październiku 1957 roku świat obiegła sensacyjna wiadomość: Związek
Radziecki wprowadził na okołoziemską orbitę pierwszego sztucznego sate-
litę Ziemi, Sputnika, a wkrótce potem drugiego, tego z suczką Łajką na po-
kładzie. Wprawdzie niewiele później Amerykanie również umieścili swoje
satelity w przestrzeni kosmicznej, ale jednak to nie oni byli pierwsi. Dla nich
był to straszny szok i upokorzenie: to znak, że wschodni przeciwnik już wy-
przedza Stany Zjednoczone w dziedzinie najnowocześniejszych technologii.
Amerykanie intensyfikują prace nad programem kosmicznych lotów
załogowych (program Apollo), ale w kwietniu 1961 roku otrzymują kolej-
ny upokarzający cios: to znów Rosjanin, major Jurij Gagarin, jako pierw-
szy człowiek w historii odbywa w przestrzeni kosmicznej lot wokół Ziemi.
Czyżby rację miał radziecki przywódca, Nikita Siergiejewicz Chruszczow,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Wielki program na przełomowe lata sześćdziesiąte 357

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

358 14. Wiek informatyki

a następnie powróciła na Ziemię. Szkoda tylko, że sam prezydent Kennedy


nie mógł być świadkiem tego wydarzenia: wiemy, że jeszcze w listopadzie
1963 roku zginął w Dallas od kuli zamachowca.
Amerykanie odzyskali pierwszeństwo w wyścigu kosmicznym i od-
nieśli prestiżowy sukces, ale prawdziwe – choć nie tak spektakularne – ko-
rzyści z naukowego i technologicznego przyspieszenia dokonanego w la-
tach sześćdziesiątych ujawniły się później nie na Księżycu, ale tu, na Ziemi.
Oczywiście, w pierwszej kolejności skorzystały na nim projekty wojsko-
we, związane z równolegle rozgrywającym się wyścigiem zbrojeń. Jednak
niedługo potem pojawiły się również cywilne zastosowania opracowanych
wtedy technologii, urządzeń i materiałów. Wystarczy wspomnieć choćby
pierwsze satelity telekomunikacyjne Telstar, dzięki którym cywilni opera-
torzy telekomunikacyjni z USA, Wielkiej Brytanii i Francji zorganizowali
– przy udziale NASA – transatlantycki system łączności. Podobnie działo
się w wielu innych dziedzinach gospodarki, w medycynie, lotnictwie itd.

Komputery lat sześćdziesiątych


Pozostańmy przy tej dziedzinie, która interesuje nas najbardziej. Proste porów-
nanie dat pokazuje, że początek produkcji układów scalonych dokładnie zbiega
się w czasie z opisywanymi wyżej wydarzeniami. W tej sytuacji nie może dzi-
wić, że pierwsze układy scalone były produkowane wyłącznie na potrzeby pro-
gramów Apollo i Minuteman. Były tam niezbędne nie tylko w układach stero-
wania, ale również w systemach pomiarowych, diagnostycznych i w sprzęcie
łączności. Bez nich nie do pomyślenia było budowanie niezawodnych urzą-
dzeń elektronicznych, zdolnych do pracy na pokładzie rakiety, w warunkach
występujących tam wstrząsów, przyspieszeń i zmian temperatury.
Zapotrzebowanie na układy scalone (zwłaszcza ze strony programu
rakiet Minuteman) wymusiło opracowanie technologii prawdziwie masowej
produkcji tych układów, dzięki czemu ich cena szybko malała. Wystarczy
wspomnieć, że w ciągu pierwszych trzech lat (1960–1963) koszt wytwo-
rzenia jednego układu scalonego spadł czterdziestokrotnie: z astronomicznej
sumy 1000 dolarów do 25 dolarów za sztukę, a po dalszych trzech latach,
w roku 1966 wynosił już średnio tylko 3 dolary. Wkrótce zdolności pro-
dukcyjne przekroczyły zapotrzebowanie ze strony samych tylko programów
rządowych i układy scalone stały się dostępne również dla cywilnych pro-
jektów badawczych i komercyjnych.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Komputery lat sześćdziesiątych 359

Dzięki nieustannym ulepszeniom technologii, już w połowie lat sześć-


dziesiątych umiano wytwarzać układy scalone, zawierające kilkanaście tran-
zystorów wraz z towarzyszącymi im innymi elementami elektronicznymi.
W przypadku układów cyfrowych2 pozwalało to na zmieszczenie w jednej
scalonej kostce (rzeczywiście o wymiarach zbliżonych do kostki do gry) kil-
ku oddzielnych bramek logicznych lub przerzutników. Takie układy nazwa-
no układami SSI, tj. o małej skali integracji (ang. Small Scale Integration).
Konstruowanie dwójkowych układów kombinacyjnych i sekwencyjnych po-
legało więc na opracowaniu schematu logicznego sieci złożonej z bramek
i przerzutników (tak jak i my to robiliśmy w poprzednim rozdziale), a na-
stępnie połączenie końcówek układów scalonych zgodnie z tym schematem.
Niedługo trzeba było czekać na układy o średniej skali integracji (MSI,
Medium Scale Integration), zawierające już setki tranzystorów. Były niewie-
le droższe od SSI, a zawierały gotowe podzespoły logiczne, takie jak kil-
kubitowy sumator, jednostkę arytmetyczną, multiplekser, dekoder, rejestr.
Przekroczono w ten sposób pewną granicę: wewnątrz układu scalonego za-
częto wykonywać nie tylko połączenia zgodne z elektronicznym schematem
pojedynczego obwodu, lecz również połączenia logiczne, takie, jakie my pro-
jektowaliśmy, wykorzystując algebrę Boole’a, tablice Karnaugha itd.
W oparciu o tę już dostępną technologię układów scalonych SSI oraz
MSI, w drugiej połowie lat sześćdziesiątych zaczęto budować nowe pokolenie
komputerów. Po pierwszej generacji, lampowej i po drugiej, tranzystorowej,
przyszedł czas na ich trzecią generację3. Obok dużych i średnich maszyn,
w których specjalizowała się zwłaszcza IBM4 i większe firmy komputerowe,
charakterystycznym zjawiskiem końca lat sześćdziesiątych XX wieku było
pojawienie się minikomputerów, produkowanych często przez firmy całkiem
nowe na rynku. Ci nowi producenci kupowali od różnych dostawców ukła-

2 Zajmujemy się tu przede wszystkim układami cyfrowymi, lecz produkowano


również scalone układy analogowe, np. na potrzeby łączności radiowej i telewi-
zyjnej.
3 Tego terminu używano przez pewien czas, ale potem liczenia kolejnych generacji
praktycznie w ogóle zaprzestano. Teraz mówi się niekiedy o kolejnych genera-
cjach, ale już w ramach grupy coraz bardziej zaawansowanych współczesnych
mikroprocesorów.
4 O ważnej koncepcji IBM, pochodzącej z tego okresu (System/360) wspomnimy
jeszcze dalej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

360 14. Wiek informatyki

dy scalone, pamięci, urządzenia zewnętrzne i inne podzespoły, zestawiali je


według własnego projektu w całość, dorabiali podstawowe oprogramowanie
– i sprzedawali pod własną marką jako oryginalny model minikomputera.
Techniczne parametry pierwszych minikomputerów mogą dziś śmie-
szyć, ale wówczas była to rewolucja. Pierwsze minikomputery miały już
rozmiary – powiedzmy – dużego ówczesnego telewizora czy średniej lo-
dówki i kosztowały sporo poniżej 25 000 dolarów. Za tę cenę użytkow-
nik otrzymywał właściwie tylko procesor z pamięcią RAM o rozmiarze od
16 K do 64 K słów, a rozbudowa konfiguracji o urządzenia zewnętrzne (da-
lekopis, czytnik i perforator taśmy lub kart, zewnętrzna pamięć taśmowa
i dyskowa) była w stanie szybko podnieść koszt całości do kilkudziesięciu
tysięcy dolarów. Jednak nie były to setki tysięcy ani miliony dolarów, więc
na przyzwoity, niewielki i niezawodny komputer mogło sobie już pozwolić
znacznie więcej firm i organizacji.
Komputery i technika cyfrowa wyszły wtedy w USA i innych kra-
jach zachodnich poza krąg wojskowych laboratoriów, przemysłu zbrojenio-
wego, rządowych centrów i najbogatszych uniwersytetów, przedsiębiorstw
czy banków. Począwszy od końca lat sześćdziesiątych, zaczęły stopniowo
pojawiać się już wszędzie: w mniejszych uczelniach, cywilnym przemyśle,
usługach, telekomunikacji i w mediach: prasie, radiu, telewizji. Również
komputery średnie i wielkie (ang. mainframes), budowane z wykorzysta-
niem układów scalonych, były coraz tańsze i bardziej niezawodne, a więc
znajdowały użytkowników w coraz większym kręgu instytucji.
W roku 1969 miało miejsce jeszcze jedno wydarzenie, którego praw-
dziwych konsekwencji nikt wtedy nie przewidywał. W Stanach Zjednoczo-
nych uruchomiono mianowicie połączenie między pierwszymi czterema
węzłami sieci komputerowej ARPA (inaczej: ARPANET). Jej budowę fi-
nansowała i koordynowała Agencja do spraw Zaawansowanych Projektów
Badawczych (Advanced Research Projects Agency, stąd – od pierwszych
liter – nazwa sieci) powołana również przez Pentagon, tj. Departament
Obrony USA. Sieć miała umożliwić ośrodkom badawczym (zaangażowa-
nym w projekty wojskowe) wzajemne korzystanie na odległość z opro-
gramowania i mocy obliczeniowej ich komputerów. Jednocześnie – co dla
Pentagonu z pewnością nie było bez znaczenia– decentralizacja i geogra-
ficzne rozproszenie zasobów sieci pozwalało zachować przynajmniej czę-
ściową jej sprawność w wypadku ataku atomowego.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Komputery lat sześćdziesiątych 361

W sieci ARPA zastosowano zasadę, że przesyłane dane dzieli się na


pakiety o ustalonej długości. Ruch pakietów w sieci kontrolowały specjalnie
do tego celu przeznaczone minikomputery. W każdym węźle był jeden taki
minikomputer komunikacyjny. Odbierał on przychodzące pakiety i te, które
były zaadresowane do głównego komputera (ang. host, dosłownie – gospo-
darz) danego węzła – kierował do niego, a pozostałe – przesyłał dalej. Dziś
nazwalibyśmy te minikomputery pierwowzorami sieciowych ruterów (ang.
routers), a cała sieć ARPA była zalążkiem dzisiejszego Internetu. Wkrótce
do sieci zaczęto dołączać następne węzły i po pięciu latach, w roku 1974,
było ich już ponad 60. Jednak droga do dzisiejszej, globalnej sieci była jesz-
cze daleka.
Nie trzeba tłumaczyć, jak ogromną cywilizacyjną rolę odgrywa Internet
obecnie. Odmienił on sposób porozumiewania się między ludźmi, dostęp do
światowych zasobów wiedzy, handel, usługi, rozrywkę... praktycznie wszyst-
kie dziedziny życia. A wówczas – co jest prawie nie do wiary – na przełomie
lat sześćdziesiątych i siedemdziesiątych nikt tego nie przewidział, choć na
kartach literatury fantastyczno-naukowej i w dziesiątkach filmów SF rozwi-
jano niezwykłe wizje przyszłości. Czegóż tam nie było: były i podróże mię-
dzygalaktyczne w hiperprzestrzeni, i zmyślne roboty, i cybernetyczne organi-
zmy (cyborgi), i miniaturowe komunikatory, i superkomputery dysponujące
sztuczną inteligencją, i spotkania z obcymi cywilizacjami... Ale powstania tu,
na Ziemi, ogólnoświatowej sieci nie przewidział nikt 5.
Co zabawniejsze, kiedy sieć ARPANET już istniała, operatorzy po-
szczególnych węzłów szybko zaczęli wymieniać między sobą tekstowe
wiadomości: najnowsze dowcipy, życzenia urodzinowe, plotki i informa-
cje o pogodzie. Dziś wiemy, że były to początki poczty elektronicznej, ale
wówczas takie niepoważne wykorzystywanie służbowego sprzętu było za-
kazane i przez kierownictwo sieci surowo tępione. Dopiero później, gdy
sieć rozrosła się o dalsze dziesiątki cywilnych ośrodków – stało się jasne, że
to właśnie komunikacja między ludźmi jest w gruncie rzeczy podstawowym
zadaniem sieci, a komunikacja między komputerami jest jedynie technicz-

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

362 14. Wiek informatyki

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.

Lata siedemdziesiąte: postęp nie zwalnia


Na początku lat siedemdziesiątych postęp wcale nie zwolnił. Liczba tran-
zystorów w jednym scalonym plasterku (ang. chip) sięgnęła kilku tysięcy,
upakowanych na kilkunastu milimetrach kwadratowych powierzchni krysta-
licznej płytki. To kwadracik o boku, powiedzmy, 4 mm. Również logiczna
złożoność zawartych w nim układów wzrosła. Już w roku 1971 firma Intel
wyprodukowała układ scalony Intel 4004, zawierający cały (wprawdzie na
razie tylko czterobitowy) mikroprocesor, a więc jednostkę, która po dołącze-
niu pamięci była zdolna do samodzielnego wykonywania zawartych w tej
pamięci programów. Na rynku pojawiły się wtedy na masową skalę cyfrowe
zegarki i elektroniczne kalkulatory o rozbudowanych funkcjach, konstru-
owane z wykorzystaniem mikroprocesorów. To one zaczęły wypierać z in-
żynierskiej praktyki stare, poczciwe i bardzo zasłużone analogowe suwaki
logarytmiczne, które wkrótce całkiem odeszły do historii.

6 Warto dodać, że w 1974 roku wyjątkowo trafną wizję przyszłego „uniwersal-


nego systemu informacyjnego”, opartego na komputerach, terminalach i łączach
telekomunikacyjnych, który „przełamie bariery w wymianie informacji między
ludźmi i krajami” przedstawił radziecki uczony, akademik Andriej Sacharow
(1921–1989). Był on wybitnym fizykiem, w latach pięćdziesiątych i sześćdzie-
siątych – jednym z twórców radzieckiej bomby wodorowej, potem stał się opo-
zycjonistą o wielkiej odwadze cywilnej i moralnej sile, gorącym zwolennikiem
rozbrojenia i praw człowieka. Nagrodzono go w roku 1975 pokojową nagrodą
Nobla, a w swej ojczyźnie – pozbawiono możliwości pracy naukowej i deporto-
wano (w 1980 roku) z Moskwy do miasta Gorki (Niżny Nowogród), skąd uwolnił
go dopiero Gorbaczow na fali tzw. pieriestrojki w końcu 1986 roku
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Lata siedemdziesiąte: postęp nie zwalnia 363

Trzy lata później, w roku 1974, zaczęto mówić o układach LSI –


o wielkiej skali integracji (ang. Large Scale Integration). Liczba tranzystorów
w jednym krzemowym plasterku (chipie) zbliżyła się do dziesięciu tysięcy.
Na rynku pojawiały się coraz to nowe mikroprocesory, 8-bitowe, a później
16-bitowe. Z ich użyciem można już było budować nie tylko kalkulatory i ze-
garki, ale prawdziwe, programowane komputery.
Bardzo ważnym krokiem naprzód było opanowanie produkcji scalo-
nych pamięci półprzewodnikowych. Nie pisaliśmy o tym wcześniej, ale od
początku lat pięćdziesiątych XX wieku główną pamięć (RAM) praktycznie
wszystkich komputerów budowano z wykorzystaniem rdzeni ferrytowych
(ang. ferrite core memory). Fizycznie były to malutkie pierścienie z materia-
łu o właściwościach magnetycznych, w których kierunek namagnesowania
można było skokowo zmieniać na przeciwny przez przyłożenie odpowied-
nio skierowanego pola elektrycznego. Co ważne, rdzeń ferrytowy zachowy-
wał tak ustawiony kierunek namagnesowania nawet po odłączeniu zasilania.
Był więc w stanie zapamiętać jeden bit informacji: 0 – gdy był namagneso-
wany w jednym kierunku, a 1 – gdy w przeciwnym. W czasach komputerów
lampowych i tranzystorowych był to znacznie tańszy, a przede wszystkim
bardziej niezawodny sposób realizacji pamięci RAM, niż gdyby się ją zbu-
dowało z dziesiątków tysięcy ówczesnych przerzutników i bramek.
Pamięć ferrytowa musiała zawierać tyle rdzeni, ile bitów miała zapamię-
tać. Dla przykładu, do wykonania bloku pamięci RAM o pojemności zaledwie
np. 4 k (czyli 4096) szesnastobitowych słów trzeba było łącznie 4096 ⋅ 16 =
= 65 536 rdzeni. Te rdzenie trzeba było jeszcze „pozszywać” w tyle płatów
pamięci, ile bitów liczyło słowo. Tak więc w tym przypadku trzeba by było
wykonać szesnaście płatów, po 4096 rdzeni w każdym, przeplatając w staran-
nie zaplanowany sposób cztery (później trzy) cienkie jak włos druciki przez
otworek w każdym rdzeniu. Druciki te służyły do wybierania jednego z rdzeni
w każdym z płatów, do odczytywania jego stanu i do przemagnesowywania
przy zapisie. Oczywiście, blok pamięci miał także elektroniczny układ steru-
jący, który wymuszał odpowiednie elektryczne sygnały w tych przewodach.
Niestety, czynność „szycia” pamięci ferrytowej nie dawała się zauto-
matyzować i była wykonywana ręcznie, pod specjalnym szkłem powięk-
szającym lub mikroskopem. Jeśli się weźmie pod uwagę, że rdzenie fer-
rytowe miały zewnętrzną średnicę rzędu 1 mm, a otworek w ich środku
rzędu 0,2 mm – to trzeba przyznać, że w porównaniu z szyciem pamięci
ferrytowej oddzielanie ziarenek soczewicy od maku (co, jak pamiętamy, po-
leciła Kopciuszkowi zła macocha) wydaje się wesołą zabawą. W zakładach
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

364 14. Wiek informatyki

produkujących pamięci ferrytowe zatrudniano więc osoby bez większego


przygotowania technicznego (często rzeczywiście szwaczki ze zlikwidowa-
nych zakładów odzieżowych), lecz wykazujące się wyjątkowo dobrą ko-
ordynacją ruchową, a przede wszystkim – ponadprzeciętną cierpliwością
i opanowaniem. Tak czy inaczej, ręczny charakter produkcji pamięci ferry-
towych utrudniał zwiększanie rozmiaru pamięci RAM ponad np. 32–64 kB
w przypadku minikomputerów czy 256–512 kB w przypadku droższych
i większych mainframes.
W pierwszej połowie lat siedemdziesiątych, gdy liczba tranzystorów
w jednym układzie scalonym zbliżyła się do kilku tysięcy – powrócono do
pomysłu realizowania pamięci RAM z elektronicznych układów logicznych,
zwłaszcza że komórki pamięci mają regularną, powtarzalną strukturę, ła-
twiejszą do zaplanowania i rozmieszczenia w układzie scalonym niż np. lo-
giczny schemat mikroprocesora.
Od roku 1974 scalone pamięci półprzewodnikowe, choć najpierw
małe (rzędu 1 kb, czyli 1024 bity), już były dostępne na rynku, a rdze-
nie ferrytowe zaczęły stopniowo odchodzić do lamusa. Później, w miarę
doskonalenia technologii układów scalonych liczba tranzystorów w jednym
chipie stale rosła i w roku 1986 można już było kupić kostkę pamięci RAM
o pojemności 1 Mb (tj. miliona bitów), zawierającą ponad milion tranzysto-
rów. Do zbudowania pamięci o pojemności 1MB (miliona Bajtów) potrzeba
zaledwie ośmiu takich kostek. Dziś nosimy w kieszeni pendrive, który ma
pojemność kilku czy kilkunastu GB (gigabajtów), a więc zawiera kilkana-
ście miliardów tranzystorów.

Kolejny przełom: komputery prywatne


W styczniu 1975 roku pewien amerykański magazyn dla elektroników
majsterkowiczów podjął wysyłkową sprzedaż nowego zestawu do samo-
dzielnego montażu w domu. Zestawy te kompletowała i wysyłała na za-
mówienie nieduża, kilkunastoosobowa firma (nazywała się MITS: Micro
Instrumentation and Telemetry Systems), która – jak wiele innych – zaczy-
nała od garażu właściciela i wytwarzała dla hobbystów nadajniki radiowe,
modele rakiet, kalkulatory itp. do składania w domu.
Tym razem w zestawie był ośmiobitowy komputer nazwany Altair
8800: płyta główna, procesor Intel 8080, 1 kB półprzewodnikowej pamięci
RAM... Wszystko za jedne 439 dolarów. Producent przewidywał, że koszty
mu się zwrócą, jeśli w ciągu roku uda się sprzedać 200 sztuk takich komple-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Kolejny przełom: komputery prywatne 365

tów. Ku wielkiemu zaskoczeniu, w ciągu pierwszego miesiąca od ogłoszenia


napłynęło ponad 1000 zamówień, a linia telefoniczna urywała się pod za-
pytaniami od następnych klientów. Firma musiała zatrudnić kilkudziesięciu
nowych pracowników i mimo trudności z dotrzymaniem terminów realizacji
zamówień, do sierpnia 1975 roku udało się jej sprzedać ponad 5000 sztuk
kompletów do samodzielnego montażu Altaira.
Prawie w tym samym czasie, w innym amerykańskim garażu, dwaj
zaprzyjaźnieni Stefanowie, Steve Wozniak i Steve Jobs, zbudowali kom-
puter Apple I, też z myślą o jego produkcji i sprzedaży prywatnym użyt-
kownikom. Na płycie głównej Apple I znajdowało się około 60 układów
scalonych, wśród nich mikroprocesor (z zegarem 1 MHz) i pamięć RAM
o pojemności 4 kB (z możliwością rozszerzenia do 48 kB). Pierwotna cena
komputera (lipiec 1976 r.) wynosiła 666,66 dolara, lecz użytkownik musiał
sam zatroszczyć się jeszcze o obudowę, zasilacz, klawiaturę i telewizor, któ-
rego ekran służył za monitor komputera.
Również i w tym przypadku popyt przekroczył oczekiwania pro-
jektantów. Wozniak i Jobs szybko opracowali kolejny model komputera,
Apple II, a następnie dalsze, w szczególności (później) komputer Macintosh
(1984), który był jednym z pierwszych modeli z myszą i graficznym inter-
fejsem użytkownika. Ich firma – Apple Computer Inc. – stała się znanym
producentem komputerów, tych z kolorowym nadgryzionym jabłuszkiem na
obudowie.
Był to kolejny przełomowy moment w historii techniki komputerowej.
Ceny masowo produkowanych układów scalonych spadły już do takiego po-
ziomu, że nagle otworzył się nowy, wielomiliardowy rynek użytkowników
prywatnych, których było stać na własny komputer dla rozrywki, hobby i nie-
zbyt wyrafinowanych celów domowych.
W USA, Europie i w Japonii jak grzyby po deszczu zaczęły powsta-
wać nowe firmy, produkujące domowe komputery, konsole i automaty do
gier, a także „prywatne” oprogramowanie: gry komputerowe, edytory tek-
stów, arkusze kalkulacyjne, kolejne wersje prostych języków programowa-
nia dla hobbystów: BASIC, Logo... Większość tych firm nie wytrzymała
później konkurencji i jest dziś (jak np. Commodore, Atari, czy Spectrum)
jedynie z sentymentem wspominana przez ówczesnych użytkowników. Inne
– jak choćby wspomniany Apple Computer Inc. (obecnie Apple Inc.) czy
Microsoft Corp., założony również w 1975 roku przez Billa Gatesa i Paula
Allena – działają na tym rynku do dziś.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

366 14. Wiek informatyki

W roku 1981 gigant komputerowy, IBM Corp., także przedstawił


swój model: IBM 5150, szerzej znany jako IBM PC7. Kosztował on w
wersji standardowej prawie 2900 dolarów. Mimo tej dość wysokiej ceny
(dziś odpowiadałaby ona grubo ponad sześciu tysiącom dolarów) IBM PC
odegrał jednak szczególnie ważną rolę. Producent opublikował bowiem
pełną dokumentację techniczną komputera i zezwolił na jego kopiowanie
bez jakichkolwiek opłat czy licencji. Natychmiast wzięły się do tego setki
firm z całego świata i sklonowane kopie IBM PC zaczęły się pojawiać na
rynku w dziesiątkach tysięcy sztuk. IBM zyskał na tym niebywałą promo-
cję marki, ale przede wszystkim to, że jego rozwiązania techniczne (w tym
także dyskietki, urządzenia zewnętrzne itp.) stały się de facto standardami
w światowej skali. Choć IBM PC szybko zastąpiono nowszymi modelami
– sam skrót PC (od ang. Personal Computer, czyli komputer osobisty) po-
został w społecznym odbiorze synonimem peceta, komputera do prywat-
nego użytku.
Teraz ruszyła prawdziwa lawina. Popyt generowany przez ten nowy,
ogromny rynek spowodował, że w ciągu kilku lat powstały nowe fabryki
układów scalonych i podzespołów komputerowych, także w krajach, których
przedtem raczej nie kojarzono z najnowocześniejszą technologią. Do trady-
cyjnej czołówki zaawansowanych technologicznie krajów, takich jak USA,
Japonia czy zachodnia Europa, dołączyły także Korea Południowa, Tajwan,
Singapur, Hongkong, Malezja – które w ciągu lat osiemdziesiątych XX wie-
ku zalały cały świat tanią, nie tylko komputerową elektroniką.
Powstały całe nowe gałęzie przemysłu: przemysł gier komputerowych,
produkcji oprogramowania do prywatnego użytku, dla małych przedsiębiorstw
itd... Wyrosły nowe globalne firmy i nowe fortuny. Najbardziej spektakular-
nym przykładem jest Microsoft Corp., której szef, Bill Gates, ze współwła-
ściciela małej firmy (dzięki temu, że utrafił w swój czas i opracował system
operacyjny MS DOS dla komputerów IBM PC, a następnie potrafił rozwinąć
działalność firmy) stał się w ciągu kilku lat najbogatszym człowiekiem świa-
ta. Coraz powszechniejsza dostępność komputerowego sprzętu i oprogramo-

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 367

wania wyzwoliła żywiołową falę inwencji i aktywności u tysięcy ambitnych


ludzi, którzy też chcieli powtórzyć sukces Wozniaka, Jobsa czy Gatesa.
Skoncentrowaliśmy się tu głównie na układach scalonych, należy
jednak pamiętać, że równolegle postępował rozwój technologii innych cy-
frowych urządzeń i podzespołów. Dyski magnetyczne: twarde i elastyczne
(dyskietki), liczne urządzenia optoelektroniczne: fotodiody, diody świecące,
lasery i światłowody (które – znane od lat, ale jak gdyby ponownie wynale-
zione także w latach siedemdziesiątych – wkrótce zrewolucjonizowały tele-
komunikację), dyski optyczne (CD, a potem DVD), wyświetlacze i ekrany:
najpierw kineskopowe, potem plazmowe i ciekłokrystaliczne... Nie sposób
wymienić wszystkich. Ważne jednak, że producenci tych podzespołów nie
byli już uzależnieni wyłącznie od programów wojskowych i rządowych, od
projektów badawczych finansowanych z budżetu państwa czy od zapotrze-
bowania ze strony największych przedsiębiorstw.
Szybko rosnący rynek użytkowników prywatnych generował ogrom-
ny popyt i zapewniał nie tylko motywację, lecz również źródło finansowania
badań nad nowymi rozwiązaniami i ulepszaniem technologii masowej ich
produkcji. Oczywiście, korzystały na tym później także programy wojsko-
we i ośrodki akademickie oraz telekomunikacja i przemysł. W ten sposób
w latach osiemdziesiątych XX wieku komputery i cyfrowe układy sterowa-
nia zaczęły wchodzić jeszcze szerszą falą do całej zachodniej gospodarki:
przemysłu, administracji, medycyny, telekomunikacji, szkolnictwa, mediów,
lotnictwa, motoryzacji, przemysłu rozrywkowego...

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

368 14. Wiek informatyki

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.

A co tam, panie, w polityce?


Prawda, jak konsekwentny i logiczny ciąg zdarzeń prowadzi od zimnowo-
jennej rywalizacji na Ziemi i w kosmosie, poprzez układy scalone – aż do
rewolucji informacyjnej i ogólnoświatowej sieci?
Nie, nieprawda. Nie dajmy się zwieść: jesteśmy tacy mądrzy dopiero
z perspektywy kilkudziesięciu już minionych lat. Wtedy, kiedy to wszystko
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

A co tam, panie, w polityce? 369

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-

8 Sam marketingowo chwytliwy termin minikomputer pojawił się – z całą pewno-


ścią nieprzypadkowo – dokładnie w tym samym czasie, co minispódniczki, które
były głośną, szokującą nowością młodzieżowej mody lat sześćdziesiątych.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

370 14. Wiek informatyki

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

A co tam, panie, w polityce? 371

Według oryginalnych zamierzeń IBM, system ten (zwany Systemem


360, w skrócie S/360) składał się z kilku (początkowo: sześciu) modeli jedno-
stek centralnych, a także zestawu bloków pamięci i urządzeń zewnętrznych,
z których – jak z dobrze dopasowanych klocków – można było budować
różne konfiguracje, zależnie od potrzeb i poziomu zamożności użytkownika.
Każdą taką konfiguracją zarządzał w zasadzie ten sam (choć dostosowywany
do konkretnej konfiguracji) system operacyjny, OS/360.
Ważne było to, że oprogramowanie aplikacyjne przygotowane dla
mniejszych i wolniejszych (a więc też tańszych) konfiguracji – wykonywa-
ło się także w konfiguracjach droższych i szybszych. Pozwalało to przedsię-
biorstwu kupić najpierw mały i stosunkowo tani system, a potem, w miarę
rozwoju firmy, rozbudowywać go, a nawet wymienić jednostkę centralną na
kilkudziesięciokrotnie szybszą, zachowując jednocześnie opracowane dotąd
bazy danych i całe oprogramowanie użytkowe niezbędne dla ciągłości pracy
przedsiębiorstwa. Pomysł okazał się niezwykle trafny i S/360 szybko przyjął
się na światowym rynku większych komputerów (mainframes) dla przedsię-
biorstw. Korporacja IBM rozpoczęła sprzedaż Systemu/360 już w 1966 roku.
Rosjanom też ta koncepcja się spodobała i w roku 1968 przystąpili
do klonowania Systemu/360, oczywiście bez żadnej zgody czy wsparcia ze
strony IBM i bez oficjalnego dostępu do dokumentacji. Kryło się pod tym
chytre (lecz w praktyce naiwne i trudne do realizacji) założenie, że dzięki
zakładanej identyczności hardware’u obu systemów będzie można wyko-
rzystać od razu, za darmo, nie tylko gotowy system operacyjny OS/360,
lecz również całe oprogramowanie użytkowe opracowane przez IBM lub
zachodnie przedsiębiorstwa korzystające z S/360.
W roku 1972, po kilku latach żmudnego i mało twórczego odtwarza-
nia IBM-owskiego sprzętu i oprogramowania systemowego, podjęto w końcu
w Związku Radzieckim produkcję pierwszych, najmniejszych maszyn z całego
planowanego szeregu. Tymczasem już dwa lata wcześniej, bo w roku 1970,
IBM opracowało nową linię komputerów: System/370, kompatybilny z S/360,
lecz ze znacznie doskonalszym hardware'em i kilkoma radykalnie nowymi wer-
sjami systemu operacyjnego. W roku 1977 sprzedaż S/360 w ogóle definitywnie
zamknięto. Ale po komunistycznej stronie, w produkcję maszyn JS EMC Riad
zaangażowano już w tym czasie kilka krajów obozu socjalistycznego i kontynu-
owano ją przez wiele lat, z topornym wdziękiem właściwym dla scentralizowa-
nej gospodarki, wśród administracyjnej mitręgi, tysięcy uzgodnień i planowania
na całe lata w przód. JS EMC Riad był więc w zasadzie technicznie spóźniony
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

372 14. Wiek informatyki

już w momencie narodzin i – dość niemrawo produkowany – nie przyczynił się


w znaczącym stopniu do modernizacji krajów obozu komunistycznego.
Podobnie było w przypadku minikomputerów i komputerów osobi-
stych. Nieruchawa machina planowej państwowej gospodarki nie miała żad-
nych szans, by nadążyć za rozwojem tej dziedziny. Na Zachodzie opierał się
on na swobodzie wdrażania nowych pomysłów, dostępności podzespołów,
a przede wszystkim na żywiołowej inicjatywie aktywnych jednostek, napę-
dzanych – co tu ukrywać – tak wstrętną dla komunistycznej ideologii kapita-
listyczną żądzą zysku. Nic takiego nie mogło mieć miejsca w krajach obozu
wschodniego – i na rezultaty nie trzeba było długo czekać.

Nieoczekiwany koniec zimnej wojny


Na początku lat osiemdziesiątych XX wieku cywilizacyjne zapóźnienie kra-
jów wschodniego obozu stawało się coraz bardziej wyraźne. Dwie dekady
technologicznej modernizacji Zachodu nie poszły na marne. Zachodnia go-
spodarka odzyskała wigor i weszła w okres prosperity, do czego przyczy-
niła się także liberalna polityka gospodarcza USA i Wielkiej Brytanii pod
rządami takich przywódców, jak Ronald Reagan (prezydent USA w latach
1981–89) i Margaret Thatcher (pani premier Wielkiej Brytanii w latach
1979–1990).
Tymczasem Związek Radziecki od końca lat siedemdziesiątych za-
czął się pogrążać w ekonomiczną zapaść, która była konsekwencją wielolet-
nich błędów w zarządzaniu państwem i absurdalności zasad funkcjonowania
komunistycznej gospodarki. Wprowadzono kartkowy system racjonowania
żywności, której wkrótce i tak zaczęło brakować. Mocarstwo, którego zie-
mie kiedyś, jeszcze „za cara”, zaopatrywały w zboże pół świata – musia-
ło zwrócić się do międzynarodowej społeczności o humanitarną pomoc.
Podobne kłopoty dotknęły też inne kraje socjalistycznego obozu, których
obywatele z trudem wiązali koniec z końcem i spędzali pół życia w upo-
karzających kolejkach po najprostsze, codzienne produkty: chleb, mięso,
benzynę, cukier...
Sama troska o codzienne życie obywateli nie byłaby może w sta-
nie zakłócić dobrego samopoczucia radzieckiego kierownictwa. Gorzej, że
coraz wyraźniejsze stawały się objawy technologicznego zapóźnienia tak-
że w produkcji zbrojeniowej i przemyśle lotniczym. Te działy radzieckiej
gospodarki były szczególnie ważne nie tylko ze względu na bezpośredni
związek z wojskowym potencjałem Układu Warszawskiego, ale również
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Nieoczekiwany koniec zimnej wojny 373

dlatego, że były znaczącym źródłem dochodów z eksportu uzbrojenia do


krajów Trzeciego Świata. Wprawdzie półautomatyczny karabinek projektu
Kałasznikowa, AK-47 (sławny po dziś dzień kałach), wciąż był (i nadal
jest) eksportowym hitem, ale radzieckie czołgi, śmigłowce, samoloty, syste-
my obrony przeciwlotniczej i przeciwrakietowej – zaczęły coraz wyraźniej
ustępować konkurencyjnym zachodnim modelom wyposażonym w skom-
puteryzowane systemy sterowania i łączności, o lepszych właściwościach
bojowych i technicznych.
Jakby tego było mało, w roku 1983 prezydent USA, Ronald Reagan,
ogłosił rozpoczęcie prac nad Inicjatywą Obrony Strategicznej (SDI –
Strategic Defense Initiative), którą dziennikarze szybko (przez skojarzenie
z głośnym wówczas filmem) nazwali gwiezdnymi wojnami. SDI miała po-
legać na rozmieszczeniu w okołoziemskiej przestrzeni kosmicznej skoor-
dynowanego systemu wojskowych satelitów, wyposażonych w laserowe
środki niszczenia rakiet balistycznych zmierzających w kierunku Stanów
Zjednoczonych.
Systemu tego nigdy nie zbudowano i do dziś nie wiadomo, czy był
to rzeczywisty projekt Pentagonu, czy jedynie gigantyczny blef Ronalda
Reagana, który miał uświadomić Rosjanom rozmiary przewagi USA
w dziedzinie najnowszej technologii. A może był to odwet za upokorzenie
z czasów pierwszych sputników? Tak czy inaczej, kierownictwo Związku
Radzieckiego musiało boleśnie zdać sobie sprawę, że jego imperium nie jest
już w stanie podjąć rywalizacji na tym polu.
Koncentrowaliśmy się tu na procesach gospodarczych i rozwoju kom-
puterowej technologii. Rzeczywiście, miała ona bardzo ważny wpływ na cy-
wilizacyjny rozwój Zachodu: od badań naukowych poprzez procesy projek-
towania i wytwarzania wszelkich urządzeń i materiałów, od systemów uzbro-
jenia – aż do rolnictwa i medycyny. Ale przecież równolegle toczyły się tak-
że inne, nie mniej ważne procesy i miały miejsce inne, nie mniej ważne wy-
darzenia. Sowiecka interwencja wojskowa w Afganistanie (koniec 1979 r.),
a z drugiej strony – Aleksandra Sołżenicyna Archipelag Gułag (wydany na
Zachodzie w 1973 r.), ruch praw człowieka i konferencja helsińska (1975),
powstawanie w krajach socjalistycznych środowisk opozycyjnych takich,
jak ruch Karta 77 w Czechosłowacji, Komitet Obrony Robotników (1976)
i podziemne organizacje niepodległościowe w Polsce, akademik Andriej
Sacharow w Rosji, wybór kardynała Karola Wojtyły na Papieża (1978), ruch
Wolnych Związków Zawodowych, polska „Solidarność” (1980) i stan wo-
jenny w Polsce (1981)...
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

374 14. Wiek informatyki

Nie piszemy tu rozdziału o historii powszechnej, poprzestańmy więc


na wymienieniu tylko tych kilku haseł. W każdym razie wszystkie te proce-
sy, osoby i wydarzenia podkopały moralne i polityczne poparcie dla systemu
komunistycznego nawet w tych kręgach, które do tej pory jeszcze wierzyły
w jego zwycięstwo.
Decydujący cios systemowi komunistycznemu zadał sam radziecki
przywódca, Michaił Gorbaczow. Wybrany w roku 1985 na stanowisko ge-
neralnego sekretarza komunistycznej partii, podjął próbę radykalnej przebu-
dowy (ros. pieriestrojka) komunistycznego systemu poprzez zapoczątkowa-
nie otwartej społecznej debaty. Ale wziął się do reformowania tego, co było
niereformowalne. Jawność dyskusji (ros. głasnost’) jedynie ukazała radziec-
kim obywatelom nie tylko prawdziwy stan radzieckiej gospodarki i biedę
społeczeństwa, lecz również bezmiar absurdów, kłamstw i nieprawości, na
których ten system był zbudowany.
Katastrofa radzieckiego reaktora atomowego w Czarnobylu (1986)
i seria porażek w Afganistanie (prowadząca w 1988 roku do decyzji o wy-
cofaniu stamtąd pobiedonosnoj Krasnoj Armii, czyli „zawsze zwycięskiej”
Armii Czerwonej) dopełniły obrazu nieudolności radzieckiego imperium.
Trwało ono jednak nadal siłą przyzwyczajenia, nieprzewidywalne i wciąż
militarnie groźne, z tysiącami rakiet balistycznych, bronią masowej zagła-
dy i kilkumilionową armią rozmieszczoną na ogromnych obszarach naszego
globu.
W roku 1989 Polacy jako pierwsi spróbowali wyrwać się z kręgu
systemu komunistycznego. Było znaczącym memento historii, że dokładnie
tego samego dnia, 4 czerwca 1989 roku, kiedy w Polsce odbywały się pierw-
sze od lat – na razie jedynie częściowo wolne – wybory, w dalekim Pekinie
komunistyczne chińskie władze zmasakrowały czołgami pokojową demon-
strację studentów, którzy wysuwali postulaty umiarkowanej demokratyzacji.
Na szczęście w Europie do żadnej takiej tragedii nie doszło i w ciągu zaled-
wie kilku jesiennych miesięcy 1989 roku nie tylko Polska, ale w ślad za nią
także wszystkie europejskie kraje demokracji ludowej powołały nowe rządy
i skierowały się na własną drogę rozwoju.
Dwa lata później, w roku 1991, rozwiązał się Układ Warszawski, głów-
ny militarny przeciwnik NATO, a jeszcze kilka miesięcy później przestał
w końcu istnieć także sam Związek Radziecki, rozpadając się na kilkanaście
odrębnych, formalnie niepodległych państw. Ponadczterdziestoletnia zimna
wojna zakończyła się nagle i to w sposób kompletnie nieprzewidziany: przez
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Nieoczekiwany koniec zimnej wojny 375

zniknięcie jednej ze stron. Wprawiło to zaskoczone kraje zachodnie w stan


głębokiej konfuzji, ale także wzbudziło wielkie obawy o losy nagromadzonych
na wschodzie arsenałów konwencjonalnej i jądrowej broni.
Tak w największym skrócie wyglądała historia kilkudziesięciu po-
wojennych lat naszego globu. Rozwój technologii układów scalonych
i masowa komputeryzacja, nowe języki programowania, algorytmy i opro-
gramowanie, cyfrowa telekomunikacja i Internet – były w niej na pewno
bardzo ważnymi (choć przecież niejedynymi) czynnikami wpływającymi
na losy świata.
I oto już nasza współczesność. Czy nastały czasy powszechnej szczę-
śliwości? Oczywiście, nie. Pojawiły się nowe problemy społeczne i poli-
tyczne, nowe linie podziałów, nowe zagrożenia, nowe wojny i konflikty in-
teresów...
A ku czemu to wszystko zmierza, w technologii i w polityce? O tym
powiedzą najlepiej nasze wnuki, które będą podkpiwać z naszych dzisiej-
szych prymitywnych komputerów i z wyższością stwierdzą o naszych cza-
sach, że trzeba było wtedy... że można było łatwo przewidzieć... że już wte-
dy było oczywiste...
Mądrale.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

15. Von Neumanna komputer


z programem w pamięci

Jak to się zaczęło


W poprzednim rozdziale powiedzieliśmy już, w jakich okolicznościach zro-
dził się pomysł komputera z pamiętanym programem (ang. stored program
computer). Powstał on w wyniku doświadczeń z bardzo rozbudowanymi,
ale wciąż dziesiętnymi i wciąż zewnętrznie programowanymi maszynami,
zwłaszcza ENIAC i Mark I. Wiemy także, że w tym samym czasie znane
już były od kilku lat naukowe idee Turinga i Shannona. Alan Turing sprecy-
zował abstrakcyjny, bardzo uniwersalny model sekwencyjnych obliczeń, i to
nie tylko numerycznych. Claude Shannon z kolei skierował myśl naukow-
ców i inżynierów w stronę systemu dwójkowego, logiki i algebry Boole’a,
również sugerując niezwykłą uniwersalność takiego podejścia.
Wydaje się, że pomysł maszyny liczącej z pamiętanym programem naj-
wcześniej zaczął się krystalizować w środowisku projektantów i bezpośrednich
użytkowników ENIAC-a, takich jak J.A. Presper Eckert, John Mauchly, Henry
Goldstine, Albert Burks. Ostatecznie jednak przypisuje się go Johnowi von
Neumannowi, który był naukowym konsultantem zespołu i nadał temu pomy-
słowi formę uporządkowanego raportu.
Sam John von Neumann (1903–1957) to bardzo interesująca postać,
wyrastająca wysoko ponad przeciętność. Jest on uważany za jednego z naj-
większych matematyków XX wieku. Urodził się (jako Janos von Neumann)
w Budapeszcie, na Węgrzech (a właściwie w ówczesnym Cesarstwie Austro-
Węgierskim), w zamożnej rodzinie o żydowskich korzeniach. We wczesnych
latach życia zasłynął jako cudowne dziecko, o genialnych zdolnościach ma-
tematycznych. W wieku 22 lat miał już stopień doktora w dziedzinie ma-
tematyki oraz ukończone wyższe studia na kilku innych kierunkach: fizyki,
chemii i inżynierii chemicznej. Wtedy też podjął pracę w Uniwersytecie
Berlińskim jako najmłodszy docent w historii tej uczelni. Jego prace z tego
okresu, dotyczące pewnych podstawowych zagadnień czystej matematyki
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Jak to się zaczęło 377

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

oraz mechaniki kwantowej – przyniosły mu już wtedy rozgłos i uznanie


środowiska naukowego.
W roku 1930, po śmierci ojca, von Neumann wyemigrował z resz-
tą rodziny do Stanów Zjednoczonych. Znalazł tam miejsce w renomowa-
nym uniwersytecie w Princeton, a następnie w nowo utworzonym tamże
sławnym Institute of Advanced Study, obok m.in. takich naukowych zna-
komitości, jak m.in. Albert Einstein i Kurt Gödel. Z czasem jego naukowe
zainteresowania zwróciły się także w kierunku matematyki stosowanej, co
owocowało świetnymi, nowatorskimi rezultatami w wielu dziedzinach, tak-
że takich, które graniczyły z fizyką i ekonomią.
Naukowa pozycja, ale także chyba i polityczny temperament von
Neumanna sprawiły, że był on zapraszany jako naukowy konsultant do naj-
wyższych politycznych i wojskowych gremiów w USA. W czasie II wojny
światowej należał do najściślejszego kręgu uczonych, którzy mieli nie tyl-
ko wgląd w najtajniejsze rządowe projekty badawcze, ale także wywiera-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

378 15. Von Neumanna komputer z programem w pamięci

li wpływ na decyzje strategiczne i polityczne. Można choćby wspomnieć,


że był jednym z pięciu uczonych powołanych w skład komisji, której po-
wierzono wytypowanie celów do pierwszego uderzenia atomowego na
japońskie miasta w lecie 1945 roku, a także oszacowanie spodziewanych
skutków tego bombardowania. Później von Neumann brał również czyn-
ny udział w projekcie budowy amerykańskiej bomby wodorowej. Nic przy
tym nie wskazuje na to, by skłoniło go to do jakichś głębszych refleksji
czy rozterek sumienia, które były udziałem wielu uczonych, jak np. ojca
amerykańskiej bomby atomowej, Roberta Oppenheimera czy radzieckiego
uczonego Andrieja Sacharowa.
Tak czy inaczej, jest rzeczą całkowicie zrozumiałą, że w okresie II
wojny światowej von Neumann zajmował się wojskowymi projektami
o strategicznym znaczeniu, wymagającymi ogromnego nakładu obliczeń
numerycznych. Był nimi bardzo zainteresowany osobiście jako matematyk
i jednocześnie – jako osoba nadzorująca ich przebieg.

Od prostego kalkulatora do czegoś w rodzaju


własnej roboty ENIAC-a
Przeprowadźmy pewien eksperyment intelektualny. Postarajmy się mianowi-
cie na możliwie prostym modelu wyobrazić sobie drogę rozumowania, która
mogła uczonych i inżynierów z lat czterdziestych XX wieku doprowadzić
do koncepcji, na jakiej są obecnie oparte praktycznie wszystkie komputery.
Weźmy prosty, kilkudziałaniowy kalkulator, na przykład taki, jaki jest
dostępny wśród akcesoriów programowych dostarczanych każdemu użyt-
kownikowi wraz z systemem operacyjnym MS Windows (rysunek 15.2).
Niech on reprezentuje – dla uproszczenia – całą arytmetyczną część ówcze-
snej dziesiętnej maszyny liczącej. To oczywiście założenie całkowicie ahi-
storyczne, ale to przecież tylko ilustracja: chodzi nam tu nie o rzeczywiste,
techniczne rozwiązania, lecz o samą zasadę automatyzacji obliczeń.
Ten kalkulator ma dwa rejestry arytmetyczne: jeden widoczny w po-
staci poziomego okienka, w którym pojawiają się dziesiętne liczby oraz
drugi, niewidoczny, w którym zbiera się (akumuluje) wynik w przypadku
dłuższych ciągów operacji. Niech ten pierwszy, który gra tu jednocześnie
rolę rejestru wejścia-wyjścia, nazywa się E (ang. entry), a drugi, ten niewi-
doczny, niech się nazywa A (rejestr akumulatora, ang. accumulator).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Od prostego kalkulatora do czegoś w rodzaju własnej roboty ENIAC-a 379

Rys. 15.2. Prosty kalkulator

Dane wprowadzamy do rejestru E, posługując się klawiaturą z dziesięcioma


cyframi oraz kropką dziesiętną. Naciśnięcie klawisza Backspace powodu-
je wymazanie ostatniej (na przykład omyłkowo wprowadzonej) cyfry, zaś
CE (Clear Entry) służy do zerowania całej aktualnej zawartości okienka E.
Klawisz C (Clear) zeruje natomiast zarówno rejestr E, jak i niewidoczny
akumulator A.
Dodatkowo, kalkulator ma też pamięć. Nazwa dumna, ale cóż to za
pamięć: zaledwie jedna komórka M. Można ją wyzerować, naciskając MC
(Memory Clear), skopiować do niej zawartość rejestru E (MS, Memory Save)
i odczytać zawartość (bez jej niszczenia) do rejestru E (MR, Memory Read).
Naciskając M+, można też spowodować operację arytmetycznego dodawania,
która jest wykonywana jako M + E → M (tj. z zapisaniem wyniku do M).
W pozostałych operacjach arytmetycznych (tzn. z wyjątkiem M+) ar-
gumentami wejściowymi są zawartości rejestrów E oraz A, a wynik zapi-
sywany jest zawsze jednocześnie do E oraz do A. Repertuar takich operacji
obejmuje zmianę znaku (+/–), cztery działania arytmetyczne oraz obliczanie
odwrotności (1/x), procentu (%) i pierwiastka kwadratowego (sqrt). Początek
wprowadzania nowej liczby do rejestru E lub naciśnięcie przycisku CE nisz-
czy dotychczasową zawartość E, ale ostatnio zapisana kopia wyniku pozo-
staje w ukrytym rejestrze A, może więc brać udział w następnej operacji.
Znając rolę rejestrów arytmetycznych oraz pamięci kalkulatora, po
kilku ćwiczeniach jesteśmy w stanie nawet przy użyciu tak prostego narzę-
dzia wykonywać całkiem niebanalne obliczenia, wykraczające poza poje-
dyncze operacje arytmetyczne czy proste sumowanie kilku liczb.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

380 15. Von Neumanna komputer z programem w pamięci

Wyobraźmy sobie na przykład, że z jakichś powodów chcemy obli-


czyć wartość

123, 52 + 432,152 .

Pamiętając, że pierwiastek kwadratowy – to sqrt (od ang. square root), mo-


żemy to obliczenie wykonać w następujących kilkunastu krokach:
1. C
2. MC
3. Wprowadź 123,5
4. *
5. =
6. MS
7. Wprowadź 432,15
8. *
9. =
10. M+
11. MR
12. sqrt
13. Odczytaj wynik w okienku E
Jest to oczywiście coś w rodzaju prostego programu obliczeń. Jeśli
takie obliczenie chcemy wykonać raz lub dwa, dla zaledwie kilku konkret-
nych danych liczbowych – wystarczy, że przechowamy ten program na pod-
ręcznej karteczce albo wręcz mamy go w głowie. Gorzej byłoby, gdybyśmy
obliczenie takie jak powyższe, o ogólnej postaci

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

Od prostego kalkulatora do czegoś w rodzaju własnej roboty ENIAC-a 381

wszystkim do sporządzania tablic funkcji, np. trygonometrycznych czy lo-


garytmów. Od czasów maszyn Holleritha automatyzowano na podobnej za-
sadzie statystykę i księgowość. Jeszcze bardziej złożone powtarzalne obli-
czenia numeryczne występują we wszystkich dziedzinach techniki. Wzory
dla najróżniejszych obliczeń (np. konstrukcyjnych czy – jak w przypad-
ku ENIAC-a – balistycznych i meteorologicznych) opracowuje się przecież
właśnie po to, by później projektanci czy specjaliści wojskowi wielokrotnie
z nich korzystali, podstawiając do nich coraz to nowe zestawy danych.
Naturalnym pomysłem było usprawnienie obliczeń przez:
– przygotowywanie danych wcześniej, oddzielnie od samych obliczeń,
– powiększenie pamięci kalkulatora,
– zbudowanie układu sterowania, który wykonywałby raz przygotowany
program wielokrotnie, już bez udziału człowieka.
Nietrudno znaleźć uzasadnienie dla tych pomysłów. Rzeczywiście,
wprowadzanie danych z uprzednio przygotowanych kart dziurkowanych
znacznie przyspiesza sam proces obliczeń i eliminuje popełniane w pośpie-
chu błędy. Kolejne komplety danych można wcześniej, w spokoju przygoto-
wać, sprawdzić i ułożyć w odpowiednim porządku. Do samego urządzenia
liczącego trzeba tylko dobudować czytnik kart oraz – dla wygody – maszy-
nę drukującą wyniki. Ze schematu kalkulatora może natomiast zniknąć dzie-
siętna klawiatura, zastąpiona przez dwa nowe przyciski: Wczytaj (następną
daną) oraz Wydrukuj (zawartość rejestru E).
Potrzeba zwiększenia pamięci jest również oczywista. Już od daw-
na doskonale zdawano sobie sprawę, że w dłuższych, bardziej skompliko-
wanych obliczeniach zapamiętuje się zwykle wiele wyników częściowych.
Ponadto pewne wartości stałe, biorące wielokrotnie udział w obliczeniach,
warto zapamiętać raz a dobrze po to, by ich nie wprowadzać wiele razy od
nowa. Powiększenie pamięci pociąga za sobą jednak pewną komplikację:
jeśli komórek pamięci jest więcej niż jedna – to wraz z wywołaniem każ-
dej funkcji dotyczącej pamięci (a więc MC, MS, MR, M+) należy określić,
której komórki pamięci ta instrukcja dotyczy. Trzeba wtedy podać numer
komórki, zwany adresem.
Te ulepszenia doprowadziłyby nas zapewne (podobnie jak konstruk-
torów ENIAC-a) do powstania maszyny liczącej, której zasada jest pokaza-
na na rysunku 15.3.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

382 15. Von Neumanna komputer z programem w pamięci

Największe wyzwanie stanowi układ sterowania. Powinien on umoż-


liwiać utrwalenie programu obliczeń tak, by można go było potem wielo-
krotnie wykonać bez udziału człowieka. Stąd pomysł, by wyposażyć maszy-
nę w panel sterujący, za pomocą którego będzie się ją programowało, a więc
ustawiało kolejność operacji. Jedno z możliwych rozwiązań panelu steru-
jącego jest pokazane schematycznie na rysunku 15.3. Poziome linie odpo-
wiadają poszczególnym operacjom oraz (w dolnej części) adresom komórek

Rys. 15.3. Model zewnętrznie programowanego kalkulatora


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada maszyny z programem w pamięci 383

pamięci, pionowe – kolejnym krokom programu. Wetknięcie wtyczki w od-


powiedni otwór oznacza, że dana operacja ma być w danym kroku wykona-
na. Układ wtyczek na każdej pionowej linii definiuje więc jedną instrukcję
maszynową (inaczej – rozkaz maszynowy). Na rysunku 15.3 zaprogramowa-
no w ten sposób opisaną wcześniej procedurę obliczania pierwiastka kwa-
dratowego z sumy kwadratów dwóch liczb.
Układ sterowania powinien wykonywać cyklicznie, na zmianę, dwie
czynności: pobranie instrukcji, a następnie jej wykonanie. Znów pobranie
i wykonanie, pobranie i wykonanie... i tak do końca programu. Pobranie
instrukcji oznacza przesunięcie się do następnego kroku obliczeń zadanego
na panelu sterowania, zaś wykonanie – ma wywołać w naszym modelu taki
skutek, jak naciśnięcie odpowiedniego przycisku kalkulatora. Te dwie po-
wtarzające się czynności tworzą cykl rozkazowy (ang. instruction cycle).
Oczywiście, w praktyce panel sterowania powinien być znacznie
dłuższy, a jego techniczne rozwiązanie mogłoby być inne. Zamiast tablicy
i wtyczek moglibyśmy np. użyć dwupołożeniowych wyłączników albo – jak
w maszynie Mark I – wielokanałowej taśmy perforowanej, którą można by
nawet sklejać w kółko. Zapewne potrzebne byłyby jakieś dodatkowe prze-
łączniki i pokrętła, np. do ustawiania wartości stałych. Nie wdawajmy się
jednak w techniczne szczegóły: to przecież i tak tylko uproszczony model.
Patrząc na schemat z rysunku 15.3, można zrozumieć, jak wielkie
kłopoty sprawiało programowanie takiej maszyny. Przy każdej zmianie pro-
gramu trzeba było zatrzymać maszynę i ręcznie ustawić na nowo dziesiąt-
ki lub setki wtyczek, przełączników i kluczy. Bardzo droga maszyna przez
ten czas próżnowała. Długość programu była ograniczona rozmiarem panelu
sterującego. Realizacja rozgałęzień warunkowych i pętli w programie wy-
magała ręcznej interwencji operatora, który przestawiał lub cofał układ ste-
rowania do właściwego kroku programu itd. Nic dziwnego, że projektanci
ENIAC-a zastanawiali się nad radykalnym ulepszeniem maszyny.

Zasada maszyny z programem w pamięci


Koncepcja maszyny von Neumanna jest bardzo prosta, tak jak proste są za-
zwyczaj pomysły genialne. Jej istotę można streścić w trzech punktach:
– program w pamięci,
– licznik rozkazów,
– dwójkowe kodowanie zarówno instrukcji maszynowych, jak i danych.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

384 15. Von Neumanna komputer z programem w pamięci

Tak więc, po pierwsze – powiedział von Neumann – przyjmijmy zasa-


dę, że w czasie wykonywania program nie będzie mechanicznie zakodowa-
ny w zewnętrznym panelu sterowania, lecz będzie znajdował się w pamięci
maszyny, tej samej pamięci, której używamy do przechowywania danych
wejściowych oraz częściowych i końcowych wyników.
Już to założenie zaskakiwało wtedy śmiałością. Maszyny liczące
dysponowały wówczas zaledwie kilkunastoma czy kilkudziesięcioma ko-
mórkami pamięci, wykorzystywanymi wyłącznie do przechowywania liczb
dziesiętnych. Dla wizji von Neumanna nie stanowiło to jednak przeszkody.
Trzeba po prostu maszynę wyposażyć w większą pamięć. To zadanie dla
inżynierów. Jeśli im się powiedzie, to może niezadługo maszyna licząca bę-
dzie miała kilkaset albo nawet – strach pomyśleć – kilka tysięcy komórek
pamięci? Załóżmy w każdym razie, powiedział von Neumann, że jest to
możliwe.
Jeśli tak, to przyjmijmy, że kolejne rozkazy maszynowe będą rezydo-
wały w kolejnych komórkach pewnego obszaru pamięci. Pozostałe komórki
pamięci mogą być – jak poprzednio – wykorzystywane do przechowywania
numerycznych danych, stałych i wyników cząstkowych.
Oczywiście, rozkazy maszynowe muszą być jakoś zakodowane, ale
tym zajmiemy się później. Będzie się je wprowadzało do pamięci przed
uruchomieniem programu tak, jak dotąd wprowadzano arytmetyczne dane:
przez wczytywanie z uprzednio przygotowanych kart lub taśmy. Dzięki temu
można będzie przygotowywać program wcześniej, bez pośpiechu, a przede
wszystkim – bez konieczności unieruchamiania całej maszyny na czas usta-
wiania owych dziesiątek przełączników i wtyczek. Co więcej, można będzie
wykorzystywać w programie pewne gotowe fragmenty, wzięte z już wcześ-
niej opracowanych programów. Trzeba je tylko odpowiednio „wtasować”
pomiędzy karty nowego programu. Z czasem może w ten sposób powstać
cała biblioteka takich „rutynowych” podprogramów (ang. routines), goto-
wych do wykorzystania.
Po drugie, powiada von Neumann, dodajmy do zestawu rejestrów
maszyny nowy, dodatkowy rejestr, zwany licznikiem rozkazów (lub liczni-
kiem programu, ang. program counter, w skrócie PC). Jego rola jest zupeł-
nie podstawowa. W czasie wykonywania rozkazu rejestr PC przechowuje
numer (adres) komórki pamięci, z której ma być pobrany do wykonania już
następny rozkaz maszynowy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada maszyny z programem w pamięci 385

Rys. 15.4. Podstawowy cykl rozkazowy maszyny z licznikiem rozkazów

Wspomnieliśmy wyżej, że w czasie wykonywania programu układ sterowa-


nia maszyny w sposób cykliczny wykonuje dwie czynności: pobranie in-
strukcji i wykonanie instrukcji. Teraz, po wprowadzeniu licznika rozkazów,
cykl rozkazowy trzeba uzupełnić o trzecią czynność: zwiększanie zawar-
tości licznika PC o jeden. Niech więc teraz cykl rozkazowy wygląda jak
na rysunku 15.4. Dlaczego w takiej właśnie kolejności? Przekonamy się
wkrótce, że jest to rozwiązanie bardzo celowe i pomysłowe.
Dalej, skoro licznik rozkazów jest już jednym z rejestrów – uzupeł-
nijmy repertuar operacji procesora o kilka podstawowych działań na jego
zawartości, jak gdybyśmy dodali kilka nowych przycisków funkcyjnych.
Wystarczą choćby takie funkcje, jak Zeruj PC, Zapisz PC, Odczytaj PC.
Będzie ich można użyć w programie podobnie jak każdej innej instrukcji,
dotyczącej rejestrów arytmetycznych lub komórek pamięci. Jednak skutki
będą inne niż w przypadku zwykłych rozkazów arytmetycznych.
Powiedzmy dla przykładu, że w pewnym programie instrukcja za-
pisana w komórce numer 150 nakazuje wyzerowanie licznika PC. Po za-
kończeniu wykonywania poprzedzającej ją instrukcji nr 149 rozkaz ten
(o adresie 150) zostanie pobrany z pamięci i wprowadzony do układu ste-
rowania. Zgodnie z nową zasadą cyklu rozkazowego, licznik rozkazów zo-
stanie zwiększony o 1 i przybierze wartość 151, a dopiero potem nastąpi
wykonanie pobranego przed chwilą rozkazu. Jak założyliśmy, spowoduje to
w rezultacie wyzerowanie licznika PC.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

386 15. Von Neumanna komputer z programem w pamięci

Ponieważ następną instrukcję pobiera się według aktualnej zawarto-


ści PC, następna instrukcja zostanie pobrana nie z komórki 151, tylko z ko-
mórki o numerze 0. Instrukcja nr 150 jest więc instrukcją skoku (ang. jump
instruction). Przerwała ona sekwencję rozkazów z kolejnych, następujących
po sobie komórek (…148…149…150) i spowodowała skok do instrukcji
z komórki 0. Oczywiście, instrukcja spod adresu 0 zostanie teraz pobrana
i wykonana, po niej instrukcja z komórki 1, potem 2, 3, ... i tak dalej dopó-
ty, dopóki któraś z kolejnych instrukcji skoku znów nie zmieni zawartości
licznika rozkazów.
To jest bardzo mocny mechanizm. W powyższym przykładzie, dla jego
prostoty, rozkaz skoku polegał na wyzerowaniu PC. Ale również każda inna
instrukcja, która powoduje zapisanie do PC nowej zawartości, jest w efekcie
też rozkazem skoku. Powiedzmy, że w komórce pamięci o numerze 50 znaj-
duje się liczba 33. Instrukcja o treści M50 → PC (zapisz do PC zawartość
komórki nr 50) spowoduje w rezultacie skok do rozkazu z komórki 33.
Co więcej, jeśli PC jest jednym z rejestrów procesora, to można na
jego zawartości wykonywać operacje arytmetyczne. Jeśli wykonamy opera-
cję o treści M50 + PC → PC (załóżmy, że jest taka na naszej liście rozka-
zów), to spowodujemy przeskoczenie (ominięcie) trzydziestu trzech rozka-
zów, znajdujących się bezpośrednio za instrukcją skoku itd.
Zawartość użytej w powyższym przykładzie komórki M50 nie musi
być z góry zadaną stałą. Może ona być sama wynikiem jakiegoś oblicze-
nia poprzedzającego skok. Tak więc adres skoku (lub „przeskoku” o pew-
ną liczbę komórek) może być uprzednio wyliczany przez ten sam program,
w którym rozkaz skoku będzie za chwilę użyty. Program może w ten sposób
mieć wpływ na swój dalszy przebieg.
Mało tego. Zasada, że licznik rozkazów jest zwiększany przed, a nie po
wykonaniu instrukcji, została wprowadzona nieprzypadkowo. Dzięki temu,
kosztem stosunkowo niewielkiej modyfikacji układu sterowania, możemy
dołączyć do repertuaru instrukcji maszynowych rozkazy skoku warunkowe-
go (ang. conditional jump). Zgodnie z nazwą zapisanie (w fazie wykonania
instrukcji) nowej zawartości do licznika rozkazów dokonuje się w nich je-
dynie wtedy, gdy spełniony jest pewien wskazany warunek. W przeciwnym
przypadku w rejestrze PC pozostaje stara zawartość, a więc nadal wskazuje
on na następną instrukcję.
Dla przykładu powiedzmy, że umieszczony w komórce 150 rozkaz
zerowania licznika rozkazów jest warunkowy, a badanym warunkiem jest
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada maszyny z programem w pamięci 387

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.

Rys. 15.5. Sieć działań z pętlą i jej realizacja w języku maszynowym

Po wykonaniu części 1 badany jest pewien warunek C. Jeśli jest on spełnio-


ny, to ma być wykonana część 3 algorytmu. Jeśli nie – algorytm zawraca
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

388 15. Von Neumanna komputer z programem w pamięci

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

Zasada maszyny z programem w pamięci 389

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

390 15. Von Neumanna komputer z programem w pamięci

Każda instrukcja ma binarnie zakodowaną część operacyjną oraz


część adresową. Pierwsza z nich identyfikuje rodzaj operacji i jednocześ-
nie określa, w jaki sposób należy interpretować pozostałą część rozkazu:
czy rzeczywiście jako adres komórki w pamięci, czy jako numer urządzenia
zewnętrznego, czy adresy rejestrów itd. Układ sterowania dekoduje część
operacyjną i dalej postępuje w zależności od wyniku: inaczej w przypadku
rozkazów dotyczących pamięci, inaczej – w przypadku rozkazów dotyczą-
cych tylko procesora, urządzenia zewnętrznego i tak dalej. W następnym
rozdziale powiemy o tym więcej.
Tu pojawia się pewna wątpliwość. Podstawową cechą maszyny z pa-
miętanym programem miało być to, że ciągi rozkazów przebywają w tej
samej pamięci co dane. Jednak okazało się, że rozkazy wygodnie koduje się
dwójkowo, a pamięć była do tej pory projektowana zawsze z myślą o prze-
chowywaniu dziesiętnych danych. Co więcej, na przestrzeni paru wieków in-
żynierowie i uczeni włożyli już wiele wysiłku i pomysłowości w budowanie
urządzeń, zdolnych do zapamiętywania dziesiętnych liczb. Bębny o dziesię-
ciu położeniach, dziesiętne przekładnie zębate, dziesięciopołożeniowe prze-
łączniki i wybieraki telefoniczne, układy elektroniczne o dziesięciu stanach,
karty perforowane o dziurkach w dziesięciu możliwych miejscach w jednej
kolumnie... Cały arytmometr był też dziesiętny, przystosowany do współ-
pracy z takimi właśnie elementami pamięci. Może więc – dla zachowania
całego tego dorobku – również instrukcje i adresy kodować w oparciu o sys-
tem dziesiętny?
Von Neumann i jego współpracownicy podjęli odważną i doprawdy
epokową decyzję. Nie, powiedzieli, przeciwnie. Ujednolicenie powinno po-
legać na konsekwentnym zastosowaniu wspólnego, ale właśnie dwójkowe-
go systemu kodowania wszystkiego: rozkazów, adresów i danych, a także
wykonywanych na nich operacji. Również cała pamięć maszyny musi być
skonstruowana tak, by można było w niej przechowywać informacje za-
kodowane nie dziesiętnie, lecz binarnie. W konsekwencji arytmometr też
powinien być binarny.
Powinniśmy docenić śmiałość tej decyzji. Wówczas wszelkie kon-
strukcje maszyn liczących trzymały się kurczowo założenia o dziesiętnej
naturze danych, dziesiętnych tabliczek dodawania i mnożenia itd. Nie były
nawet jeszcze dobrze opracowane zasady dwójkowej reprezentacji liczb sta-
ło- i zmiennoprzecinkowych, reguły dwójkowej arytmetyki itd. O binarnych
kodach alfanumerycznych wiedziano tyle, że w telekomunikacji używa się
kilkubitowych dwójkowych kodów do przesyłania dalekopisowych tekstów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada maszyny z programem w pamięci 391

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

392 15. Von Neumanna komputer z programem w pamięci

Co więcej, wszystko się wprawdzie rozpoczęło od obliczeń numerycz-


nych i dwójkowej konwersji dziesiętnych liczb, ale wkrótce uświadomiono
sobie, że na identycznej zasadzie można przekładać na wewnątrzmaszyno-
wą dwójkową postać nie tylko liczby, ale też wszelkie inne formy infor-
macji: teksty, dźwięki, obrazy. Dzięki temu można je pamiętać w tej samej
komputerowej pamięci i przetwarzać przy użyciu tych samych dwójkowych
podzespołów. Należy tylko wyposażyć maszynę w odpowiednie urządzenia
zewnętrzne (wraz z obsługującym je oprogramowaniem sterującym), które
dokonywałyby konwersji informacji z takiej postaci, jaką umie i lubi posłu-
giwać się człowiek – na postać dwójkową, obowiązującą wewnątrz maszy-
ny.
I dokładnie tak się dzieje we wszystkich komputerach aż do dnia dzi-
siejszego.

Kilka słów o programowaniu maszyn z pamiętanym


programem
Komentarza wymaga jeszcze sposób programowania takiej maszyny nowego
typu. W maszynie zewnętrznie programowanej sprawa była zrozumiała: wty-
kało się odpowiednie wtyczki i ustawiało przełączniki. Teraz – przynajmniej
na samym początku historii komputerów z pamiętanym programem – trzeba
było wypełnić zerami i jedynkami pokratkowany arkusz, w którym wiersze
odpowiadały poszczególnym instrukcjom, zaś ściśle oznaczone kolumny
zawierały część operacyjną i adresową każdego rozkazu. Wiersze były po-
numerowane: numery te były jednocześnie adresami komórek pamięci, do
których miały trafić kolejne rozkazy. W rezultacie taki arkusz programowa-
nia przypominał prawą część rysunku 15.5, tyle że wypełnioną szczegółową
zero-jedynkową zawartością1.
Byłaby to żmudna praca gdyby nie to, że i do niej bardzo szybko
zaprzęgnięto komputer, a ściślej mówiąc – systemowy program, którego za-
daniem było wczytywanie plików kart. Potrzeba istnienia takiego programu

1 Oczywiście, zawartość arkusza programowania była jeszcze przenoszona na karty


dziurkowane. Zajmowały się tym zazwyczaj pracownice, pełniące podobną funk-
cję jak maszynistki w biurach i urzędach, przepisujące na maszynie rękopisy do-
kumentów.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Kilka słów o programowaniu maszyn z pamiętanym programem 393

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

394 15. Von Neumanna komputer z programem w pamięci

Za tymi ulepszeniami poszły następne, jak np. możliwość dołącza-


nia całych gotowych podprogramów i wiele innych, których nie będziemy
tu omawiać. W każdym razie programowanie szybko przestało być sztuką
wypełniania zero-jedynkowych arkuszy, ale początkowo wciąż jeszcze pro-
gramista układał (choć z wieloma mnemotechnicznymi ułatwieniami) kod
maszynowy wprost, rozkaz po rozkazie.
Po niedługim czasie użytkownicy komputerów zaczęli domagać się
tego, by programista nie musiał w ogóle znać listy rozkazów maszynowych,
nazw i roli rejestrów procesora itp. Inżynier czy fizyk powinien móc zapisy-
wać swoje procedury obliczeniowe w sposób możliwie zrozumiały dla siebie,
natomiast maszyna (a ściślej odpowiedni program tłumaczący) powinna sama
przekształcić matematyczną formułę na ciąg rozkazów maszynowych. Dla
przykładu, chodziło o to, by chcąc obliczyć pierwiastek kwadratowy z sumy
dwóch kwadratów, użytkownik mógł napisać następujący ciąg znaków:
z = sqrt(x^2 + y^2)

rozumiejąc, że x, y oraz z są nazwami zmiennych, ^2 oznacza podnoszenie


do kwadratu, zaś sqrt – pierwiastek kwadratowy. Na podstawie tak zapisa-
nej formuły program tłumaczący (translator) powinien wytworzyć – już bez
udziału programisty – ciąg rozkazów maszynowych zbliżony do tego, który
omówiliśmy poprzednio i nawet zaprogramowaliśmy za pomocą wtyczek
na rysunku 15.3.
Kierując się taką motywacją już w latach pięćdziesiątych XX wie-
ku opracowano język FORTRAN (od translator formuł, ang. FORmula
TRANslator), ukierunkowany właśnie na automatyczne tłumaczenie formuł
matematycznych, zapisywanie złożonych obliczeń numerycznych: inżynie-
ryjnych, fizycznych itd. W podobnym czasie powstał inny język, COBOL
(ang. COmmon Business-Oriented Language), ukierunkowany – jak nazwa
wskazuje – na symboliczne zapisywanie obliczeń właściwych dla biznesu
i administracji. Oczywiście, towarzyszyły temu odpowiednie, stopniowo
coraz bardziej zaawansowane programy tłumaczące – translatory. Były to
pierwsze szerzej rozpowszechnione języki programowania wyższego po-
ziomu (ang. high level programming languages), uwalniające programistę
od konieczności myślenia w kategoriach rejestrów, komórek pamięci i listy
rozkazów maszynowych.
Oczywiście, przetwarzanie programów napisanych w językach wy-
sokiego poziomu wymaga od translatora znacznie większych możliwości
niż te, którymi charakteryzował się asembler. Translator musi najpierw
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Kilka słów o programowaniu maszyn z pamiętanym programem 395

sprawdzić, czy dany ciąg znaków jest leksykalnie i składniowo poprawny:


inaczej nie da się z niego skonstruować sensownego programu wynikowe-
go. Następnie musi dokonać rozbioru składniowego, wyróżnić w zadanym
ciągu znaków operacje i ustalić ich kolejność, argumentom tych operacji
przypisać adresy w pamięci, a następnie – wyprodukować sekwencję roz-
kazów maszynowych, już tak skonstruowanych, by później, na etapie wy-
konywania programu, układ sterowania maszyny nie miał trudności z ich
rozpoznaniem i wykonaniem.
Koniec lat pięćdziesiątych i lata sześćdziesiąte XX wieku – to początek
okresu intensywnego rozwoju języków programowania i odpowiadających
im translatorów. Powstał cały dział wiedzy, zwany teorią translacji języków
programowania, formułujący w oparciu o pojęcia lingwistyki matematycznej
metody definiowania języków wyższego poziomu, a także ich rozbioru skła-
dniowego, badania poprawności leksykalnej i składniowej, a wreszcie gene-
rowania i optymalizacji programów wynikowych. Badania nad tymi zagad-
nieniami trwają zresztą do dziś. W rezultacie, w ogromnej większości przy-
padków program maszynowy jest obecnie tworzony przez translator języka
programowania, a nie bezpośrednio przez człowieka – programistę.
Jednak możliwość samodzielnego konstruowania programu maszyno-
wego zachowano do dziś. Dla każdego typu procesora wciąż istnieje język
asemblerowy (ang. assembly language), precyzujący reguły zapisywania
mnemotechnicznych kodów operacji, formę adresów symbolicznych, tryby
adresowania itd. Odpowiada mu oczywiście program tłumaczący – asembler
– który nadal umożliwia programiście składanie programów z poszczegól-
nych, zaprojektowanych przez niego instrukcji maszynowych.
Choć nie jest to dzisiaj powszechny sposób programowania, jednak
stosuje się go w szczególnych przypadkach, zwłaszcza przy implementacji
samych translatorów lub elementów oprogramowania systemowego, głów-
nie tam, gdzie programiście zależy na bardzo szczegółowej kontroli czasu
wykonywania programu.
Warto także wspomnieć, że oba wyżej wymienione języki: FORTRAN
i COBOL, choć już nieomal starożytne w informatycznej skali czasu – są
(po wielu ulepszeniach i modyfikacjach) używane do dnia dzisiejszego.
Powód jest jasny: przez dziesięciolecia zgromadziły się wielkie biblioteki
napisanych z użyciem tych języków procedur z najróżniejszych dziedzin,
które wciąż znajdują zastosowanie. Oczywiście, istnieją także setki bardziej
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

396 15. Von Neumanna komputer z programem w pamięci

współczesnych języków programowania, uniwersalnych i specjalizowanych


– i wciąż powstają nowe.
Wraz z gromadzeniem się doświadczenia w dziedzinie programowa-
nia komputerów zaczęto szybko dostrzegać, że jedne programy (mówimy
o programach źródłowych, a więc w takiej postaci, jak pisze je programista)
są napisane lepiej, a inne – gorzej. Jedne są czytelne (i to nie tylko dla au-
tora), a inne – przeciwnie: są dziwaczne, niezrozumiałe, trudno dzielą się na
logiczne części itd. Pierwsze łatwiej poddają się testowaniu i wyszukiwaniu
w nich błędów, łatwiej się je rozbudowuje i modyfikuje, łatwiej jest też na-
uczyć przyszłego użytkownika posługiwania się takim programem. Drugie
– potrafią być dla użytkownika zmorą, a jeśli ich autor zmieni pracę lub
zachoruje i nie może służyć poprawkami czy poradą – mogą się stać wręcz
praktycznie bezużyteczne.
Zaczęto zastanawiać się, jak można by sprecyzować zasady dobrego
programowania. Proponowano różne zalecenia co do zasad dzielenia progra-
mu na części, co do unikania pewnych konstrukcji programowych (np. roz-
kazów skoków innych niż skok do podprogramu), stosowania właściwych
struktur danych, nazewnictwa zmiennych itd. Szybko w językach programo-
wania pojawiły się konstrukcje językowe ułatwiające (a nawet w pewnym
stopniu wymuszające) stosowanie tych zasad w praktyce.
Po pewnym czasie takie zasady poprawnego programowania (i odpo-
wiadające im języki) upowszechniają się i większość programistów zaczyna
się do nich stosować. Mówimy wtedy, że wykształcił się pewien paradygmat
programowania: zbiór zasad postępowania, metod i narzędzi, dość po-
wszechnie przyjęty w środowisku informatyków i narzucający pewien styl
programowania.
W miarę upływu czasu zmieniają się jednak i potrzeby użytkowni-
ków, i możliwości komputerów, i wiedza o algorytmach i metodach transla-
cji, i złożoność projektowanego software’u. W ślad za tym proponowane są
coraz to nowe zasady tworzenia programów i nowe paradygmaty programo-
wania oraz właściwe dla nich języki programowania i narzędzia programo-
we ułatwiające programiście pracę.
Na początku istnienia programowanych komputerów programowanie
miało charakter imperatywny (ang. imperative programming): program był
sekwencją instrukcji, które programista nakazywał maszynie wykonać.
Budowa programu była sprawą pomysłowości i gustu programisty. W la-
tach sześćdziesiątych XX wieku wykształcił się pewien styl programowania
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Inżynieria oprogramowania i inżynieria systemowa 397

imperatywnego, a mianowicie programowanie strukturalne (ang. structured


programming). Kładziono w nim nacisk głównie na przejrzystość struktury
programu, tak, by składał się on z głównego wątku i wyraźnie wyróżnio-
nych (być może zagnieżdżających się) podprogramów i funkcji. Równolegle
(i później) proponowano inne paradygmaty, jak programowanie deklaratyw-
ne (declarative p.), logiczne (logic p.), funkcjonalne (functional p.) zdarze-
niowe (event driven p.), współbieżne (concurrent p.), aspektowe (aspect p.)
itp.
W latach siedemdziesiątych i osiemdziesiątych zaczęły się pojawiać
pomysły (polegające często na reaktywacji idei jeszcze starszych), które
– po latach konkurowania i ewolucji – doprowadziły do wykrystalizowania
się zasad programowania obiektowego (ang. object oriented programming).
Tu w przetwarzaniu biorą udział zdefiniowane przez programistę abstrak-
cyjne obiekty, z których każdy jest spakowanym w jedną całość pewnym
fragmentem danych wraz z zestawem operacji czy funkcji, które na tych
danych się wykonuje.
Niektóre z operacji polegają na komunikacji z innymi obiektami,
a same obiekty mogą tworzyć pewne hierarchie, pewne układy podobieństw
i pokrewieństw itd. Tworząc program źródłowy, człowiek wyobraża sobie,
że przetwarzanie polega na interakcjach między obiektami, a rozbudowa
programu to tworzenie i dołączanie nowych obiektów. Oczywiście, transla-
tor przekształca potem program źródłowy na postać maszynową, to znaczy
znów na ciąg maszynowych instrukcji i danych, którego budowy i treści
sam twórca programu nigdy nawet nie zobaczy, ale który komputer wykona,
bo od tego jest.
Jak się wydaje, programowanie obiektowe jest obecnie paradygma-
tem dominującym, co nie znaczy, że wiecznym ani jedynym możliwym.
Niestety, w ramach tak wstępnej książki jak ta – nie będziemy w stanie
powiedzieć o tym więcej.

Inżynieria oprogramowania i inżynieria systemowa


Warto uświadomić sobie, że programowanie już dawno przestało być wyłącz-
nie indywidualnym procesem twórczym, do którego wystarczy sprawny umysł,
kartka, ołówek i znajomość wybranego języka programowania. Oczywiście,
nadal są w cenie specjaliści czy wynalazcy, przedstawiający pomysły nowych
zastosowań, nowych algorytmów, struktur danych i programów. Jednak pro-
fesjonalne przygotowanie złożonego produktu programowego, z którego będą
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

398 15. Von Neumanna komputer z programem w pamięci

potem korzystać być może miliony użytkowników – przekracza możliwości


pojedynczego, nawet bardzo zdolnego człowieka. W procesie projektowania
i wykonania elementów oprogramowania biorą udział duże zespoły, których
pracę trzeba dokładnie zaplanować i koordynować.
Utalentowany mechanik potrafi zbudować ze szwagrem, w małym
warsztacie, rewelacyjny motocykl czy samochód, który wzbudzi zazdrość na
najbliższym zlocie miłośników motoryzacji. Jednak projektowanie i przygo-
towanie wielkoseryjnej produkcji nowego modelu samochodu – to coś zu-
pełnie innego. Potrzebne są inne maszyny, urządzenia i pomieszczenia pro-
dukcyjne, inny personel, inaczej zorganizowany system dostaw, sprzedaży,
rozliczeń itd. Inaczej wygląda organizacja produkcji, testowanie, kontrola
jakości, serwis, reklama… Inny jest także rząd wielkości nakładów finan-
sowych, które trzeba zainwestować. Wyższy jest poziom ryzyka inwestycji,
ale także wysoki zysk, jeżeli przedsięwzięcie się powiedzie.
Tak samo dzieje się w przypadku produkcji oprogramowania. Nie
wystarczy sam dobry pomysł. Jeśli oprogramowanie ma wytrzymać kon-
kurencję na rynku – trzeba zadbać o możliwie szeroki krąg potencjalnych
użytkowników, poznać (a właściwie przewidzieć, czasami wręcz wmówić)
ich potrzeby, opracować zachęcający łatwością i atrakcyjnością sposób po-
sługiwania się programem, zapewnić zgodność z dziesiątkami norm i prze-
widywanych formatów danych…
Ponadto, oprogramowanie dla banku, centrali telekomunikacyjnej,
większego przedsiębiorstwa handlowego czy biura konstrukcyjnego może
liczyć (w przeliczeniu na konwencjonalny, pisany tekst) kilka milionów
wierszy kodu źródłowego. Nie napisze tego (zwłaszcza w sensownym cza-
sie) jeden człowiek. Mniejsze aplikacje, liczące kilkadziesiąt czy kilkaset
tysięcy wierszy kodu, może by i mogły być dziełem jednego człowieka, ale
liczy się również czas ich opracowywania. Projekt trzeba więc podzielić na
części czy moduły, o dobrze ustalonych zasadach współpracy między nimi.
Ich wykonanie trzeba powierzyć kompetentnym osobom lub zespołom, któ-
re potrafią nie tylko wykonać to zadanie, ale również realistycznie oszaco-
wać potrzebne na to czas i środki.
Moduły trzeba następnie wykonać (tutaj znaczy to: zaprogramować),
a następnie poddać testom, które pozwolą wykryć i usunąć przynajmniej
część nieuchronnych błędów. Dalej, trzeba połączyć poszczególne moduły
w całość i przeprowadzić testy integracyjne, które sprawdzą poprawność ich
współdziałania. A jeszcze próbna eksploatacja, instrukcja dla użytkownika,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Inżynieria oprogramowania i inżynieria systemowa 399

system aktualizacji i modyfikacji produktu, zasady dystrybucji, marketing,


organizacja ośrodka odpowiadania na problemy klientów…
Wszystko to razem sprawia, że od wielu już lat profesjonalna produk-
cja oprogramowania jest uważana za jedną z dyscyplin sztuki inżynierskiej.
Powstał cały dział wiedzy i działalności praktycznej, zwany właśnie inżynierią
oprogramowania (ang. Software Engineering). Ma on swoje procedury, mię-
dzynarodowe normy, techniki definiowania wymagań, planowania i kontroli
procesu realizacji oprogramowania itd. Specjaliści z tej dziedziny posługują się
bardzo zaawansowanymi narzędziami wspomagającymi tworzenie oprogramo-
wania (ang. CASE tools, od Computer Aided Software Engineering t). W tym
przypadku narzędzia te to złożone programy, które pozwalają na uporządko-
wanie procesu tworzenia nowych produktów programowych: od wstępnej de-
finicji wymagań, poprzez podział na moduły, organizację współpracy wielu ze-
społów, kontrolę wersji, testowanie – aż do końcowej dokumentacji produktu
i organizacji procesu usuwania błędów wykrytych w toku jego eksploatacji.
Tak, w tej dziedzinie odeszliśmy doprawdy bardzo daleko od zapeł-
niania arkusza programowania zero-jedynkowym zapisem w maszynowym
kodzie.
Opisany wyżej proces usamodzielniania się programowania jako od-
dzielnej dyscypliny sztuki inżynierskiej miał również ten efekt, że wielu
profesjonalnych programistów, tworzących bardzo zaawansowane programy
aplikacyjne – przestało się w istocie głębiej interesować budową kompute-
rowego sprzętu. Oczywiście, potrafią powiedzieć, jakiego rozmiaru pamięci
RAM wymaga ich oprogramowanie, potrafią oszacować pożądaną rozdziel-
czość ekranu czy prędkość procesora, ale nie zawracają sobie głowy tym,
jak ten procesor działa, ile ma rejestrów, jak wygląda lista instrukcji języka
maszynowego, jak działa system przerwań…
Istnieje jednak ważna grupa cyfrowych systemów, przy których pro-
jektowaniu bardziej szczegółowa wiedza o działaniu komputerowego sprzę-
tu jest wciąż potrzebna. Tak jest w przypadku systemów sterowania w czasie
rzeczywistym (ang. real-time control systems) oraz ich podklasy, systemów
wbudowanych (ang. embedded systems). Sterują one pracą wielkich obiek-
tów przemysłowych, elektrowni i sieci energetycznych, zespołów robotów
przemysłowych itd., a systemy wbudowane – choć ukryte i wyglądem nie-
przypominające konwencjonalnych komputerów – kierują działaniem tysię-
cy najróżniejszych cyfrowo sterowanych urządzeń: od komórkowych tele-
fonów, różnych gadżetów i domowych automatycznych pralek – do kom-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

400 15. Von Neumanna komputer z programem w pamięci

puterowych tomografów, samochodów, samolotów, wojskowych systemów


uzbrojenia, rakiet balistycznych i pojazdów eksplorujących Marsa.
W przypadku typowych domowych i biurowych aplikacji czasowe
aspekty działania systemu nie podlegają szczególnie ostrym ograniczeniom.
Gdy oczekujemy na wyniki wyszukiwania w Internecie – sekunda mniej czy
sekunda więcej nie odgrywa praktycznie roli. W przypadku systemów ste-
rowania w czasie rzeczywistym jest inaczej: sekunda może decydować nie
tylko o poprawności działania systemu i wypełnianiu jego misji, ale nawet
o ludzkim życiu.
Typowe systemy wykorzystywane w biznesie czy w domu posługu-
ją się równie typowymi urządzeniami zewnętrznymi, jak pamięć dyskowa,
drukarka czy modem. W przemysłowych czy wojskowych systemach stero-
wania i systemach wbudowanych różnorodność urządzeń zewnętrznych jest
znacznie większa. Obustronny kontakt z otoczeniem zapewniają czujniki,
zawory, silniki, kamery optyczne i termowizyjne, aparatura biometryczna
i setki typów innych urządzeń wejścia-wyjścia. Każde z nich ma swą jed-
nostkę sterującą, która z jednej strony jest właściwa dla funkcji, jakie dane
urządzenie spełnia, ale z drugiej – powinna być dołączona do systemu w pe-
wien ustandaryzowany sposób.
Z tych powodów systemy czasu rzeczywistego i systemy wbudowane
posługują się innymi systemami operacyjnymi i protokołami komunikacyj-
nymi niż te, których zadaniem jest obsługiwanie zastosowań biznesowych,
baz danych i usług internetowych. Również projektowanie tych systemów
– zarówno ich sprzętowych konfiguracji, jak i oprogramowania – ma swo-
je specyficzne techniki, konwencje i narzędzia wspomagające. Dlatego
oprócz inżynierii oprogramowania istnieje także inżynieria systemowa (ang.
Systems Engineering), która zajmuje się procedurami projektowania i eks-
ploatacji systemów tego typu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

16. Organizacja jednoprocesorowego


systemu komputerowego

O jakim komputerze mowa?


W poprzednim rozdziale powiedzieliśmy – w najogólniejszym zarysie – na
jakiej zasadzie działa komputer z programem przechowywanym w pamięci.
Od czasów, kiedy koncepcja ta się w ogóle zrodziła, upłynęło jednak kil-
kadziesiąt lat i choć owe najbardziej podstawowe założenia von Neumanna
pozostają nadal aktualne, to jednak dzisiejsze komputery różnią się znacz-
nie od swych ówczesnych przodków. Jak jest więc zbudowany i jak działa
współczesny komputer? Postaramy się o tym opowiedzieć w tym i dwóch
następnych rozdziałach.
Najpierw jednak trzeba rozstrzygnąć pewną wątpliwość. Tak jak
w przypadku wszelkich innych urządzeń, choćby na przykład samochodów,
mamy tu do czynienia z ogromną różnorodnością rozwiązań, typów, modeli
i producentów. Inaczej jest zbudowany mały miejski samochód o napędzie
elektrycznym, a inaczej ogromna, wielotonowa wywrotka pracująca w od-
krywkowej kopalni. Podobnie nasz prywatny laptop różni się znacznie od
wieloprocesorowego superkomputera do modelowania zjawisk meteorolo-
gicznych albo od potężnego serwera globalnej wyszukiwarki. O jakim kom-
puterze będziemy więc tu mówić?
Odpowiedź jest wymijająca, ale szczera: o żadnym konkretnym
i o wszystkich jednocześnie. Posłużymy się uproszczonym modelem systemu,
skonstruowanym dla potrzeb niniejszego wykładu. Będzie on miał procesor,
pamięć, urządzenia zewnętrzne, magistralę systemową – słowem, wszystko to,
co już pozwala uznać go za pełnoprawny system komputerowy – a jednocześ-
nie będzie na tyle prosty, by zrozumienie zasad jego działania nie było trudne.
Te zasady odnajdziemy we współczesnych komputerach, choć w rzeczywisto-
ści są one znacznie bardziej skomplikowane. W rozdziale 19 podamy, na czym
polegają te ulepszenia i modyfikacje. Przekonamy się tam, że łatwiej jest je
poznać i zrozumieć, jeśli rozpocznie się właśnie od uproszczonego modelu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

402 16. Organizacja jednoprocesorowego systemu komputerowego

Nasz model komputera będzie – nieprzypadkowo – zbliżony do szes-


nastobitowego minikomputera PDP-11, zbudowanego przez firmę DEC
(Digital Equipment Corporation) na początku lat siedemdziesiątych XX
wieku. Było to już dawno temu, ale z drugiej strony – wówczas już po
ćwierćwieczu ewolucji komputerów z pamiętanym programem, PDP-11
był konstrukcją wyjątkowo dobrze przemyślaną, koncepcyjnie elegancką,
logiczną i przejrzystą. Jej architektura i obowiązujące w PDP-11 zasady
organizacji dobrze sprawdziły się w praktyce. Szybko też zastosowano je
w większych, trzydziestodwubitowych komputerach VAX (również produk-
cji DEC) i zaczęto je powielać w modelach komputerów innych firm, a tak-
że wprowadzać w mikroprocesorach, wytwarzanych przez najważniejszych
producentów układów scalonych o bardzo wielkiej skali integracji (VLSI).

Podstawowy schemat blokowy systemu


Podstawowy model, przedstawiony na rysunku 16.1, składa się z pewnej
liczby jednostek (ang. units), zgrupowanych wokół magistrali systemowej,
nazywanej też szyną systemową (ang. system bus). Magistrala służy do ko-
munikowania się jednostek między sobą, a zarządza nią jednostka arbitrażu
(ang. arbitration unit), którą dalej będziemy w skrócie nazywali arbitrem.

Rys. 16.1. Schemat blokowy systemu z jednym procesorem


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Podstawowy schemat blokowy systemu 403

Mówi się, że jest to model systemu na poziomie PMS (od ang.


Processors, Memories, Switches), co znaczy, że występują w nim nie logiczne
bramki i przerzutniki ani nie poszczególne rejestry i arytmometr, lecz jednost-
ki większe: procesory (processors), bloki pamięci (memories) i „przełącznice”
(ang. switches). Tu rolę takiej „przełącznicy” odgrywa magistrala systemowa.
Technicznie rzecz biorąc, od pojawienia się układów LSI procesor
oraz arbiter rezydują w jednym układzie scalonym. Kiedy dziś mówimy
o mikroprocesorze, mamy na myśli taką właśnie scaloną kostkę czy kawa-
łek plastra krzemowego kryształu (ang. chip), zawierającą oba te elementy
systemu. W rzeczywistości (zwłaszcza w przypadku współczesnych, za-
awansowanych mikroprocesorów) kryją się tam jeszcze inne podzespoły,
jednak pominiemy je na razie, dla zachowania prostoty modelu.
Magistrala systemowa, zaznaczona na rysunku w postaci jednej sze-
rokiej blokowej strzałki – składa się w rzeczywistości z kilkudziesięciu linii,
o których przeznaczeniu i działaniu powiemy za chwilę znacznie więcej.
Każda z linii jest wyprowadzona z mikroprocesora na zewnątrz w postaci
cienkiej jak szpilka nóżki (ang. pin, dosłownie szpilka), o długości kilku
milimetrów. Dodatkowe piny służą celom technicznym (doprowadzenie za-
silania, uziemienia, wyprowadzenie zegara, który też jest umieszczony we-
wnątrz mikroprocesora itd.). W sumie takich nóżek jest więc – w zależności
od typu mikroprocesora – kilkadziesiąt, kilkaset, a nawet (we współczesnych
mikroprocesorach) grubo powyżej tysiąca. W naszym, uproszczonym, wy-
starczyłoby ich kilkadziesiąt.
Mikroprocesor umieszcza się w gnieździe (ang. socket), które ma
oczywiście odpowiednią liczbę odpowiednio rozmieszczonych otworów
ze stykami, służących do pomieszczenia wszystkich nóżek mikroproce-
sora. Gniazdo to jest z kolei umocowane na plastikowej płycie głównej
komputera (ang. motherboard). Metalizowane ścieżki wykonane na tejże
płycie łączą gniazdo mikroprocesora z innymi gniazdami, w które wkłada
się na podobnej zasadzie inne układy scalone lub mniejsze płytki (zwane
kartami), zawierające całe zespoły układów scalonych. Niektóre gniaz-
da są połączone z podzespołami znajdującymi się poza płytą główną.
Wykorzystuje się w tym celu najczęściej szerokie, płaskie przewody, skła-
dające się z wielu żył, różniących się – dla wygody montażu i testowania
– kolorem izolacji.
W ten sposób połączone są ze sobą fizycznie elementy konfiguracji,
przedstawionej na rysunku: procesor (jednostka centralna, ang. CPU, Central
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

404 16. Organizacja jednoprocesorowego systemu komputerowego

Processing Unit), jednostki pamięci RAM, (ang. memory units), jednostki


sterujące (ang. control units) urządzeń zewnętrznych (inaczej – urządzeń
peryferyjnych, ang. peripheral devices) itd. W ramach architektury systemu,
który wzięliśmy za wzór – wszystkie jednostki porozumiewają się z arbi-
trem i między sobą w taki sam, ujednolicony sposób.
Jednostek pamięci RAM jest zazwyczaj kilka. Razem tworzą one
pamięć główną (inaczej: pamięć operacyjną) komputera (ang. main memo-
ry). Jest ona podzielona na ponumerowane (adresowane) komórki i to w niej
właśnie – zgodnie z regułami, które sformułował von Neumann – są prze-
chowywane dane i rozkazy maszynowe, tworzące aktualnie wykonywany
program.
Skrót RAM oznacza pamięć o dostępie swobodnym (ang. Random
Access Memory), to znaczy taką pamięć, w której czas dostępu do poszcze-
gólnych komórek pamięci nie zależy od ich fizycznego rozmieszczenia na
pamięciowym nośniku. Dzięki temu, można w sposób dowolny (a więc – po
angielsku – random) odwoływać się do komórek pamięci o różnych adre-
sach, nie płacąc za to wydłużeniem czasu dostępu do ich zawartości.
Przykładem pamięci, która takiej właściwości nie ma (a więc nie jest
RAM) – jest pamięć dyskowa, w której czas dostępu do następnego sektora
dyskowego zależy od tego, czy znajduje się on na tej samej ścieżce co sektor
poprzednio adresowany. Przejście do innej ścieżki wiąże się z koniecznością
przesuwania głowic odczytująco-zapisujących i oczekiwania na pojawienie
się sektora pod głowicami, co wymaga czasu.
Procesor (CPU) jest tu jeden, ponieważ dla uproszczenia założyliśmy,
że jest to system jednoprocesorowy. O jego wewnętrznej budowie i działa-
niu powiemy znacznie więcej w następnym rozdziale. Na razie wystarczy
jeśli wspomnimy, że we wnętrzu procesora znajduje się między innymi aryt-
mometr i lokalne rejestry (w tym rejestr licznika rozkazów), a także układ
sterowania, który – zgodnie z zasadą cyklu rozkazowego – pobiera z pamię-
ci i wykonuje kolejne instrukcje maszynowe.
Największa różnorodność panuje wśród urządzeń zewnętrznych. Po
pierwsze, są wśród nich stosunkowo powolne urządzenia znakowe (takie
jak np. klawiatura czy drukarka), przystosowane do wysyłania lub przyj-
mowania ciągów alfanumerycznych znaków z szybkością wystarczającą do
komunikacji z człowiekiem.
Dalej, są także urządzenia DMA (od ang. Direct Memory Access, co
oznacza bezpośredni dostęp do pamięci). Urządzenia DMA są z kolei zdolne
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Podstawowy schemat blokowy systemu 405

do bardzo szybkiego przenoszenia całych dużych bloków danych1 pomię-


dzy pamięcią główną a zewnętrznym źródłem lub odbiornikiem danych i to
właśnie bezpośrednio, to znaczy bez udziału procesora. Typowymi urządze-
niami DMA są na przykład jednostki pamięci dyskowej (ang. hard disc),
stanowiące pomocniczą, zewnętrzną pamięć, o pojemności wielokrotnie
większej niż główna pamięć RAM.
Ponadto, do systemu mogą być także dołączone najróżniejsze inne
urządzenia zewnętrzne, na przykład służące do zapisywania i odtwarzania
dźwięku i obrazu czy do współpracy z innymi technicznymi urządzeniami,
np. ze sprzętem telekomunikacyjnym (np. modem, karta sieciowa itd.) lub
oprzyrządowaniem technologicznym (aparatura medyczna, roboty przemy-
słowe, czujniki i elementy wykonawcze w komputerowo sterowanej insta-
lacji produkcyjnej itd.). Każde z tych urządzeń dołącza się do systemu za
pośrednictwem odpowiedniej jednostki sterującej.
Taka architektura systemu, jak na rysunku 16.1, ułatwia opanowanie
różnorodności wśród urządzeń zewnętrznych i umożliwia dołączanie urzą-
dzeń nowych, nawet tych, które będą opracowane dopiero w przyszłości.
Jeśli chcemy dołączyć do systemu nowe urządzenie – musimy wyposażyć
je w jednostkę sterującą, która będzie w stanie wykonywać czynności wła-
ściwe dla danego, konkretnego mechanizmu, ale z drugiej strony będzie się
jednocześnie bezwzględnie stosować do wszystkich konwencji i sygnałów
organizujących wymianę informacji z pozostałymi jednostkami za pośred-
nictwem systemowej szyny. Jeśli ten warunek jest spełniony – to w zasadzie
dowolne urządzenie może podjąć pracę w konfiguracji, zgrupowanej wokół
systemowej magistrali.
Jednostki są w znacznym stopniu samodzielne. Każda z nich może
wykonywać część swoich funkcji lokalnie, niezależnie od pozostałych jed-
nostek, w tym – od procesora. Tak więc na przykład procesor może zle-
cić jednostce pamięci RAM odszukanie i dostarczenie zawartości komórki
o zadanym adresie, a jednostka pamięci samodzielnie to polecenie wykonuje.
Podobnie procesor poleca innej jednostce (na przykład jednostce dyskowej)
wykonanie pewnej złożonej operacji (na przykład sprowadzenie do pamięci
RAM całego bloku danych), którą ta potem zrealizuje itd.
Nie wszystko jednak odbywa się na polecenie procesora. Role mogą
się odmienić. Jednostka sterująca urządzenia (np. wspomnianego modemu)

1 Dlatego są one nazywane również urządzeniami blokowymi.


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

406 16. Organizacja jednoprocesorowego systemu komputerowego

może na przykład – z własnej inicjatywy – zechcieć powiadomić proce-


sor o zerwaniu połączenia telekomunikacyjnego. Procesor powinien wtedy
podjąć odpowiednie działanie, na przykład wykonać program, powodujący
m.in. wyświetlenie na ekranie stosownego komunikatu. Podobnie, jednostka
sterująca dysku (DMA) może polecić jednostce pamięci odczytanie słowa
z komórki o wskazanym adresie. Po otrzymaniu z pamięci żądanego słowa
– jednostka dyskowa powoduje (już lokalnie) zapisanie go na nośniku dys-
kowym itd.
Elementarne operacje przekazywania informacji między dwoma jed-
nostkami mają postać krótkich przesłań, zawierających jedno lub dwa słowa
maszynowe. Takie słowo, odpowiadające rozmiarem jednej komórce pamię-
ci, może zawierać np. binarną daną (którą jednostka pamięci ma zapisać pod
wskazanym adresem), kod operacji (którą ma wykonać u siebie urządzenie
zewnętrzne), kod rodzaju przerwania2 (które procesor powinien jak najszyb-
ciej obsłużyć) itd. – zależnie od intencji zleceniodawcy. Słowu towarzyszy
adres wskazujący, gdzie ma być ono zapisane lub skąd ma być odczytane.
Do przenoszenia takich informacji między jednostkami służy magi-
strala (szyna) systemowa. Wewnątrz, składa się ona z kilkudziesięciu linii,
podzielonych na trzy grupy. Są to:
– linie danych (ang. data lines),
– linie adresowe (ang. address lines),
– linie sterujące (ang. control lines).
Linie danych służą do równoległego przekazywania zawartości słowa.
Każdemu bitowi słowa odpowiada jedna linia, linii danych jest więc tyle, ile
bitów zawiera przekazywane słowo: 16 w komputerze 16-bitowym, 32 w 32-
-bitowym itd. Taką magistralę nazywamy równoległą: wszystkie bity przesy-
łanego słowa pojawiają się na niej jednocześnie, każdy na swojej linii3.

2 O przerwaniach powiemy więcej poniżej, a już znacznie więcej – w rozdziale 18.


3 We współczesnych komputerach wykorzystuje się także kilka typów magi-
stral szeregowych (serial bus). Kolejne bity nie biegną w nich po wielu liniach,
równolegle, obok siebie, lecz są przekazywane po jednej linii, jeden za drugim.
Każdy użytkownik domowego komputera podłącza na przykład mysz, modem,
ruter, pendrive, kamerkę itd. charakterystyczną płaską wtyczką do łącza USB,
a skrót ten oznacza właśnie Universal Serial Bus, czyli uniwersalną magistralę
szeregową. W naszym uproszczonym modelu magistrali szeregowej nie ma.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Pan, sługa i arbiter, czyli jak uzyskać dostęp do magistrali 407

Linie adresowe służą – jak łatwo zgadnąć – do podawania adresu,


spod którego należy dane słowo odczytać lub pod jaki należy je zapisać.
Adres jest oczywiście dwójkową liczbą w kodzie NB, więc jeśli linii ad-
resowych jest n – to można za ich pomocą bezpośrednio zaadresować 2n
komórek pamięci RAM4.
Linie sterujące służą natomiast do organizowania procesu komuniko-
wania się między jednostkami. Ich rolą zajmiemy się teraz dokładniej.

Pan, sługa i arbiter, czyli jak uzyskać dostęp do


magistrali
Należy podkreślić, że taka magistrala systemowa narzuca ważne ogranicze-
nie: w każdej chwili może się za jej pośrednictwem komunikować co najwy-
żej jedna para jednostek. Ponieważ zaś chętnych może być więcej – ruchem
na magistrali zarządza arbiter. To on, korzystając z linii sterujących, odbiera
zgłoszenia i przydziela prawo do korzystania z magistrali, dbając o to, by po
liniach danych i adresowych komunikowała się tylko jedna para jednostek
naraz.
W każdej komunikującej się parze jednostek role są wyraźnie okre-
ślone: jedna jest w danej chwili zleceniodawcą, zaś druga – wykonaw-
cą. Mówi się, że taka komunikacja odbywa się na zasadzie master-slave.
Zleceniodawca jest w danej chwili panem (ang. master), a druga jednostka
– sługą czy niewolnikiem (ang. slave). Operację komunikacji między jed-
nostkami inicjuje i kończy zawsze pan (master), a sługa (slave) ma jedynie
posłusznie wykonywać jego polecenia.
Jak wyżej wspominaliśmy, role te mogą się zmieniać. Jedynie jed-
nostki pamięci RAM nie wykazują własnej inicjatywy i potrafią tylko wy-
konywać polecenia innych, jako słudzy, slaves. Natomiast pozostałe jed-
nostki (procesor, urządzenia DMA oraz inne urządzenia zewnętrzne) mogą
raz odgrywać rolę pana, a kiedy indziej – sługi.
Aby przeprowadzić transfer słowa, zainteresowane jednostki muszą
najpierw zgłosić się do arbitra po zgodę na wykorzystanie magistrali. Arbiter

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

408 16. Organizacja jednoprocesorowego systemu komputerowego

wybiera jedną z nich (zgodnie z pewnymi regułami pierwszeństwa, o któ-


rych jeszcze powiemy) i daje jej przydział szyny (ang. bus grant). Wybrana
jednostka dopiero wtedy staje się panem nad magistralą i (już jako master)
rzeczywiście wyłącznie panuje nad liniami adresowymi i liniami danych.
Otrzymując magistralę we władanie, nowy master ustawia odpowied-
ni poziom napięcia (np. logiczne 1) na jednej z linii sterujących, często
o nazwie BBSY (ang. Bus Busy, szyna zajęta). Następnie master kontaktuje
się z odpowiednim sługą (slave) i przekazuje mu swoje polecenie. Po wy-
konaniu polecenia master ustawia stan linii BBSY z powrotem na 0, czym
sygnalizuje arbitrowi, że zwalnia magistralę. Te czynności tworzą jeden cykl
szyny (ang. bus cycle). Po zakończeniu cyklu szyny natychmiast rozpoczyna
się cykl następny: arbiter wybiera następną oczekującą jednostkę, przydziela
jej szynę... i tak dalej.
Patrząc z tej perspektywy, możemy powiedzieć, że to wcale nie cen-
tralny procesor, lecz arbiter jest właściwie szefem całej tej konfiguracji jed-
nostek. To on odbiera „podania” o przydział magistrali, a następnie udziela
zgody jednemu z petentów, podczas gdy pozostali muszą cierpliwie pocze-
kać na swoją kolej.
Komunikacja między układami sterowania wszystkich jednostek
a arbitrem odbywa się na poziomie sprzętowym, co znaczy, że układy te
porozumiewają się poprzez ustawianie odpowiednich wartości logicznych
(0 albo 1) na łączących je liniach sterujących.
Zasadę tę już znamy z rozdziału o układach sekwencyjnych. Lokalne
układy sterowania wszystkich jednostek (łącznie z arbitrem) możemy bo-
wiem wyobrażać sobie jako sprzętowe automaty skończone, podobne (oczy-
wiście z zachowaniem proporcji co do złożoności) do tego, który projekto-
waliśmy w rozdziale 13 do obsługi szczeliny do wrzucania monet.
Tam również nasz układ sterowania (NS) komunikował się (patrz
rysunek 13.6) z głównym układem sterowania (GUS) całej maszyny. Do
komunikacji służyły dwie linie: gotowe oraz następny. Pierwsza z nich była
linią wyjściową automatu NS, a wejściową dla GUS, druga – przeciwnie.
Komunikacja między automatami polegała na tym, że każdy z automatów
ustawiał na swojej linii wyjściowej odpowiednią wartość logiczną (0 albo
1), a ta była odbierana u partnera jako sygnał wejściowy, który powodował
odpowiednią reakcję: np. zmianę stanu automatu, zmianę wartości logicznej
na linii wyjściowej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Czego pan może wymagać od sługi 409

W taki sam sposób – za pomocą linii sterujących – porozumiewają się


z arbitrem jednostki dołączone do systemowej magistrali.
Ze względu na fizyczne umieszczenie procesora i arbitra w tym samym
scalonym układzie, bezpośrednie negocjacje procesora z arbitrem odbywają się
wewnątrz mikroprocesora, a potrzebne do tego celu linie sterujące (żądanie do-
stępu ze strony procesora i przydzielenie mu szyny) nie są wyprowadzone na
zewnątrz. Nie znaczy to jednak, że procesor ma („po znajomości”) pierwszeń-
stwo przed innymi jednostkami. Przeciwnie, musi się on stosować do reguł
obowiązujących wszystkich. W szczególności, jest on dołączony do wszystkich
linii sterujących, niezbędnych dla współpracy z innymi jednostkami, a także do
tych samych linii danych i linii adresowych co pozostałe jednostki. Musi ich
używać zgodnie z powszechnymi regułami, a czasami – jak zobaczymy – musi
innej jednostce ustąpić pierwszeństwa w dostępie do szyny.
W każdym konkretnym rozwiązaniu magistrali linie sterujące są do-
kładnie wymienione i ponazywane, a towarzyszy temu interpretacja, co
oznacza na danej linii stan 0, a co stan 1. Towarzyszą temu także diagramy
precyzujące, jak zmienia się w czasie stan poszczególnych linii sterujących
w różnych sytuacjach i fazach komunikowania się między jednostkami.
Wszystkie te ustalenia razem tworzą standard danej magistrali (szyny).

Czego pan może wymagać od sługi


Opisaliśmy wyżej zasadę negocjacji między zainteresowanymi jednostka-
mi a arbitrem. W rezultacie, jedna z jednostek otrzymuje od arbitra zgodę
(grant) i przejmuje na jeden cykl szyny panowanie nad systemową magi-
stralą. W jakim jednak celu się o to starała i co zrobi teraz?
Na rysunku 16.2 pokazano schematycznie cztery sytuacje, w których
jednostki systemu mają powód, by komunikować się między sobą.
W sytuacji przedstawionej na rysunku 16.2a procesor (CPU) chce
– jako master – odczytać z pamięci RAM (lub do niej zapisać) zawartość
jednego słowa.
Procesor odwołuje się w ten sposób do pamięci RAM co najmniej
jeden raz w każdym swoim cyklu rozkazowym, a mianowicie po to, by
(według aktualnej zawartości licznika rozkazów) sprowadzić z niej kolejny
rozkaz maszynowy. Jeśli po zdekodowaniu rozkazu okaże się, że dotyczy
on pamięci, to procesor będzie wkrótce (zapewne już w następnym cyklu
szyny) ponownie domagał się dostępu do pamięci: tym razem po to, by
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

410 16. Organizacja jednoprocesorowego systemu komputerowego

Rys. 16.2. Cztery sytuacje komunikacji między jednostkami (M – master, S – slave)

sprowadzić daną potrzebną do wykonania instrukcji albo zapisać do pamię-


ci wynik operacji. Dlatego sytuacja z rysunku 16.2a występuje w praktyce
najczęściej.
Każda jednostka pamięci odpowiada za pewien przedział adresów
RAM. Graniczne wartości tego przedziału są ustawione na stałe w danej
jednostce. W każdej jednostce pamięci istnieje też prosty układ logiczny,
który nieustannie porównuje te wartości graniczne z aktualnym stanem li-
nii adresowych magistrali. Jeżeli adres, wystawiony na liniach adresowych,
mieści się w tych granicach (a więc dotyczy danej jednostki), to w momen-
cie, gdy pojawia się nowy master – jednostka pamięci, która to stwierdziła,
ma obowiązek zgłosić się po przeznaczonej do tego linii sterującej, np. na-
zywającej się SACK (ang. Selection Acknowledge, potwierdź wybór).
Tak więc, gdy procesor chce odczytać z pamięci RAM zawartość ko-
mórki pamięci, to – przyjmując rolę mastera szyny – ustawia żądany adres
na liniach adresowych magistrali, a na odpowiedniej linii sterującej doprecy-
zowuje, że chodzi właśnie o odczytywanie. Pojawienie się nowego mastera
jest – jak już wiemy – sygnalizowane stanem linii BBSY (Bus Busy). Kiedy
więc na linii BBSY pojawia się wartość 1 – wszystkie jednostki sprawdzają,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Czego pan może wymagać od sługi 411

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

412 16. Organizacja jednoprocesorowego systemu komputerowego

z lub do urządzenia zewnętrznego, jeszcze inny – status, czyli stan urzą-


dzenia (np. mówiący, czy jego zasilanie jest włączone, czy – w przypad-
ku CD – dysk jest włożony, czy – w przypadku drukarki – nie brakuje
papieru, czy urządzenie jest w danej chwili aktywne itd.). W przypadku
urządzeń DMA potrzebne są również rejestry adresowe, gdzie przechowy-
wany jest fizyczny adres na dysku, adres zapisu (lub odczytu) do/z RAM
i tak dalej.
Zainicjowanie operacji wejścia-wyjścia polega na zapisaniu przez pro-
cesor (CPU) do rejestrów wybranego urządzenia zewnętrznego odpowiednich
zawartości (kodu operacji, adresów itd.). Procesor może także zechcieć przed
uruchomieniem sprawdzić stan urządzenia, odczytując jego rejestr stanu.
Później, po wykonaniu tych czynności, jednostka sterująca urządzenia po-
dejmie samodzielnie dalsze działania i zapewne w przyszłości zakomunikuje
procesorowi o ich zakończeniu. Wtedy, będzie to sytuacja pokazana schema-
tycznie na rysunku 16.2d. Omówimy ją niżej, gdy nadejdzie na nią kolej.
W wielu maszynach do inicjowania operacji wejścia i wyjścia służą
specjalnie do tego celu przeznaczone maszynowe instrukcje. Problem zapi-
sywania i odczytywania rejestrów urządzeń można jednak – za komputerem
PDP-11 – rozwiązać w inny, bardzo uniwersalny i pomysłowy sposób. Oto po-
szczególnym rejestrom urządzeń zewnętrznych przypisuje się ustalone adresy
z przestrzeni adresowej pamięci RAM. Innymi słowy, umówiona część pamię-
ci operacyjnej – to nie są „prawdziwe” komórki pamięci RAM, lecz oznaczone
ich numerami lokalne rejestry urządzeń zewnętrznych5. Adresy te są na stałe
zapisane w jednostkach sterujących poszczególnych urządzeń. Podobnie jak
jednostki pamięci RAM obserwują one stan linii adresowych i mają obowiązek
zgłosić się jako slave w chwili, gdy nowy master je wywoła.
Dzięki tej konwencji, wszystkie czynności związane z inicjowaniem
operacji w urządzeniach zewnętrznych są wykonywane tak, jak zwykłe za-
pisywanie lub odczytywanie komórek pamięci, tyle tylko że w odpowiedzi
na adres zgłasza się nie pamięć RAM, lecz jednostka sterująca odpowied-
niego urządzenia zewnętrznego. Takie rozwiązanie jest bardzo uniwersalne:
w liście rozkazów maszynowych nie ma oddzielnych, z góry ustalonych in-
strukcji wejścia-wyjścia, co umożliwia konstruowanie i dołączanie do magi-
strali bardzo różnorodnych urządzeń zewnętrznych.

5 Na przykład w PDP-11 przyjęto, że dla rejestrów urządzeń zewnętrznych rezer-


wuje się 4 k najwyższych adresów RAM.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Czego pan może wymagać od sługi 413

Trzeci przypadek komunikacji, z rysunku 16.2c, dotyczy przenosze-


nia danych w trybie DMA (bezpośredni dostęp do pamięci). Wyobraźmy
sobie tę sytuację.
Przyjmijmy, że jednostka dysku twardego, działająca w trybie DMA
(tzn. o bezpośrednim dostępie do pamięci), otrzymuje od procesora zlecenie
odczytania całego bloku danych z dysku i zapisania go do pamięci RAM,
począwszy od pewnego wskazanego adresu. Zapewne wzięło się to stąd, że
w toku wykonywania przez procesor pewnego programu wynikła potrzeba
pobrania z dysku kolejnego fragmentu danych (lub kolejnego odcinka pro-
gramu), liczącego wiele bajtów.
Trzeba zdawać sobie sprawę, że z punktu widzenia procesora taka
operacja trwa przez czas bardzo długi, wystarczający do tego, by procesor
wykonał tymczasem wiele tysięcy instrukcji. Procesor nie czeka więc bez-
czynnie na zakończenie operacji w urządzeniu DMA, lecz zleca całą tę zło-
żoną czynność jednostce dyskowej, a następnie przechodzi do wykonywania
jakiegoś innego procesu, który w tej chwili jest do tego gotowy6. Później,
gdy jednostka DMA potwierdzi, że potrzebne dane (lub program) znalazły
się już w całości w pamięci operacyjnej – procesor będzie mógł wrócić do
wykonywania programu, z którego wyszło zlecenie.
Zleceniu musi towarzyszyć wskazanie fizycznego adresu dyskowego
(numer ścieżki i sektora, długość odczytywanego bloku itd.), a także – adre-
su w pamięci RAM, od którego ma się zacząć zapisywanie danych. Wiemy
już, jak to się robi: wszystkie te niezbędne informacje procesor zapisuje do
kolejnych rejestrów sterujących jednostki dyskowej w ciągu kilku cykli ma-
gistrali, tak jak to omówiliśmy przed chwilą.
Po otrzymaniu zlecenia, jednostka sterująca dysku musi najpierw sa-
modzielnie uruchomić napęd dysku, ustawić głowicę odczytujące nad odpo-
wiednią ścieżką i odczekać do pojawienia się pod głowicą sektora, wskaza-
nego w poleceniu. Trwa to przez czas rzędu setnych sekundy, a więc – jak
wspomnieliśmy – bardzo długo w porównaniu z możliwościami procesora.
Ale w chwili, gdy początek zaadresowanego sektora pojawia się pod głowi-

6 Zarządzaniem taką kolejką programów, oczekujących na dostęp do procesora,


zajmuje się odpowiednia część systemu operacyjnego komputera. Poświęcimy
temu więcej miejsca dalej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

414 16. Organizacja jednoprocesorowego systemu komputerowego

cami odczytującymi – sytuacja zmienia się dramatycznie: również jednostka


sterująca dysku musi zacząć pracować bardzo szybko.
Głowica czyta ze ścieżki kolejne bity danych (szeregowo, bit po bi-
cie) i zapisuje je kolejno w odpowiednim rejestrze buforowym jednostki
dyskowej. Rozmiar tego rejestru odpowiada wielkości słowa w pamięci
RAM: jeśli słowa są 16-bitowe, to i rejestr buforowy jest 16-bitowy itd. Po
skompletowaniu pełnego słowa trzeba je teraz zapisać do pamięci RAM pod
wskazany w zleceniu adres.
Tak, ale żeby to zrobić – trzeba zwrócić się do arbitra o przydział ma-
gistrali, uzyskać grant, wywołać odpowiedni blok pamięci RAM, poczekać
na jego zgłoszenie się jako slave... A tymczasem dysk kręci się, a głowica
czyta z niego nieprzerwany strumień bitów. Odczytaliśmy dopiero pierwsze
16 bitów, co więc zrobić z następnymi?
Gdybyśmy przerwali odczytywanie strumienia bitów z dysku aż do
czasu uzyskania dostępu do magistrali i opróżnienia rejestru buforowego,
to moglibyśmy wznowić kompletowanie nowego słowa dopiero po całym
obrocie dysku, gdyż dopiero wtedy dalszy ciąg sekwencji bitów znajdzie się
ponownie pod głowicą odczytującą. To byłaby niedopuszczalna strata cza-
su: cóż to za pamięć dyskowa, która potrafi odczytać i przesłać do pamięci
tylko jedno słowo na jeden obrót dysku?7
Trzeba zastosować inne rozwiązanie: zacząć zapisywać dalszy ciąg
biegnącego strumienia bitów w drugim rejestrze buforowym, mając nadzie-
ję, że zanim skompletuje się to drugie pełne słowo – zawartość pierwszego
bufora zdążymy wyekspediować do pamięci RAM, wyzerować ten bufor
i zwiększyć adres zapisu do RAM o jeden. Po skompletowaniu drugiego
słowa – przekierujemy biegnący strumień bitów z powrotem do pierwszego,
już zwolnionego bufora i podejmiemy znów zapisywanie drugiego słowa do
RAM itd. W ten sposób, przełączając się na zmianę od jednego do drugiego
bufora i odpowiednio szybko zapisując kolejne skompletowane słowa do
RAM – będziemy w stanie obsłużyć bardzo szybki transfer danych z dysku
do pamięci operacyjnej.

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

Czego pan może wymagać od sługi 415

Analogiczne zasady obowiązują przy przenoszeniu danych w drugim


kierunku: z pamięci RAM do dysku. Jednostka sterująca dysku sprowadza
słowo z pamięci do swojego buforowego rejestru. W czasie, gdy jest ono
zapisywane szeregowo, bit po bicie na powierzchni obracającego się nośni-
ka – żąda dostępu do magistrali i sprowadza następne słowo do drugiego
rejestru buforowego. Potem rejestry zamieniają się rolami: z drugiego się
zapisuje, do pierwszego w tym czasie sprowadza i tak dalej.
Jest rzeczą zrozumiałą, że w tej sytuacji żądania dostępu do magistrali
kierowane do arbitra przez jednostkę DMA muszą być załatwiane w pierw-
szej kolejności. Dlatego jednostki DMA zgłaszają się do arbitra po wyróż-
nionej linii (nazywającej się np. NPR, ang. Non-Processor Request, żądanie-
-nie-od-procesora). Ma ona priorytet wyższy nawet od tego wewnętrznego
połączenia, w którym o rolę mastera ubiega się sam procesor. Tak więc,
jeśli o panowanie nad szyną ubiega się jednocześnie procesor i jednostka
DMA – arbiter przydziela magistralę z zasady nie procesorowi, lecz właśnie
jednostce DMA. Procesor musi poczekać na rozpatrzenie jego zgłoszenia,
dopóki jednostka DMA nie zwolni magistrali.
Zasadę transmisji DMA można oczywiście jeszcze ulepszyć, np. przez
zwiększenie liczby buforowych rejestrów w jednostce sterującej dysku oraz
wprowadzenie możliwości, by jednostka DMA, otrzymawszy dostęp do sys-
temowej magistrali, nie musiała rozłączać się po zapisaniu (lub odczytaniu)
zaledwie jednego słowa, ale by mogła zapisać do RAM (lub z niej odczytać)
całą serię słów, opróżniając w ten sposób (lub zapełniając) kilka rejestrów
buforowych (burst mode). Takie rozwiązania rzeczywiście później zastoso-
wano. Dla przykładu można dodać, że sterowniki współczesnych dysków
twardych mają bufory rzędu kilku MB.
Pozostaje wreszcie do omówienia czwarty przypadek komunikacji
między jednostkami (rysunek 16.2d), a mianowicie sytuacja, gdy zewnętrz-
ne urządzenie zgłasza się do arbitra i chce uzyskać przydział magistrali po
to, by przekazać procesorowi jedno (a naprawdę najczęściej dwa) słowa
danych. Dzieje się tak wtedy, gdy urządzenie chce skłonić procesor (który
w tym czasie jest zajęty czymś innym) – do zajęcia się jakimś jego (tzn.
tego urządzenia) pilnym problemem. Owe dwa przesyłane słowa precyzują,
o jaki problem chodzi.
Pamiętajmy, że jedyne, co procesor potrafi robić – to wykonywać
programy zakodowane w jego języku wewnętrznym i przebywające w danej
chwili w pamięci operacyjnej. Jeśli więc mówimy, że procesor jest „czymś
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

416 16. Organizacja jednoprocesorowego systemu komputerowego

zajęty” – to znaczy, że wykonuje on w tej chwili jakiś program. Jeśli mó-


wimy, że urządzenie „chce”, by procesor „zajął się jego problemem” – to
znaczy, że na skutek pewnego zdarzenia, które zaszło w urządzeniu, pro-
cesor powinien przerwać wykonywanie aktualnego programu i przejść do
wykonywania innego programu, tego, w którym jest zakodowany algorytm
reakcji na to właśnie zdarzenie.
Taką sytuację nazywamy przerwaniem (ang. interrupt). Obsługa
przerwania wymaga dość złożonych działań, po stronie zarówno sprzętu
(hardware’u) komputera, jak i systemowego oprogramowania. Sprawa jest
bowiem znacznie bardziej skomplikowana, niż się na pierwszy rzut oka wy-
daje. Po pierwsze, przerywany program nie może ponieść żadnej szkody, tak
by można go było w przyszłości wznowić. Po drugie, systemowe programy
reakcji na przerwania są również programami. Czy one też mogą być prze-
rywane, na przykład przez inne, ważniejsze przerwania? Skąd procesor wie,
które z przerwań jest ważniejsze i jaki jest adres początku programu reakcji
na dane przerwanie?
Takich pytań można zadać jeszcze więcej. Postaramy się na nie odpo-
wiedzieć w rozdziale 18, gdy (po poznaniu zasad organizacji procesora) bę-
dziemy do tego lepiej przygotowani. Na razie skoncentrujmy się wyłącznie
na tym, co schematycznie pokazano na rysunku 16.2, a więc na roli arbitra,
urządzeń zewnętrznych i systemowej magistrali w procesie dostarczania do
procesora informacji o przerwaniach.
Wyróżnijmy wśród linii sterujących kilka par linii. Niech w skład
każdej pary wchodzi linia BR (ang. Bus Request, żądanie szyny) oraz linia
BG (Bus Grant, przydział szyny). Po pierwszej z nich urządzenie zgłasza się
do arbitra po przydział magistrali, po drugiej – arbiter tę szynę przydziela.
Niech te pary będą dodatkowo oznaczone numerami (np. BR7 oraz BG7,
BR6 i BG6, BR5 i BG5 itd.), które odpowiadają ważności czy też pilności
zgłoszenia. Im numer wyższy – tym wyższy również priorytet zgłoszenia.
W każdym urządzeniu istnieje zapewne kilka sprzętowych ukła-
dów, które wykrywają różne sytuacje wymagające zgłoszenia przerwania.
Wyjścia tych układów są dołączone do różnych linii zgłoszeń, w zależno-
ści od ich stopnia ważności. Tak więc na przykład w jednostce sterującej
drukarką jeden rodzaj przerwania może sygnalizować brak papieru, a inny
– wyłączenie zasilania drukarki. Oczywiście, to drugie przerwanie jest waż-
niejsze, a jego zgłoszenie powinno być sygnalizowane arbitrowi linią Bus
Request o wyższym priorytecie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Czego pan może wymagać od sługi 417

Każde urządzenie może więc produkować kilka rodzajów zgłoszeń


przerwań i zgłaszać je za pośrednictwem linii o różnych poziomach prio-
rytetu.
Oczywiście, samych urządzeń zewnętrznych też jest wiele. W rezul-
tacie, na każdej linii Bus Request arbiter odbiera sumę logiczną zgłoszeń, to
znaczy otrzymuje wartość 1, jeżeli którakolwiek z dołączonych do tej linii
jednostek zgłasza chęć stania się masterem.
Tu ważna uwaga. Linie zgłoszeń przerwań nie docierają do arbitra
magistrali wprost. Są one po drodze jak gdyby cenzurowane przez znaj-
dujący się w procesorze układ przerwań. W szczególności układ przerwań
może na pewien czas całkiem zablokować dostarczanie arbitrowi wszelkich
zgłoszeń przerwań albo zezwolić na przepuszczenie tylko niektórych z nich.
Chodzi o to, by uchronić przed przerwaniem jakiś ważny program, który
w danej chwili właśnie się wykonuje. Jak to dokładniej działa – powiemy
w rozdziale 18. Teraz zapamiętajmy tylko, że dalsza obsługa zgłoszeń przez
arbitra wymaga zgody ze strony układu przerwań.
Tak więc, wraz z początkiem nowego cyklu szyny, jeżeli żadne
z urządzeń DMA nie zgłasza się po linii NPR (Non-Processor Request)
i jeśli układ przerwań wyraża taką zgodę – arbiter przegląda linie zgłoszeń
przerwań (Bus Request), poczynając od tej, która ma najwyższy priorytet.
Jeśli tam jest 0 – to odczytuje stan następnej i tak dalej.
Jeśli stan którejkolwiek z tych linii zgłoszeń jest równy 1 – arbiter
ustawia stan 1 na odpowiadającej jej linii przydziału magistrali (Bus Grant
albo – w przypadku NPR – na linii NPG: Non-Processor Grant) i nie spraw-
dza już linii o niższych priorytetach. Sygnalizowane za ich pomocą zgłosze-
nia będą musiały cierpliwie poczekać na swoją kolej.
Linia przydziału magistrali (Bus Grant) nie jest odczytywana jedno-
cześnie przez wszystkie (dołączone do danej linii) urządzenia zewnętrzne.
Przeciwnie, obiega je po kolei, jedno po drugim. Z punktu widzenia sygnału
przydziału magistrali urządzenia są bowiem uszeregowane w łańcuch, na-
zywany daisy chain. Jeżeli pierwsze urządzenie w łańcuchu nie zgłaszało
przerwania – przekazuje przydział drugiemu. Jeśli również i to urządzenie
nie chce dostępu do magistrali – przekazuje następnemu itd. Ale pierwsze
urządzenie, które jest zainteresowane dostępem do magistrali i „złapie” sy-
gnał Bus Grant – nie przepuszcza go już dalej. Zgłasza się wówczas jako
master, zajmuje magistralę, wywołuje swojego sługę i dalej działa tak, jak
już wyżej omawialiśmy.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

418 16. Organizacja jednoprocesorowego systemu komputerowego

Jak widać, zasady pierwszeństwa obowiązujące przy przydzielaniu


magistrali mają charakter niejako dwuwymiarowy. Po pierwsze, samych li-
nii żądań jest wiele i wtedy są one uszeregowane (i sprawdzane przez arbi-
tra) w pewnej ustalonej kolejności. Po drugie, na każdej linii o pierwszeń-
stwie decyduje pozycja danej jednostki w łańcuchu (daisy chain). Tak więc
o priorytetach w zbiorze jednostek rozstrzyga sposób podłączenia każdej
z nich do linii sterujących magistrali.
Taka zasada umożliwia łączenie do magistrali systemowej dużej licz-
by różnorodnych urządzeń zewnętrznych. Oczywiście, niektóre z nich, te
bardziej oddalone od arbitra (licząc wzdłuż daisy chain) muszą często nie-
co dłużej czekać na przydział magistrali, ale w praktyce nie odgrywa to
wielkiego znaczenia, zwłaszcza w przypadku stosukowo wolnych urządzeń
znakowych, takich jak np. klawiatura czy drukarka.
Jak widzimy, rola magistrali systemowej i sterującego nią arbitra jest
zupełnie podstawowa dla komunikowania się pomiędzy jednostkami syste-
mu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

17. Procesor

Schemat blokowy procesora


Do tej pory, zgodnie z zasadami modelu PMS (Processors, Memories,
Switches), procesor (CPU, ang. Central Processing Unit) był dla nas tyl-
ko jedną z jednostek, komunikujących się między sobą za pośrednictwem
systemowej magistrali. Teraz przejdziemy na poziom bardziej szczegółowy
i zobaczymy, co dzieje się wewnątrz niego. O takim stopniu szczegółowości
mówi się, że opisuje działanie procesora na poziomie RTL (ang. Register
Transfer Level). Znaczy to, że będziemy się koncentrowali przede wszyst-
kim na przesyłaniu informacji pomiędzy rejestrami i innymi podzespołami
znajdującymi się wewnątrz procesora.
Podobnie jak poprzednio, będziemy wzorować się na organizacji pro-
cesora komputera PDP-11. Choć niedzisiejszy, ujmuje on przejrzystością
koncepcji, którą doceniono już w momencie jego powstania, na przełomie
lat sześćdziesiątych i siedemdziesiątych poprzedniego wieku. My zaś, na
potrzeby wykładu, tę koncepcję jeszcze bardziej uprościmy tak, by można
było działanie procesora omówić w kilku słowach, nie wchodząc głębiej
w techniczne szczegóły.
Najpierw, przyjmijmy na potrzeby niniejszego rozdziału, że oma-
wiany system składa się tylko z procesora i jednej jednostki pamięci RAM
(rysunek 17.1). W ich współpracy nie będzie więc brał udziału arbiter ma-
gistrali, nie będą w nią ingerowały inne jednostki: kanały DMA, urządzenia
zewnętrzne itd. To nam znacznie uprości rozumowanie.
W jednostce pamięci RAM przebywają te fragmenty programu
i danych, które są aktualnie potrzebne na danym etapie przetwarzania.
Oczywiście, program jest zapisany już w języku wewnętrznym maszyny.
Kolejne rozkazy programu oraz liczby, ciągi tekstowe, dane logiczne itd.
są zakodowane dwójkowo i rozmieszczone w poszczególnych komórkach
pamięci RAM. Każda komórka ma oczywiście swój (również dwójkowy)
numer, czyli adres.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

420 17. Procesor

Rys. 17.1. Procesor (CPU) i pamięć operacyjna (RAM)

Przyjmijmy – też dla uproszczenia – że poszczególne rozkazy języka ma-


szynowego i dane (np. liczby) mają stałą długość, równą rozmiarowi ko-
mórki pamięci. Niech długość ta wynosi 16 bitów, a pamięć RAM składa
się z 216 = 64 kB komórek.
Tak więc, chcąc sprowadzić z pamięci kolejny rozkaz (lub daną po-
trzebną do wykonania tego rozkazu) musimy dostarczyć do mechanizmu
dostępu pamięci RAM (oznaczonego na rysunku literami MD) dwie infor-
macje: 16-bitowy adres oraz dwójkowy kod operacji pamięci (choćby tyl-
ko po to, by sprecyzować, czy chodzi o odczytanie, czy zapisanie komórki
o tym adresie).
Podobnie, przy zapisywaniu słowa do pamięci RAM należy to słowo
umieścić w Rejestrze Buforowym Pamięci (RBP), a mechanizmowi dostępu
dostarczyć adres wraz z informacją sterującą, że chodzi o zapisywanie. Tę
parę informacji musi układowi dostępu do pamięci RAM dostarczyć proce-
sor. Wiemy już, że w rzeczywistości musi najpierw poprosić arbitra o zgodę,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Schemat blokowy procesora 421

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

422 17. Procesor

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

1 W niektórych procesorach (np. Intel) PC nie należy do puli rejestrów uniwer-


salnych. Jest oddzielnym, wyróżnionym rejestrem i repertuar wykonywanych na
nim operacji jest ograniczony.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Schemat blokowy procesora 423

Pointer). Przekonamy się dalej, jak wielkie ma on znaczenie dla wykonywa-


nia pewnych bardzo pożytecznych operacji systemowych. Na razie jednak
zauważmy tylko, że wszystkie rejestry uniwersalne są w strukturze proceso-
ra traktowane równorzędnie. Każdy z nich może być użyty jako argument
w operacji JAL, przesłany do innego rejestru, zapisany do pamięci itd.
Na rysunku 17.1 wymienione rejestry są połączone strzałkami pro-
wadzącymi do dwóch grubych kresek, od których z kolei podobne strzałki
wiodą do wejść jednostki arytmetyczno-logicznej. Każda z tych cienkich
strzałek – to w rzeczywistości wiązka 16 równoległych połączeń, po któ-
rych można skopiować zawartość całego, 16-bitowego rejestru. Grube kre-
ski, oznaczone jako L-bus i P-bus – to natomiast (odpowiednio) lewa i pra-
wa szyna wejściowa arytmometru.
Podobnie, od wyjścia JAL, na którym pojawia się wynik operacji
(W), prowadzi strzałka (również kryjąca w sobie 16-bitową wiązkę) do gru-
bej krechy (W-bus), stanowiącej szynę wyjściową JAL. Stamtąd podobne
(też 16-bitowe) strzałki prowadzą znów do wspomnianych wyżej rejestrów
procesora. Łatwo się domyślić, że tym razem symbolizuje to możliwość za-
pisania wyniku znów do jednego, wybranego rejestru. Jak jednak dokonuje
się w każdej z szyn taki wybór?
Przypomnijmy sobie to, o czym mówiliśmy w rozdziale 12. Każda
z dwóch szyn wejściowych arytmometru jest w istocie układem logicznym
składającym się z tylu multiplekserów, ile bitów liczą w danej maszynie sło-
wa, a więc w naszym przypadku – z 16 multiplekserów. Każdy multiplekser
ma tyle wejść, z ilu źródeł można skopiować zawartość na dane wejście
JAL. Multiplekser numer 0 ma na swych wejściach wszystkie bity o nume-
rze 0 (a więc najmniej znaczące) z poszczególnych rejestrów, multiplekser
numer 1 – wszystkie bity o numerze 1… itd., aż do multipleksera nr 15,
który gromadzi na wejściach szesnaste, najbardziej znaczące bity rejestrów.
Wejścia sterujące są natomiast dla tego zespołu multiplekserów wspólne.
Na schemacie z rysunku 17.1 zaznaczyliśmy je w postaci małych strzałek
opisanych jako s3 i s4.
Jak wiemy z rozdziału 12, w każdym z multiplekserów układ zer i je-
dynek na wejściu sterującym wymusza skopiowanie na wyjście wartości jed-
nego, wskazanego w ten sposób wejścia. Ponieważ w każdej z szyn wejścio-
wych arytmometru to samo wejście sterujące jest doprowadzone do wszyst-
kich multiplekserów – na wyjściach grupy szesnastu multiplekserów pojawia
się cała zawartość jednego wybranego rejestru.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

424 17. Procesor

W opisanym procesie wyboru grupa rejestrów uniwersalnych (RU)


może być traktowana łącznie jako jedna z możliwości. W takim przypadku sy-
gnał sterujący do zespołu multiplekserów (lub demultiplekserów) nakazujący
wybranie jednego z rejestrów RU należy uzupełnić o trzybitowy sygnał steru-
jący (s2) skierowany wprost do bloku rejestrów, wskazujący, którego z ośmiu
rejestrów ta operacja dotyczy. Blok tych ośmiu rejestrów RU stanowi malut-
ką, ale za to bardzo szybką podręczną pamięć arytmometru, a wspomniany
trzybitowy sygnał sterujący jest odpowiednikiem adresu w tej pamięci.
Wybór miejsca zapisania wyniku dokonuje się na nieco innej zasa-
dzie. Szyna wyjściowa arytmometru (W-bus), kryjąca się w naszym sche-
macie znów pod postacią grubej kreski – doprowadza zespół bitów wyj-
ściowych z arytmometru do odpowiednich wejść jednocześnie wszystkich
rejestrów, które mogą być miejscem zapisania wyniku. Układ sterowania
kieruje natomiast sygnał sterujący „zapisz” tylko do tego rejestru (lub reje-
strów), w którym operacja zapisywania wyniku ma się rzeczywiście doko-
nać (por. rysunek 13.4)2.
Wszystkie sygnały sterujące (zarówno te zaznaczone na rysunku 17.1
małymi strzałkami od s1 do s5, jak i niepokazane na rysunku) pochodzą
z układu sterowania procesora. Ustawienie ich binarnych wartości tworzy
w strukturze procesora pewną drogę dla transferu zawartości między wska-
zanymi rejestrami.
Te elementarne czynności, dotyczące poszczególnych podzespołów
procesora, nazywane są – w kontekście działania procesora – mikroopera-
cjami. Mikrooperacją jest więc np. dodawanie dwóch liczb wykonywane
przez arytmometr, mikrooperacją jest (dokonywany przez lewą szynę wej-
ściową JAL) wybór jednego z rejestrów, którego zawartość ma być lewym
argumentem arytmometru itd. Podobnie, pojedyncza czynność zapisania
czy odczytania słowa z pamięci jest mikrooperacją pamięci RAM (jej kod
jest podawany do miejsca oznaczonego na schemacie µP) i tak dalej. Każdy
z tych podzespołów ma swój repertuar kilku czy kilkunastu mikrooperacji,
które „umie” wykonać. Sygnały sterujące s1…s5 są więc kilkubitowymi ko-
dami wskazującymi poszczególnym podzespołom, którą ze swoich mikro-
operacji mają wykonać w danej chwili.

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

Schemat blokowy procesora 425

Wszystkie drogi transferu zawartości rejestrów prowadzą przez jed-


nostkę JAL. Wśród mikrooperacji, jakie potrafi wykonywać arytmometr
(a definiowanych za pomocą wartości sygnału sterującego s1), są też czyn-
ności polegające na skopiowaniu zawartości jego lewego lub prawego argu-
mentu wejściowego wprost, bez żadnej zmiany, na wyjście W (a stąd dalej
do wskazanego celu). Taka mikrooperacja oznacza zwyczajne skopiowanie
zawartości między dwoma rejestrami. Jednak ustawiając inaczej rodzaj mi-
krooperacji wykonywanej przez JAL – można po drodze wykonać dzia-
łanie arytmetyczne lub logiczne na zawartościach wskazanych rejestrów.
Analogicznie, ustawiając odpowiednią wartość sygnału sterującego s5, moż-
na spowodować żądaną mikrooperację na zawartości wskazanej komórki
pamięci (np. odczytanie słowa, zapisanie słowa).
Zestaw jednoczesnych mikrooperacji nazywamy mikroinstrukcją (mi-
krorozkazem). Mikroinstrukcja definiuje jeden elementarny krok działania
procesora. Do wykonania jednej instrukcji zakodowanej dwójkowo w ję-
zyku maszynowym i przebywającej w pamięci RAM potrzeba kilku czy
kilkunastu takich mikroinstrukcji. Ich produkowanie jest zadaniem układu
sterowania procesora.
Dla przykładu, wyobraźmy sobie, że w pewnym kroku (a więc w pew-
nej mikroinstrukcji) wartości sygnałów sterujących (a więc kody mikroope-
racji) są ustawione tak, że:
– sygnał s4 wskazuje, że lewym argumentem arytmometru ma być jeden
z rejestrów uniwersalnych RU,
– sygnał s2 doprecyzowuje, że rejestrem tym ma być R6 (jednocześnie na
wejście sterujące „odczytaj” rejestru R6 jest podawana wartość 1),
– sygnał s1 nakazuje, by arytmometr wykonał mikrooperację o treści „lewy
argument przekaż bez zmiany do wyjścia W”,
– na wejście sterujące „zapisz” rejestru RAP jest podawana logiczne 1
(co wskazuje, że wynik W ma być przesłany do Rejestru Adresowego
Pamięci),
– sygnał s5 nakazuje mechanizmowi dostępu do pamięci (MD) wykonanie
mikrooperacji „odczytaj”.
Taka mikroinstrukcja spowoduje w rezultacie, że zawartość rejestru
R6 zostanie (drogą wiodącą przez JAL) zapisana do Rejestru Adresowego
Pamięci (RAP), z równoległym poleceniem odczytania komórki pamięci.
Układ sterowania poleca wykonanie takiej mikroinstrukcji zapewne
po to, by jednostka pamięci RAM potraktowała zawartość rejestru RAP jako
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

426 17. Procesor

adres, wybrała wskazaną komórkę pamięci, a następnie – skopiowała jej za-


wartość do rejestru buforowego RBP. Zdefiniowana w ten sposób czynność
trwa przez pewną, znaną liczbę cykli zegara. Zapewne, kiedy się zakończy,
układ sterowania wytworzy następną mikroinstrukcję (a więc nowy komplet
sygnałów sterujących), na przykład taką, że zawartość rejestru buforowego
RBP zostanie przepisana do jednego z wewnętrznych rejestrów procesora…
i tak dalej.
Kolejne mikroinstrukcje produkuje układ sekwencyjny (na rysunku
17.1 oznaczony jako sekwenser (ang. sequencer). Jest to w istocie automat
skończony, o dużej liczbie stanów i przejść między nimi. Przejścia pomię-
dzy kolejnymi stanami są wymuszane przez zegar procesora. W każdym ze
stanów sekwenser produkuje (tak jak automat Moore’a) wektor składający
się z kilkudziesięciu zer i jedynek, które stanowią treść mikroinstrukcji. One
to, odpowiednio rozprowadzone do poszczególnych podzespołów procesora
– ustawiają wartości sygnałów sterujących dla arytmometru, jego szyn wej-
ściowych i wyjściowych, rejestrów, pamięci itd.
Graf opisujący zachowanie sekwensera ma charakterystyczną, typo-
wą budowę, pokazaną schematycznie na rysunku 17.2. Czy on nam czegoś
nie przypomina? Ależ tak, to w gruncie rzeczy nieco rozbudowana, nieco
bardziej współczesna i nieco inaczej narysowana wersja cyklu rozkazowego
(ang. instruction cycle), który znamy z rysunku 15.4.

Rys. 17.2. Cykl rozkazowy


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Schemat blokowy procesora 427

W obu przypadkach chodzi o to samo: by procesor wykonywał cy-


klicznie, na zmianę dwie podstawowe czynności: pobranie rozkazu maszy-
nowego z pamięci i jego wykonanie, znów pobranie i wykonanie… i tak
dalej. Tutaj, na rysunku 17.2, ten podstawowy cykl rozkazowy rozbudo-
waliśmy o dodatkową ścieżkę, a mianowicie o ciąg czynności związanych
z procesem inicjowania obsługi przerwania. Tym fragmentem, jak i całym
układem przerwań, zajmiemy się jednak dopiero w następnym rozdziale. Na
razie załóżmy, że żadne żądania obsługi przerwania (cokolwiek to znaczy)
się nie pojawiają, a zachowanie układu sterowania polega wyłącznie na po-
wtarzaniu w kółko dwóch czynności, zwanych fazami pobierania i wykony-
wania kolejnych instrukcji maszynowych.
Faza pobierania instrukcji (ang. instruction fetch) jest zawsze taka
sama, dla wszystkich rozkazów. Nic to dziwnego: polega ona przecież właś-
nie na sprowadzaniu kolejnego rozkazu maszynowego do układu sterowania
procesora (ściślej – do jego Rejestru Instrukcji RI) i jeszcze na razie nie
wiadomo, co to za rozkaz i czego dotyczy. Faza pobierania rozkazu składa
się więc z kilku mikroinstrukcji o następującym znaczeniu:
1. Prześlij zawartość licznika instrukcji PC (a więc rejestru R7) do Rejestru
Adresowego Pamięci (RAP) z jednoczesnym wskazaniem, że należy od-
czytać słowo, umieszczone pod tym adresem,
2. Zawartość licznika rozkazów PC (czyli R7) prześlij do Jednostki
Arytmetyczno-Logicznej, nakaż (ustawiając odpowiednio mikrooperację
s1) arytmetyczne dodanie do niej jedynki, a wynik zapisz z powrotem do
R7,
3. Poczekaj, aż mikrooperacja odczytywania słowa z pamięci RAM do-
biegnie końca, a odczytana zawartość słowa znajdzie się w Rejestrze
Buforowym Pamięci (RBP),
4. Prześlij tę zawartość RBP (oczywiście poprzez arytmometr, ale bez żadnej
operacji arytmetycznej ani logicznej) do Rejestru Instrukcji (RI) w ukła-
dzie sterowania procesora.
Zauważmy, że zainicjowana w kroku 1 mikrooperacja odczytywania
nowego rozkazu z pamięci zajmuje pewien czas, potrzebny jednostce pa-
mięci RAM na dostęp do słowa o zadanym adresie i skopiowanie go do
rejestru buforowego RBP. Czas ten jest z zasady dłuższy niż ten, który jest
procesorowi potrzebny do wykonania elementarnego przesłania pomiędzy
jego wewnętrznymi rejestrami. Dlatego w międzyczasie, w stanie 2 sekwen-
ser powoduje zwiększenie zawartości licznika rozkazów (czyli rejestru R7)
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

428 17. Procesor

o jeden i dopiero wtedy przechodzi do oczekiwania na zakończenie mikro-


operacji pamięci (krok 3). Gdy to nastąpi – sekwenser powoduje kopiowa-
nie nowego rozkazu do rejestru RI (krok 4).
W ten sposób realizuje się zasadę, że później, w czasie wykonania
nowego rozkazu, zawartość licznika rozkazów będzie wskazywać komórkę
pamięci, z której ma być pobrana następna instrukcja maszynowa. W roz-
dziale 15 wyjaśniliśmy, dlaczego ta zasada obowiązuje i jak jest wykorzy-
stywana przy wykonywaniu rozkazów warunkowych.
Tak czy inaczej, po zakończeniu fazy pobierania instrukcji nowy roz-
kaz znalazł się w rejestrze instrukcji (RI) i procesor może przystąpić do
jego wykonania.
Oczywiście, wykonanie każdego rozkazu jest znowu ciągiem pew-
nych elementarnych kroków (mikroinstrukcji), które sekwenser wymusza,
przechodząc przez ciąg kilku czy kilkunastu stanów. To, jakie sygnały ste-
rujące wytwarza w kolejnym stanie i do jakiego stanu następnie przejdzie
– zależy od treści rozkazu. Inaczej wykonuje się przecież rozkaz arytme-
tycznego dodawania zawartości dwóch rejestrów uniwersalnych, inaczej –
dodawania zawartości jednego z rejestrów RU oraz komórki pamięci (którą
przedtem należy sprowadzić z RAM do rejestru buforowego RBP), jeszcze
inaczej – rozkaz skoku warunkowego pod wskazany adres, itd.
Tak więc każdemu rozkazowi z listy instrukcji danego procesora od-
powiada pewna ścieżka w grafie układu sterowania. Ścieżki dla różnych roz-
kazów mogą mieć wspólne odcinki, mogą rozwidlać się i zbiegać w różnych
stanach grafu. Układ sterowania podąża taką ścieżką „popychany” zegarem
procesora, natomiast wybór następnego stanu jest każdorazowo uzależniony
zarówno od stanu bieżącego (aktualnego), jak i od wejść sekwensera.
Jednym z tych wejść jest dekoder, który przerabia część operacyjną
instrukcji (gdzie jest zakodowany rodzaj operacji) na zespół wewnętrznych
sygnałów sterujących, potrzebnych sekwenserowi do zdecydowania o wy-
borze następnego stanu. W ten sposób następuje np. wybór ścieżki już na
samym początku fazy wykonania instrukcji. Może być też tak, że wyko-
nanie kilku instrukcji zaczyna się w identyczny sposób, a potem, po kilku
stanach, ścieżki rozwidlają się, w zależności od szczegółowej zawartości
części operacyjnej rozkazu. Również i w tym przypadku sekwenser wybiera
jedną z możliwości, posługując się informacją dostarczaną przez dekoder
części operacyjnej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Schemat blokowy procesora 429

Drugim z wejść decydujących o przebiegu ścieżki wykonania instruk-


cji jest rejestr warunków war, wykorzystywany przy wykonywaniu instruk-
cji warunkowych. O jego roli mówiliśmy już wcześniej, przypomnimy więc
tylko pokrótce, że standardowo dostarcza on do układu sterowania informa-
cję o ostatnim wyniku działania arytmometru (N – gdy ostatni wynik był
ujemny, Z – gdy był zerem, V – gdy wystąpił nadmiar, C – gdy przeniesie-
nie).
Powiedzmy dla przykładu, że aktualnie wykonywany (a więc znaj-
dujący się w rejestrze instrukcji RI) rozkaz jest rozkazem skoku warunko-
wego, który ma się wykonać jedynie wtedy, gdy ostatni wynik wychodzący
z JAL był równy zeru. Dekoder kieruje wówczas sekwenser na ścieżkę wła-
ściwą dla rozkazów skoku warunkowego. Tutaj okazuje się, że o wykonaniu
(lub niewykonaniu) rozkazu ma zdecydować bit Z. Jeżeli jednocześnie w tej
chwili Z = 1, to sekwenser podąży dalej ścieżką powodującą wykonanie roz-
kazu. Jeśli natomiast Z = 0, to sekwenser przeskoczy od razu do końcowe-
go stanu tej ścieżki, omijając wszystkie czynności związane z wykonaniem
instrukcji.
Zauważmy także, że w strukturze procesora istnieje droga pomiędzy
Rejestrem Instrukcji a przynajmniej jedną z szyn wejściowych arytmome-
tru. Dzięki temu adresowa część Rejestru Instrukcji (RI) może być jednym
z argumentów operacji arytmetycznej wykonywanej w JAL. Znajduje to za-
stosowanie w niektórych instrukcjach, np. w rozkazach (bezwarunkowego
lub warunkowego) skoku (czy raczej „przeskoku”) o pewną liczbę rozka-
zów w przód lub w tył w stosunku do instrukcji aktualnie wykonywanej.
W takim przypadku rodzaj operacji jest – jak zwykle – zakodowany
w części operacyjnej rozkazu, natomiast w pozostałej części rozkazu zapisa-
na jest krótka binarna liczba całkowita (dodatnia lub ujemna), informująca,
o ile rozkazów należy przeskoczyć. W fazie wykonania takiego rozkazu,
liczba ta (aktualnie przybywająca w RI) jest dodawana do zawartości R7
(czyli licznika rozkazów), zapisywana z powrotem do R7 i na tym faza wy-
konania instrukcji się kończy. Skutek jest taki, że w następującej zaraz po
tym fazie pobierania nowego rozkazu – do układu dostępu do pamięci RAM
powędruje zlecenie sprowadzenia go już spod nowego, zmodyfikowanego
w ten sposób adresu.
Wszystkie ścieżki wykonania kończą się w jednym, wspólnym punk-
cie zwanym – jak łatwo zgadnąć – stanem końca cyklu rozkazowego. W tym
momencie układ sterowania bada, czy jest konieczne tzw. inicjowanie ob-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

430 17. Procesor

sługi przerwania. Co to znaczy i co się ma wtedy stać – wyjaśnimy niżej,


w rozdziale 18. Jeżeli potrzeby inicjowania obsługi przerwania nie ma – se-
kwenser wraca na początek cyklu i rozpoczyna pobieranie z pamięci RAM
następnego rozkazu maszynowego. W rejestrze licznika rozkazów (R7) cze-
ka już adres, spod którego należy go pobrać.
I tak koło cyklu rozkazowego kręci się i kręci, milion – powiedzmy
– razy na sekundę.
Nie będziemy wystawiać na próbę cierpliwości czytelnika dalszymi
szczegółami: to co wyżej powiedzieliśmy powinno wystarczyć do zrozu-
mienia, jak w zasadzie wykonywane są wewnątrz naszego prostego proce-
sora instrukcje maszynowe. Ale w rzeczywistości szczegółów jest jeszcze
mnóstwo. Na przykład, reguły języka maszynowego przewidują zazwyczaj
kilka czy nawet kilkanaście sposobów adresowania (inaczej: trybów adre-
sowania, ang. addressing modes) komórek pamięci głównej. Oznacza to, że
(dla wygody tworzenia różnych konstrukcji programowych) istnieje kilka-
naście sposobów wyliczania adresu przesyłanego do Rejestru Adresowego
Pamięci (RAP) wraz z poleceniem zapisania lub odczytania wskazanej
komórki. Projektanci procesora muszą dla każdej instrukcji z listy rozka-
zów maszynowych opracować szczegółowy mikroprogram, a więc ciąg mi-
kroinstrukcji, który prowadzi w końcu do wykonania tego rozkazu. Każdą
z mikroinstrukcji muszą dwójkowo zakodować tak, by zawierała ona od-
powiednie kody mikrooperacji wykonywanych w podzespołach procesora
(u nas np. sygnały sterujące od s1 do s6 itd.). Wreszcie muszą zaprojekto-
wać i zrealizować sam automat skończony, jakim jest sekwenser: jego zbiór
stanów, funkcję następnego stanu, funkcję wyjścia, dekoder wejściowy itd.
Dziś oznacza to przygotowanie dużego fragmentu układu scalonego, jakim
jest cały mikroprocesor. Kiedyś, wtedy, gdy powstawał nasz wzorcowy pro-
cesor PDP-11, oznaczało to opracowanie i wykonanie logicznego schematu
połączeń wielu układów scalonych o średniej skali integracji (MSI, Medium
Scale Integration).
Pamiętajmy także, że układ sterowania procesora porozumiewa się
dodatkowo z arbitrem systemowej magistrali. Uprościliśmy sobie życie,
nie mówiąc o tym teraz, ale z rozdziału 16 wiemy, na czym ta komunika-
cja polega. Żeby o tym nie zapomnieć, na schemacie procesora z rysunku
17.1 pozostawiliśmy dwukierunkową strzałkę z napisem „Arbiter”, która
ma symbolizować zespół linii sterujących do komunikacji między arbitrem
a układem sterowania procesora.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Stos systemowy i operacje na stosie 431

Stos systemowy i operacje na stosie


Szczegóły wykonywania większości typowych rozkazów maszynowych
można istotnie bez obawy powierzyć domyślności i wyobraźni czytelni-
ka. Są jednak również takie instrukcje, których mikroprogramy nie są od
razu oczywiste, a odgrywają szczególnie ważną rolę w wielu programach
maszynowych. Należą do nich zwłaszcza instrukcje maszynowe wykorzy-
stujące pomysł stosu (ang. stack). Warto się z tą koncepcją zapoznać, po-
nieważ rozwiązuje ona w bardzo pomysłowy i uniwersalny sposób problem
komunikowania się programów z podprogramami, wywoływania (w tym
również rekurencyjnego) funkcji i procedur, a także inicjowania obsługi
przerwań.
Na rysunku 17.3 widzimy program główny (A), który wywołuje
podprogram B, ten zaś – podprogram C. Sam program A zaczyna się od roz-
kazu maszynowego umieszczonego w komórce o adresie A0. Po wykonaniu
pewnej liczby kolejnych rozkazów (ich działanie nie jest tu dla nas ważne)
rozkaz z komórki A1 powoduje skok do podprogramu B, który rozpoczyna
się w komórce B0. Po całkowitym zakończeniu tego podprogramu, rozkaz
z jego ostatniej komórki (B2) powinien spowodować powrót do programu
głównego, a ściślej – do rozkazu z komórki (A1 + 1), a więc następnej po
tym rozkazie, który spowodował wywołanie.

Rys. 17.3. Przykład zagnieżdżonego układu odwołań do podprogramów


Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

432 17. Procesor

Aby nieco skomplikować sprawę, załóżmy, że (znowu po serii se-


kwencyjnie wykonywanych instrukcji) program A wywołuje podprogram B
ponownie rozkazem z komórki A2. Tym razem zakończenie podprogramu B
powinno spowodować powrót do instrukcji z komórki (A2 + 1). Co więcej,
załóżmy, że sam podprogram B powoduje wywołanie innego podprogramu
(C). Ten, po zakończeniu, powinien spowodować powrót do instrukcji z ko-
mórki (B1 + 1) itd.
Zauważmy, że wywoływane podprogramy mogą powstawać zupełnie
niezależnie od programu A. Być może są to wręcz gotowe funkcje lub pro-
cedury wchodzące w skład całej biblioteki podobnych programów, dostar-
czanych programistom piszącym programy aplikacyjne, takie jak nasz przy-
kładowy program A. Twórcy tych funkcji najprawdopodobniej w ogóle nie
mieli pojęcia, kto, ile razy i spod jakiego adresu będzie je wywoływał. Skąd
zatem taki podprogram „wie”, do jakiego rozkazu powrócić po zakończeniu
aktualnego wywołania?
Oczywiście, autor podprogramu (a tym bardziej sam podprogram) nie
musi tego wiedzieć. Zadba o to układ sterowania procesora. Musi być o tym
jedynie ostrzeżony, przez użycie w rozkazach wywołania podprogramu oraz
powrotu z podprogramu – specjalnych, przewidzianych na tę okazję kodów
części operacyjnej.
Rozkaz skoku do podprogramu ma często w języku asemblero-
wym mnemotechniczny kod JSR (od ang. Jump to Subroutine) lub CALL.
Oprócz odpowiadającego mu binarnego kodu w części operacyjnej, rozkaz
ten w części adresowej wskazuje adres początku podprogramu, do którego
należy wykonać skok. Rozkaz powrotu z podprogramu jest natomiast beza-
dresowy (nie zawiera części adresowej), a jego część operacyjna oznaczana
jest skrótem RET lub RETURN. Taką też konwencję zastosowaliśmy na
rysunku 17.3.
Dla zrozumienia sposobu, w jaki układ sterowania wykonuje rozka-
zy JSR oraz RET, musimy przypomnieć zasadę działania stosu LIFO (ang.
LIFO stack). Wspomnieliśmy już o niej w jednym z wcześniejszych roz-
działów, ale pokażemy ją raz jeszcze na rysunku 17.4.
Rysunek 17.4a poglądowo wyjaśnia zasadę funkcjonowania stosu.
Przypomina on działaniem pojemnik na talerze lub tace w kafeterii. Jego
konstrukcja powoduje, że przedmioty do niego wkładane są wyjmowane
w odwrotnej kolejności. Jeśli położymy na taki stos najpierw talerz A, po-
tem B i wreszcie C – to przy wyjmowaniu jako pierwszy weźmiemy C,
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Stos systemowy i operacje na stosie 433

Rys. 17.4. Stos LIFO: zasada (a) i implementacja (b)

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

3 Wspomnijmy przy okazji, że w informatycznej terminologii często występują


również kolejki FIFO (ang. FIFO queue, od First In – First Out). To oczywiście
„sprawiedliwe” kolejki, które obsługuje się w kolejności zgłoszeń: kto pierwszy
przyszedł – również jako pierwszy wychodzi.
4 Ta liczba jest całkiem fikcyjna, przyjęta wyłącznie dla ustalenia uwagi i wygody
wyjaśniania. W rzeczywistości stos znajduje się gdzieś w obszarze pamięci zare-
zerwowanym na potrzeby systemowe, np. w maszynie PDP-11 – wśród komórek
pamięci o najwyższych adresach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

434 17. Procesor

procesorze niech tę rolę odgrywa (tak jak w PDP-11) uniwersalny rejestr


R6. Tak więc, podobnie jak rejestr R7 jest jednocześnie licznikiem rozka-
zów (PC), tak rejestr R6 jest jednocześnie wskaźnikiem stosu SP (od ang.
Stack Pointer). Kiedy stos jest pusty, rejestr SP zawiera adres 5000. Jeżeli
chcemy odłożyć na stos jedno słowo – w rejestrze SP znajdziemy adres, pod
który należy je zapisać. Ale po zapisaniu należy koniecznie i niezwłocznie
zmniejszyć zawartość SP o jeden, tak by SP zawierał znów adres pierwszej
wolnej komórki (czyli wierzchołka) stosu, w tym przypadku 4999 itd.
Przy zdejmowaniu ze stosu kolejność działań musi być inna. Jeżeli
chcemy odczytać (ostatnie tam włożone) słowo z wierzchołka stosu, należy
najpierw zwiększyć zawartość SP o jeden. Dopiero po tej operacji SP będzie
wskazywał adres ostatniego zapisu i spod tego właśnie adresu należy odpo-
wiednie słowo odczytać.
Jak można wykorzystać ten mechanizm do organizowania wywołań
funkcji (podprogramów)? Wróćmy do przykładu z rysunku 17.3, a przeko-
namy się, że zapisywanie na stos aktualnych wartości licznika rozkazów na-
tychmiast wprowadza porządek w wielokrotnych wywołaniach i powrotach
z podprogramów.
Powiedzmy, że w chwili rozpoczęcia programu A stos jest pusty.
Wskaźnik stosu (SP, czyli R6) zawiera adres 5000, a licznik rozkazów (PC,
czyli R7) zawiera adres A0. Gdy wykonanie programu rusza, z każdym obie-
giem cyklu rozkazowego zawartość licznika PC zwiększa się o jeden, wreszcie
w chwili, gdy rozkaz z komórki A1 został sprowadzony do Rejestru Instrukcji
(RI), licznik PC zawiera już adres (A1 + 1).
Układ sterowania rozpoznaje, że w Rejestrze Instrukcji znalazł się
rozkaz JSR, nakazujący skok do podprogramu o adresie początkowym B0.
Wtedy wykonuje następujące czynności:
– włóż aktualną zawartość licznika rozkazów na stos,
– wykonaj skok do adresu A0.
Technicznie rzecz biorąc, czynności te mają postać następującego mikro-
programu:
1. Skopiuj licznik rozkazów (R7) do Rejestru Buforowego Pamięci (RBP).
2. Skopiuj wskaźnik stosu (R6) do Rejestru Adresowego Pamięci (RAP)
wraz z mikrooperacją „zapisz” (w ten sposób zawartość licznika rozka-
zów zostanie zapisana na wierzchołku stosu).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Stos systemowy i operacje na stosie 435

3. Odejmij jeden od zawartości wskaźnika stosu (R6) i wynik zapisz z po-


wrotem do R6 (SP zostaje zmodyfikowany tak, by znów wskazywał wol-
ny wierzchołek stosu).
4. Adres skoku B0 zapisz do licznika rozkazów (R7).
5. Poczekaj do zakończenia mikrooperacji zapisu, zleconej wyżej, w kroku 2.
Na tym faza wykonania instrukcji JSR się kończy. Układ sterowania
przechodzi do następnego obiegu cyklu, który – jak wiemy – rozpoczyna
się od pobrania nowego rozkazu z komórki, którą teraz wskazuje aktualna
zawartość licznika rozkazów. Ponieważ licznik rozkazów zawiera obecnie
adres B0 – rozpoczyna się wykonywanie podprogramu B.
Na końcu podprogramu umieszczony jest rozkaz o części operacyjnej
oznaczanej skrótem RET lub RETURN. W trakcie jego wykonywania układ
sterowania wykona czynność odwrotną: zdejmie słowo znajdujące się na
wierzchu stosu i wpisze je jako nową zawartość licznika rozkazów. Do tego
celu wystarczy następujący mikroprogram:
1. Dodaj jeden do zawartości wskaźnika stosu (R6) i wynik zapisz z powro-
tem do R6 (SP będzie wskazywał teraz adres ostatniego zapisu na stos).
2. Skopiuj wskaźnik stosu (R6) do Rejestru Adresowego Pamięci (RAP)
wraz z mikrooperacją „odczytaj” (w ten sposób zostanie zlecone odczyta-
nie słowa, które zostało na stos zapisane jako ostatnie).
3. Poczekaj do zakończenia mikrooperacji odczytu, zleconej wyżej, w kroku 2.
4. Zawartość Rejestru Buforowego Pamięci zapisz do licznika rozkazów
(R7).
W rezultacie wraz z końcem cyklu adres powrotu do programu wy-
wołującego dany podprogram wyląduje znów w liczniku rozkazów. W na-
stępnym cyklu zostanie więc pobrana i wykonana instrukcja następująca za-
raz po rozkazie JSR, który całe to wywołanie spowodował. Po niej nastąpią
dalsze, stanowiące kontynuację wznowionego programu.
Łatwo sprawdzić, że konsekwentne stosowanie tak działających roz-
kazów skoku do podprogramu oraz powrotu z podprogramu daje możli-
wość zagnieżdżania wywołań (wielokrotnego wywoływania podprogramów
z podprogramów), w tym także – rekurencyjnego wywoływania funkcji.
Jeśli wrócimy choć na chwilę do rozdziału 4 i rysunku 4.3, to uświadomimy
sobie, jak ważnym krokiem w rozwoju technik programowania było zasto-
sowanie mechanizmu stosu LIFO.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

436 17. Procesor

Stos można wykorzystać także do innych celów. Wyobraźmy oto so-


bie, że pewna funkcja potrzebuje do swojego obliczenia czterech rejestrów
procesora. Niech będą to – dla przykładu – rejestry R0, R1, R2 oraz R3.
Funkcja ta (podprogram) zapewne sprowadzi do nich jakieś swoje dane i bę-
dzie tam zapisywać jakieś wyniki cząstkowe. Ale przecież te rejestry mogą
być wykorzystywane także przez program, który daną funkcję wywołuje!
Może w chwili wywołania funkcji w tych rejestrach znajdują się jakieś waż-
ne dane, które będą programowi wywołującemu potrzebne do dalszych ob-
liczeń zaraz po zakończeniu podprogramu? Zawartość tych rejestrów trzeba
więc koniecznie uchronić przed zniszczeniem.
Jedno z rozwiązań polega na zapamiętaniu zawartości tych rejestrów
na stosie. Wystarczy, by podprogram (który przecież „wie”, jakie rejestry
będą mu potrzebne) zaraz na początku działania odłożył wspomniane reje-
stry na stos, a na sam koniec, już po swoich obliczeniach i tuż przed rozka-
zem RET, sprowadził je z powrotem na stare miejsce. Należy jedynie pa-
miętać, że jeśli rejestry były zapisywane na stos np. w kolejności: R0, R1,
R2, R3, to muszą być ze stosu odczytywane w kolejności odwrotnej: R3,
R2, R1, R0. Proszę sprawdzić: jeśli tylko ta konwencja jest przestrzegana,
to składowanie na stosie informacji innych niż adresy – nie zakłóca mecha-
nizmu zagnieżdżania podprogramów w podprogramach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

18. System przerwań

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

438 18. System przerwań

drugiej, kolejne instrukcje pewnego programu. Jak zatem komputer może


wykonywać wiele programów jednocześnie?
Prawda jest taka, że w jednoprocesorowym systemie wszystkie pro-
gramy zainstalowane (przez nas czy przez system) w pamięci operacyjnej
dzielą się między sobą procesorem. W każdej chwili rzeczywiście wykonuje
się co najwyżej jeden z nich, ale system umożliwia sprawne przełączanie się
między nimi. Zachodzi to tak szybko, że użytkownikowi, w jego skali cza-
su, wydaje się, że wszystkie programy działają równolegle i jednocześnie.
Zasada ta dotyczy nie tylko używanych przez nas aplikacji, ale wszelkich
programów, również systemowych.
System przerwań (ang. interrupt system) – to złożony układ podze-
społów sprzętowych i elementów systemowego oprogramowania, niezbęd-
ny właśnie do organizacji współdziałania między różnymi programami.
W procesie takiej koordynacji działań różnych programów ważną rolę
gra system operacyjny komputera. Jest to też program, a ściślej – cały zbiór
programów, którego podstawowa część zostaje uruchomiona zawsze w mo-
mencie włączenia komputera do pracy. Inne programy mogą być instalowa-
ne w pamięci operacyjnej lub z niej usuwane, ale system operacyjny jest
obecny zawsze. Dla systemu operacyjnego wszelkie inne programy (a nawet
jego własne części) są zadaniami (ang. task), a do podstawowych funkcji
systemu operacyjnego należy zarządzanie nimi (ang. task management).
W każdym takim zadaniu są fragmenty, których wykonanie polega na
cyklicznym pobieraniu i wykonywaniu przez procesor kolejnych instrukcji
maszynowych, tak jak to opisaliśmy w poprzednim rozdziale. Praktycznie
nieuchronne jest jednak, że program zechce wreszcie wykonać operację
poza procesorem: na przykład zapisać blok danych do pamięci dyskowej,
wczytać nowy znak z klawiatury i tak dalej. Takie zewnętrzne operacje
trwają z zasady długo, bardzo długo w porównaniu z czasem wykonywania
instrukcji przez procesor. W czasie, jaki upływa na przykład między dwoma
naciśnięciami klawiszy na klawiaturze, procesor byłby w stanie wykonać
kilkadziesiąt, jeśli nie kilkaset tysięcy instrukcji. Dlatego program, czy też
zadanie, które zleca zewnętrzną operację, sam usuwa się i oddaje na ten
czas procesor innemu zadaniu. Jest to więc tak, jak gdyby aktualnie wyko-
nywane zadanie mówiło reszcie systemu: „Idę spać. Proszę mnie obudzić,
kiedy wystąpi takie a takie zdarzenie”. Tym zdarzeniem będzie w tym przy-
padku właśnie zgłoszenie (przez urządzenie zewnętrzne) odpowiedniego
przerwania.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada przerwań 439

Ale dlaczego to się nazywa „przerwanie”, a nie na przykład „wznowie-


nie”? Ta nazwa jest całkowicie usprawiedliwiona. Po „zaśnięciu” pierwszego
zadania, zarządca zadań spowoduje, że procesor zacznie wykonywać inne
zadanie. Być może również i ono „pójdzie spać”, ustępując miejsca następ-
nemu, a to – jeszcze następnemu… i tak dalej. Kiedy wreszcie owo pierwsze
oczekiwane zdarzenie wystąpi – trzeba będzie rzeczywiście przerwać jakiś
Bogu ducha winny program, który z pierwotnym zleceniodawcą nie ma nic
wspólnego i zbudzić tego, kto na to zdarzenie oczekiwał.
Początkowo, mechanizm przerwań powstał właśnie po to, by w wyżej
opisany sposób obsługiwać wykonywanie zewnętrznych operacji, zlecanych
przez programy aplikacyjne. Szybko zorientowano się jednak, że można
go wykorzystać również w innych sytuacjach. Można na przykład ustawić
programy aplikacyjne w cykliczną kolejkę (jak gdyby „w kółko”) i każdy
z tych programów przerywać po upływie ustalonego odcinka czasu (np. rzę-
du kilku lub kilkudziesięciu milisekund) po to, by przekazać procesor na
kolejny odcinek czasu następnemu w kolejce. To dzięki tej technice (zwanej
pracą z podziałem czasu, ang. time sharing) wszystkie aplikacje wykonują
się pozornie równolegle (przynajmniej dla człowieka), choć naprawdę – po-
suwają się do przodu małymi kawałkami, tak że procesor zajmuje się w każ-
dej chwili tylko co najwyżej jednym z nich.
W tym przypadku przerwanie produkuje specjalny zegar systemowy,
który (np. zliczając cykle tego samego zegara, który „napędza” synchro-
niczne przerzutniki) odlicza owe umówione odcinki czasu. Jednak również
i tutaj musi istnieć zadanie, które zbudzi się, gdy takie przerwanie wystąpi.
Dlatego podczas uruchamiania systemu instaluje się systemowy program
szeregujący (ang. scheduler), który od razu zasypia, mówiąc: „Proszę mnie
zbudzić, kiedy zegar zgłosi przerwanie”. Kiedy to nastąpi, ów program zo-
stanie zbudzony, przejrzy kolejkę programów aplikacyjnych, stwierdzi, któ-
ry z nich powinien (i może) być teraz wznowiony, przekaże mu procesor
i znów zaśnie, aż do następnego przerwania zegarowego.
Na identycznej zasadzie system reaguje na różne sytuacje nadzwy-
czajne, w tym awaryjne. Dobrym przykładem może być zdarzenie polega-
jące na zaniku napięcia zasilającego. Źródłem zgłaszającym przerwanie jest
wtedy specjalny elektroniczny układ, dobudowany do komputerowego zasi-
lacza. Oczywiście, w momencie uruchamiania systemu musi być zainstalo-
wany odpowiedni systemowy program, który jest „uśpiony”, ale zbudzi się,
gdy nastąpi awaria w sieci elektrycznej.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

440 18. System przerwań

W momencie awarii napięcie w sieci raptownie spada. Dla człowie-


ka zachodzi to momentalnie: światła gasną, telewizor wyłącza się, lodówka
przestaje działać. Jednak w rzeczywistości spadek napięcia, jak każde fi-
zyczne zjawisko, ma swoją bezwładność i trwa przez znaczny (w porówna-
niu z szybkością procesora) ułamek sekundy. Układ zasilacza szybko zauwa-
ża, że napięcie spada poniżej pewnego zadanego poziomu bezpieczeństwa.
Zgłasza wtedy przerwanie, które przerywa każdy aktualnie wykonywany
program, a obudzony program reakcji na awarię zasilania powoduje przełą-
czenie komputera na zasilanie awaryjne (np. z akumulatora) lub zapamiętuje
przez te kilkadziesiąt czy kilkaset milisekund w trwałej pamięci wszystkie
informacje potrzebne do późniejszego wznowienia pracy systemu.
Innym przykładem sytuacji nadzwyczajnej (niekoniecznie awaryjnej)
może być włączenie nowego urządzenia (np. modemu, pen drive’a), za-
mknięcie szufladki z płytką CD, brak papieru w drukarce, zerwanie połącze-
nia z siecią telekomunikacyjną i tak dalej. Każde z takich zdarzeń jest sygna-
lizowane przerwaniem. Przerwania te zgłaszają urządzenia zewnętrzne lub
inne przewidziane do tego celu podzespoły komputera, jak np. wspomniany
zasilacz albo magistrala systemowa, która może sygnalizować – powiedzmy
– przekroczenie limitu czasu przewidzianego na transfer danych. Dla każde-
go z takich przypadków musi być opracowany program, który – początkowo
uśpiony – zbudzi się i odpowiednio zareaguje na dane przerwanie.
Tak więc w czasie pracy komputera w jego pamięci operacyjnej zain-
stalowane są nie tylko programy aplikacyjne (które zapewne sami urucho-
miliśmy), ale także kilka dziesiątek systemowych programów, które „śpią”,
czekając na pojawienie się pewnych zdarzeń. Z drugiej strony, w jednost-
kach sterujących urządzeń zewnętrznych, w zasilaczu, procesorze itd. istnie-
ją podzespoły, których zadaniem jest wykrywanie tych zdarzeń i zgłaszanie
przerwań. Sprzętowa część systemu przerwań ma więc za zadanie dopaso-
wanie zgłoszeń przerwań do programów reakcji na nie.
Trzeba przy tym rozwiązać kilka nieprostych problemów. Po pierw-
sze, jest rzeczą oczywistą, że przerwanie jakiegoś programu nie może spo-
wodować zniszczenia ani jego samego, ani jego danych. Musi być zorga-
nizowane tak, by w przyszłości można było ten program wznowić i bez
przeszkód kontynuować.
Po drugie, zgłoszenia przerwań mają różny poziom ważności czy pil-
ności. Zgłoszenie awarii zasilania powinno być obsłużone jak najszybciej,
ale nic się zapewne nie stanie, jeśli reakcja na przerwanie sygnalizujące na-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Zasada przerwań 441

ciśnięcie klawisza myszy lub włożenie płytki CD do czytnika opóźni się


o ułamek sekundy. Jeśli więc pojawia się kilka różnych przerwań jednocześ-
nie, to jak odróżnić to, któremu trzeba przyznać pierwszeństwo? Któremu
oddać procesor, by mógł się wykonać „jego” program?
Po trzecie wreszcie, systemowe programy reakcji na przerwania są
również programami. Czy one także mogą być przerywane? Zapewne, jedne
mogą, inne – nie, zależnie od sytuacji. Co więcej, jeśli mogą być przerywa-
ne, to nie można wykluczyć, że będą się zagnieżdżać („przerwania w prze-
rwaniach”), podobnie jak podprogramy w podprogramach. Jak sobie pora-
dzić z takimi sytuacjami?
O tym, jak sprzętowa część systemu przerwań rozwiązuje te proble-
my – opowiemy za chwilę. Ten zaś wprowadzający podrozdział zamknijmy
następującymi dwoma uwagami.
Po pierwsze zauważmy, że gdyby systemu przerwań nie było, to każ-
dy, raz uruchomiony program wykonywałby się nieuchronnie aż do samego
końca, tak jak to się dzieje – przynajmniej co do zasady – w abstrakcyj-
nej maszynie Turinga. W tym czasie niemożliwy byłby jakikolwiek kontakt
maszyny z otoczeniem: z operatorem, urządzeniem zewnętrznym, zegarem
odmierzającym astronomiczny czas, z siecią, innym komputerem, z aparatu-
rą pomiarową czy technologiczną… Dzięki systemowi przerwań komputer
może natomiast reagować na zdarzenia zachodzące w jego otoczeniu, a więc
– komunikować się z tym otoczeniem.
Po drugie, z tego co napisaliśmy wyżej wynika, że w praktyce prze-
bieg każdego programu użytkowego jest wielokrotnie przerywany i potem
– wznawiany, ale w sposób dla niego w zasadzie niezauważalny. W zasa-
dzie, bo jednak niezupełnie: za każdym razem program „traci z życiorysu”
ułamki sekundy, w rezultacie czego jego działanie trwa nieco dłużej, niż
gdyby wykonywał się bez przerwy.
Dla większości zwykłych („domowych” czy „biurowych”) aplikacji
nie ma to praktycznie znaczenia. Jednak w przypadku systemów pracują-
cych w czasie rzeczywistym – na przykład w przypadku oprogramowania
sterującego pracą różnych urządzeń, pojazdów, robotów, samolotów, rakiet
itp. – wymagania co do czasu wykonywania fragmentów programów mają
znaczenie zupełnie podstawowe. Tu twórcy oprogramowania muszą dosko-
nale znać organizację systemu przerwań, a nawet sami muszą uczestniczyć
w projektowaniu (lub „dostrajaniu” przez dobór odpowiednich wartości pa-
rametrów) jego software’owej części.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

442 18. System przerwań

Od zgłoszenia przerwania do reakcji układu


sterowania procesora
Powiedzmy, że w pewnej chwili procesor jest zajęty wykonywaniem jakiegoś
programu. Może to być program aplikacyjny albo systemowy. Wiemy już, co to
znaczy: układ sterowania procesora cyklicznie pobiera kolejny rozkaz maszyno-
wy z pamięci operacyjnej, zwiększa licznik rozkazów i wykonuje pobrany roz-
kaz, znów pobiera kolejny rozkaz i go wykonuje… i tak dalej, zgodnie z zasadą
cyklu rozkazowego. Jednocześnie w pamięci operacyjnej przebywają „uśpione”
programy reakcji na przerwania.
Tymczasem w różnych jednostkach komputera są aktywne różne źró-
dła przerwań. Są to owe elektroniczne podzespoły, wykrywające (niezależ-
nie od siebie i niezależnie od tego, co się w procesorze dzieje) zdarzenia
takie, jak zakończenie zewnętrznej operacji, koniec odcinka czasu, naciśnię-
cie klawisza, spadek napięcia zasilania itp., które – ich zdaniem – zasłu-
gują na zgłoszenie przerwania. Ponieważ aktywnych źródeł przerwań jest
wiele i działają one zupełnie niezależnie i asynchronicznie względem siebie
– zgłoszeń przerwań może się pojawić jednocześnie kilka.
Zlokalizowana w procesorze sprzętowa (hardware’owa) część syste-
mu przerwań jest zbudowana tak, by potrafiła przyjąć zgłoszenie przerwania,
bezpiecznie przerwać aktualnie wykonywany program, a następnie urucho-
mić systemowy program obsługi tego przerwania. Od tej chwili układ ste-
rowania procesora znów podejmie pracę zgodnie z zasadą cyklu rozkazowe-
go: będą wykonywane kolejno, jeden po drugim, rozkazy programu obsługi
przerwania. Sytuacja wróci więc do tej, którą opisaliśmy na początku. Znów
wykonywany jest jakiś program (tym razem jest to ów systemowy program
obsługi przerwania), znów napływają zgłoszenia przerwań… i tak dalej.
Wspomnieliśmy, że jednocześnie system przerwań musi poradzić so-
bie z następującymi pytaniami:
– Które z możliwych wielu zgłoszeń przerwań wybrać jako najważniej-
sze?
– Czy aktualnie wykonywany program w ogóle można w tej chwili prze-
rwać?
– Jeśli można, to jak ten aktualnie wykonywany program przerwać w taki
sposób, by była możliwa jego kontynuacja w przyszłości?
– Któremu z systemowych programów powierzyć procesor, by zajął się
dalszą obsługą przerwania?
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Od zgłoszenia przerwania do reakcji układu sterowania procesora 443

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

444 18. System przerwań

tych iloczynów logicznych są z kolei wejściami logicznej sumy. Ten frag-


ment układu umożliwia maskowanie (czy ignorowanie) zgłoszeń przerwań,
którymi w danej chwili nie chcemy się zajmować.
Zapełnienie Rejestru Maski Przerwań samymi jedynkami spowodowa-
łoby, że na wyjściu każdego z iloczynów logicznych pojawi się taki stan,
jaki jest na odpowiadającej mu linii zgłoszeń. Jeśli żadne ze źródeł dołączo-
nych do tej linii nie zgłasza przerwania – to zero, jeśli którekolwiek zgłasza
– to jeden. Żadna z linii zgłoszeń przerwań nie jest wtedy „zamaskowana”.
Umieszczenie natomiast zera na którejś z pozycji rejestru maskującego po-
woduje, że na wyjściu odpowiedniego iloczynu logicznego pojawi się z pew-
nością również zero, nawet wtedy, gdy wiele źródeł dołączonych do tej li-
nii domaga się przerwania. Zgłoszenia na tej linii są wtedy zamaskowane:
niezależnie od tego, czy zgłoszenia tam naprawdę są, czy ich nie ma – nie
powodują żadnej dalszej akcji. Przestaną być ignorowane dopiero wtedy, gdy
do rejestru maskującego wpiszemy nową maskę, która ma jedynkę na danej
pozycji2.
W rezultacie, logiczna jedynka na wyjściu sumy logicznej oznacza, że
co najmniej jedno ze zgłoszeń przedarło się przez przeszkodę maskowania
i domaga się obsługi. Nie oznacza to jednak wcale, że natychmiast zostanie
obsłużone. Musi pokonać jeszcze jedną przeszkodę, w postaci iloczynu lo-
gicznego, którego drugim argumentem jest wyjście zbiorczego Przerzutnika
Blokady Przerwań3.
Przerzutnik ten jest również programowo dostępny, to znaczy, że
jego stan może być ustawiony odpowiednim rozkazem maszynowym. Dla
systemowego programisty jest to wskaźnik (ang. flag) blokady przerwań.
Jeżeli stan tego przerzutnika jest ustawiony na zero – wyjście końcowego
iloczynu logicznego jest również w stanie 0. Ignorowane są wtedy wszyst-
kie przerwania, łącznie nawet z tymi zgłoszeniami o najwyższym prioryte-

2 W wielu rozwiązaniach nie dopuszcza się maskowania zgłoszeń przerwań na li-


nii o najwyższym priorytecie. Najbardziej znaczący bit rejestru maskującego ma
wtedy stałą wartość równą 1 i nie można jej zmienić.
3 Często nazywa się on Interrupt Enable Flag (IEF) lub Interrupt Disable Flag
(IDF), zależnie od tego, czy wyłączenie układu przerwań jest powodowane wsta-
wieniem tam wartości 0 czy 1. Po polsku nazywa się go często Zbiorczą Maską
Przerwań.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Inicjowanie obsługi przerwania 445

cie, których ukryć za pomocą mechanizmu maskowania nie można. Dopiero


w przypadku, gdy ów wskaźnik (przerzutnik) blokady przerwań zostanie
przestawiony w stan 1 – zgłoszenie przerwania może dotrzeć do układu ste-
rowania procesora i spowodować jego reakcję.
Co się wtedy stanie w układzie sterowania? Rzućmy okiem na rysunek
17.2, na którym pokazano uproszczony graf cyklu rozkazowego. Pojawienie
się (niezamaskowanego i niezablokowanego) zgłoszenia przerwania zastaje
układ sterowania w jakimś zupełnie przypadkowym stadium cyklu rozkazo-
wego. Sekwenser kontynuuje więc najpierw już rozpoczęty mikroprogram
aż do końca danego cyklu rozkazowego, nie dostrzegając w ogóle, że poja-
wiło się zgłoszenie przerwania.
Dopiero na samym końcu cyklu sekwenser staje przed decyzją, zależ-
ną od stanu wyjścia ostatniego iloczynu logicznego w układzie przerwań.
Jeśli jest tam logiczne zero, to przerwania nie ma albo zostało zamaskowane
lub zablokowane. Można wtedy przejść z powrotem do początku nowego
cyklu i rozpocząć pobieranie kolejnego rozkazu z dotychczas wykonywane-
go programu. Jeśli jednak jest tam jedynka – to znak, że jakieś przerwanie
oczekuje na obsłużenie. Sekwenser wchodzi wtedy w mikroprogram, któ-
ry zmierza do przerwania programu aktualnie wykonywanego i zainicjo-
wania zupełnie nowego. Tym nowym będzie oczywiście jeden z systemo-
wych programów obsługi przerwań.

Inicjowanie obsługi przerwania


Pierwszą czynnością w tym mikroprogramie jest zapamiętanie podstawo-
wych informacji o przerywanym programie, tak by można go było w przy-
szłości wznowić bez żadnej dla niego szkody. Te informacje mają postać
dwóch słów maszynowych. Nazywa się je łącznie wektorem przerwań (ang.
interrupt vector).
Jedno z tych słów (rysunek 18.1) zawiera oczywiście aktualną za-
wartość licznika rozkazów (PC, u nas – R7). Od tego adresu rozpocznie się
w przyszłości, po wznowieniu programu, pobieranie jego kolejnych instruk-
cji. Druga część składowa wektora przerwań – to słowo stanu procesora,
często nazywane skrótem PSW (ang. Processor Status Word). Skopiowane
są do niego (i zebrane w jednym miejscu) inne ważne informacje z wnętrza
procesora, które charakteryzują właśnie stan (status) programu i są absolut-
nie niezbędne do późniejszego jego wznowienia.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

446 18. System przerwań

Rys. 18.1. Wektor przerwania

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

Inicjowanie obsługi przerwania 447

komputerowe) przerwania z urządzeń zewnętrznych, na które oczekują inne


aplikacje. Takich zastrzeżonych instrukcji jest więcej. Dla zachowania inte-
gralności systemu zwykłemu użytkownikowi należy nie tylko zakazać ich
stosowania, ale mu to wręcz uniemożliwić.
Dlatego wprowadzono dwa tryby pracy procesora, rozróżniane właś-
nie wartością wspomnianego binarnego wskaźnika system-użytkownik.
Fizycznie – odpowiada mu przerzutnik w układzie sterowania procesora.
Ma on być w stanie 1, jeśli w danej chwili procesor wykonuje program
systemowy, zaś w stanie 0 – gdy przetwarzany jest zwykły program aplika-
cyjny. Jednocześnie, do dekodera, tego samego, który bada zawartość części
operacyjnej rozkazu znajdującego się w Rejestrze Instrukcji, dobudowano
fragment, który wykrywa pojawienie się instrukcji zastrzeżonej dla oprogra-
mowania systemowego.
Jeżeli wskaźnik system-użytkownik ma wartość zero (a więc pokazuje,
że właśnie wykonuje się zwykła aplikacja), to wykrycie w Rejestrze Instrukcji
części operacyjnej rozkazu zastrzeżonego powoduje wyprodukowanie zgło-
szenia przerwania (o bardzo wysokim priorytecie) i całkowite ominięcie fazy
wykonania tej instrukcji. Wraz z końcem cyklu rozkazowego zostanie wtedy
wywołany program obsługi przerwania, który zakończy działanie aplikacji
i wyświetli odpowiedni komunikat ostrzegawczy.
Wróćmy do wektora przerwań. Mikroprogram inicjujący obsługę
przerwania zapamiętuje oba te słowa na systemowym stosie. Operacja ta
przebiega tak samo, jak opisane w rozdziale 17 zapisywanie (przy skoku do
podprogramu) na stos zawartości licznika rozkazów, z tym że teraz czynno-
ści są wykonywane dwukrotnie: pierwszy raz dla licznika rozkazów, drugi
– dla słowa stanu procesora.
Następnie, w kolejnym kroku mikroprogramu, układ sterowania proce-
sora zawiadamia arbitra systemowej magistrali, że może przystąpić do identy-
fikacji źródła zgłoszenia przerwania. Jednocześnie przesyła mu stan linii zgło-
szeń (po zamaskowaniu) lub wytworzony z nich umowny kod przerwania. To
z kolei sytuacja, którą przedstawiliśmy schematycznie wcześniej, na rysunku
16.2. W rezultacie jedno ze źródeł przerwań uzyskuje (jako nowy master)
przydział magistrali i wystawia na linie danych kolejno, w dwóch krokach,
zawartość nowego wektora przerwań: na przykład najpierw nowe słowo stanu
procesora (PSW), potem nową zawartość licznika rozkazów (PC).
Skąd jednak źródło zgłoszenia przerwania zna zawartość swojego
wektora przerwań? Przypisywanie źródłom przerwań odpowiadających im
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

448 18. System przerwań

wektorów zachodzi na etapie konfigurowania systemu. Na przykład przy


dołączaniu nowego urządzenia zewnętrznego system operacyjny wczytuje
oprogramowanie sterujące dla tego urządzenia (zawierające m.in. programy
obsługi przerwań) i przydziela wektory przerwań wszystkim źródłom, któ-
re w tym urządzeniu występują. Od tej pory każdemu ze źródeł wiadomo,
gdzie w pamięci operacyjnej zaczyna się systemowy program obsługi tego
przerwania (wskazuje na to początkowa wartość PC), jaka grupa przerwań
może ten program przerwać (maska przerwań) oraz to, że jest to program
systemowy, a nie użytkowy4.
Mikroprogram w układzie sterowania pobiera te informacje z magi-
strali (dokładniej – z jej linii danych) i umieszcza je tam, gdzie trzeba: nową
wartość wskaźnika system-użytkownik w przerzutniku system-użytkownik,
maskę przerwań – w Rejestrze Maski, nową wartość PC – w Rejestrze
Licznika Rozkazów (R7).
Na zakończenie działania mikroprogram inicjujący obsługę przerwa-
nia powoduje – co bardzo ważne – zablokowanie układu przerwań, ustawia-
jąc przerzutnik blokady przerwań w stan 0.
Po wykonaniu tych wszystkich czynności sekwenser przechodzi znów
do początku cyklu rozkazowego. Wiemy, co teraz nastąpi: pobranie nowego
rozkazu z komórki wskazywanej przez aktualną zawartość licznika rozka-
zów, sprowadzenie go do Rejestru Instrukcji, rozpoznanie części operacyj-
nej, wykonanie… i tak dalej. Będzie więc wykonywany program obsługi
danego przerwania.

Program obsługi przerwania


Przypomnijmy, że tuż przed pobraniem pierwszego rozkazu nowego progra-
mu cały system przerwań został zablokowany. Program obsługi przerwania
ma więc tę luksusową sytuację, że z pewnością nie zostanie natychmiast
przerwany, nawet przez przerwanie o wyższym od niego priorytecie. Aby
system przerwań zaczął znowu działać – w programie obsługi przerwania
musi znaleźć się (na wyraźne życzenie piszącego ten program programisty)
zabroniona dla użytkownika instrukcja, która ponownie odblokuje przerzut-
nik blokady przerwań.

4 Analogiczny wektor przerwań system operacyjny przydziela także każdemu nowo


instalowanemu programowi użytkowemu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Program obsługi przerwania 449

Programy obsługi przerwań mają więc pewną typową budowę, której


konwencji muszą ściśle przestrzegać systemowi programiści, układający ta-
kie programy.
Każdy program obsługi przerwania startuje jako proces całkowicie na
inne przerwania niewrażliwy. W tej fazie programu wykonuje się czynności,
które muszą być wykonane bez żadnych zakłóceń. Programista może uznać, że
należy do nich np. zapamiętywanie zawartości potrzebnych później rejestrów
procesora na systemowy stos lub np. modyfikacja maski przerwań. Po zakoń-
czeniu tych czynności programista włącza układ przerwań, a od tego momentu
o przerywaniu programu decyduje wyłącznie mechanizm maskowania.
W dalszej części programu wykonuje się te czynności, które stanowią
merytoryczną część obsługi danego przerwania. W praktyce najczęściej po-
lega ona na „obudzeniu” programu, który zlecił wykonanie operacji w urzą-
dzeniu zewnętrznym i właśnie doczekał się przerwania, sygnalizującego, że
operacja została zakończona.
Po wykonaniu tych czynności program „sprząta po sobie”, sprowa-
dzając ze stosu do odpowiednich rejestrów procesora (w odwrotnej kolejno-
ści!) wszelkie dane i adresy, które tam przedtem umieścił.
Na samym końcu programu obsługi przerwania pojawia się rozkaz
maszynowy o części operacyjnej oznaczanej zazwyczaj skrótem RETI, od
ang. Return from Interrupt. To instrukcja powrotu z przerwania. Odczytuje
ona z systemowego stosu (znów w odwrotnej kolejności) wektor przerwania,
odłożony tam w chwili przerwania poprzedniego programu. Słowo zawiera-
jące licznik rozkazów trafia do R7, a poszczególne części słowa stanu pro-
cesora wędrują tam, skąd je poprzednio pobrano (do rejestrów warunku, do
rejestru maskującego, do przerzutnika system-użytownik).
W ten sposób odtworzony zostaje – jak to się mówi – kontekst, w któ-
rym wykonywał się poprzednio przerwany program. Z odtworzeniem dawnej
zawartości rejestrów uniwersalnych procesora uporał się już program obsługi
przerwań w fazie „sprzątania po sobie”. Na podstawie przechowanego na
stosie wektora przerwań odtworzone zostały inne informacje, potrzebne do
działania układowi sterowania procesora. Zakończenie instrukcji RETI i po-
wrót do początku cyklu rozkazowego spowoduje więc, że poprzednio prze-
rwany program będzie kontynuowany bez przeszkód.
Tak więc również i tu zastosowanie mechanizmu stosu LIFO w po-
mysłowy i konsekwentny sposób zapewnia prawidłowe zagnieżdżanie prze-
rwań w przerwaniach.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

450 18. System przerwań

Warto podkreślić, że w systemie wykorzystuje się wiele stosów LIFO.


Dla każdego z programów można zorganizować w przypisanym mu obsza-
rze pamięci własny, lokalny stos. Będzie on wykorzystywany do organizacji
współpracy z jego podprogramami, łącznie z zagnieżdżaniem, przekazy-
waniem parametrów itd. Jednocześnie, istnieje również wspólny stos sys-
temowy, służący systemowi operacyjnemu do organizacji zagnieżdżonych
przerwań.
Taki podział funkcji między różne stosy nie tylko chroni przed możli-
wymi błędami przy przełączaniu się między programami w trybie podziału
czasu (time sharing), ale ponadto zmniejsza prawdopodobieństwo przepeł-
nienia się stosu, to znaczy wyjścia poza przewidziany na stos obszar pa-
mięci.
Można powiedzieć, że zawartość każdego dobrze zorganizowanego stosu
LIFO przypomina dobrze skonstruowane skomplikowane matematyczne wyra-
żenie z nawiasami. Może być ono bardzo złożone, mogą w nim występować
różne zmienne oraz wielokrotnie zagnieżdżone „nawiasy w nawiasach” – ale
jeśli każdy nawias otwierający ma (do pary) swój nawias zamykający, to kolej-
ność wykonywania działań będzie poprawna.
I tak – w zarysie – działa system przerwań. Jest on złożonym me-
chanizmem, który stanowi pomost pomiędzy sprzętem (hardware) kompu-
tera a jego systemowym oprogramowaniem. Gdybyśmy mieli kontynuować
wykład o działaniu systemu komputerowego – teraz właśnie nadszedłby
czas na zajęcie się działaniem jądra systemu operacyjnego (ang. kernel), to
znaczy tej najbardziej wewnętrznej i zbliżonej do sprzętu części systemu
operacyjnego, która udostępnia podstawowe mechanizmy koordynacji i ko-
munikacji między różnymi software’owymi procesami.
Jest to jednak tematyka zbyt zaawansowana, jak na książkę o takim
charakterze, jak ta, którą mamy właśnie przed sobą. Dlatego, nie wdając się
w dłuższy wykład, ograniczymy się tu do wzmianki o jednym tylko zagad-
nieniu. Jednym, za to podstawowym dla teorii i praktyki projektowania sys-
temów operacyjnych: o pojęciu procesu.

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

Programy i procesy 451

kroki przetwarzania. Wykonania tego samego programu może sobie jed-


nak życzyć kilku zleceniodawców. Każdy z nich ma własny zestaw danych
i chce, by kolejne instrukcje programu odwoływały się do jego danych.
Każde takie wykonanie pewnego programu na oddzielnym zestawie danych
jest właśnie procesem.
Oczywiście, każde z takich wykonań – procesów – może przebiegać
inaczej, nawet jeśli te procesy posuwają się po tym samym kodzie – progra-
mie. W jednym procesie okaże się na przykład, że dla jego danych pewien
warunek przybierze wartość „tak” i proces skieruje się w lewą gałąź sieci
działań, w innym – tenże warunek, dla innych danych okaże się niespełniony
i proces pójdzie prawym odgałęzieniem tego samego fragmentu programu.
Każdy proces ma więc swój licznik rozkazów, który wskazuje, który rozkaz
programu ma być w jego przypadku wykonany jako następny.
Ale każdy proces ma również swoje bieżące wartości warunków N,
Z, V, C, a także swoją wartość maski przerwań, która go sytuuje w hierar-
chii przerwań. O każdym można stwierdzić, czy jest systemowy, czy działa
na rzecz pewnej użytkowej aplikacji. Te właśnie informacje o procesie są
zebrane w jego wektorze przerwań. Sprowadzenie takiego wektora do pro-
cesora i rozmieszczenie go w odpowiednich rejestrach spowoduje, że dany
proces zacznie się wykonywać (lub że będzie kontynuowany). Zapamiętanie
wektora przerwań na systemowym stosie umożliwia przerwanie procesu
i bezpieczne wznowienie w przyszłości.
Widzimy więc, że system przerwań w rzeczywistości zarządza prze-
rywaniem i wznawianiem procesów, a nie – jak przedtem dla prostoty wy-
kładu pisaliśmy – programów. Dzięki temu procesy mogą się m.in. dzielić
czasem procesora i wykonywać się (pozornie) równolegle.
Jak jednak sprawić, by kilka procesów „idących” po tym samym ko-
dzie programu (choć każdy swoją drogą) wykonywało się równolegle, na
odrębnych, własnych danych? Nie jest to wcale trudne, choć wymaga wpro-
wadzenia pewnych dodatkowych konwencji, rozbudowy wnętrza procesora
o kilka dodatkowych rejestrów, a także niewielkiej modyfikacji mikropro-
gramów składających się na cykl rozkazowy.
Każdemu procesowi przydziela się pewien obszar w pamięci, ograni-
czony adresami początku i końca. Dla przykładu powiedzmy, że utworzono
dwa procesy, A1 i A2, oba posuwające się po kodzie tego samego progra-
mu A. Jednak dane dla procesu A1 umieszczono w polu pamięci pomiędzy
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

452 18. System przerwań

adresami 80000 a 89999, a dane procesu A2 – pomiędzy adresami 90000


a 100000.
Na czas wykonywania procesu oba adresy: początku i końca indywi-
dualnego pola danych tego procesu, umieśćmy w dwóch dodatkowych reje-
strach procesora. Niech te rejestry będą połączone z wejściami i wyjściem
arytmometru tak, by można było wykonywać na nich operacje arytmetycz-
ne, podobnie jak na zawartości innych rejestrów procesora.
Umówmy się także, że wszelkie programy są tłumaczone na język
maszynowy tak, jak gdyby dla każdego z nich obszar zajęty przez dane roz-
poczynał się w komórce o adresie 0. Adresy danych, do których będą się od-
woływały poszczególne instrukcje programu (w fazie wykonania instrukcji),
są więc liczone umownie względem zera.
Aby uzyskać fizyczny adres każdego odwołania do danych – nale-
ży dodać względny adres wyliczany w programie do początkowego adresu
pola danych procesu, który aktualnie się wykonuje.
Powiedzmy dla przykładu, że pewna instrukcja w programie A ma na-
stępującą treść: „sprowadź zawartość komórki 555 do rejestru R3”. W trakcie
trwania procesu A1 do rejestru R3 zostanie sprowadzona dana, znajdująca
się naprawdę w komórce 80000 + 555 = 80555, a wtedy, kiedy wykonuje się
proces A2 – dana z komórki 90000 + 555 = 90555. Na tej zasadzie statyczne
(względne) adresy pochodzące z programu są w procesorze dynamicznie mo-
dyfikowane, w trakcie trwania procesu.
Równocześnie, żaden proces nie może odwoływać się do nieswojego
obszaru pamięci. Dlatego, po wyliczeniu adresu fizycznego (a jeszcze przed
skierowaniem do pamięci operacyjnej zlecenia zapisania lub odczytania sło-
wa) trzeba sprawdzić, czy ten adres nie przekracza wartości, zapisanej (jako
adres końca pola danych) w tym drugim z dodatkowych rejestrów procesora.
Gdyby tak było – należy zgłosić przerwanie (awaryjne, o wysokim prioryte-
cie) i natychmiast zakończyć cykl rozkazowy.
Opisany sposób dynamicznego manipulowania adresami wymaga
oczywiście – obok dodatkowych rejestrów procesora – także pewnej mo-
dyfikacji mikroprogramów składających się na cykl rozkazowy. Procedura
przerywania i wznawiania procesu także nieco się komplikuje. Nie wystar-
czy przechowywanie na stosie samego wektora przerwań: do działania pro-
cesu potrzebna jest także znajomość adresów początku i końca jego pola
w pamięci operacyjnej. Je również należy przechować w chwili przerwania
i odtworzyć przed wznowieniem procesu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Programy i procesy 453

Cenę tę warto jednak zapłacić. Procesy są bardzo wygodnymi dy-


namicznymi jednostkami działania w systemie. Można je tworzyć i likwi-
dować. Aby utworzyć nowy proces – wystarczy skompletować opisujące
go informacje (graniczne adresy jego obszaru w pamięci operacyjnej oraz
odpowiednią zawartość wektora przerwań) – i wstawić je do wykazu proce-
sów, którymi zarządza manager zadań. Aby proces „zabić” (takim okrutnym
terminem istotnie często posługują się informatycy) – należy ten komplet
informacji o procesie z tejże listy usunąć.
Można także stosować różne formy koordynacji działań między pro-
cesami. Pewien proces może np. wywołać kilka nowych procesów, któ-
re będą się wykonywały w sensie logicznym równolegle (choć fizycznie
– będą się dzieliły czasem procesora), a sam zawiesi swoje działanie i wzno-
wi je dopiero w chwili, gdy zakończą się wszystkie utworzone w ten sposób
procesy5. Nadając różne maski przerwań dwóm procesom posuwającym się
po kodzie tego samego programu – można spowodować, że będą w różny
sposób wrażliwe na przerwania, choć realizują ten sam program i tak dalej.
Takich przykładów można podać wiele.
Oczywiście, zarządzanie wieloma procesami ma konsekwencje również
dla innych części systemu operacyjnego. Konieczne są na przykład programo-
we mechanizmy gospodarowania zawartością pamięci operacyjnej. Procesy są
tworzone i giną, trzeba im przydzielać fragmenty pamięci operacyjnej i zwal-
niać je po likwidacji procesu. Co więcej, należy zapewnić ochronę pamięci.
Pisaliśmy już o tym: żaden z procesów nie może przekroczyć granic przy-
dzielonego mu obszaru pamięci, odwołując się do adresów fizycznych, które
już nie należą do niego. Zaraz powstaje inne pytanie: czy każdemu procesowi
należy przydzielać jeden, jednorodny obszar pamięci, czy też – dla wygody go-
spodarowania pamięcią – kilka mniejszych fragmentów (zwanych stronami),
które mogą być rozmieszczone w różnych miejscach i są akurat w tej chwili
wolne? Jak wtedy zorganizować proces dynamicznego wyznaczania adresów
fizycznych?
Takie pytania można mnożyć, a rozwiązań może być oczywiście wie-
le. To tłumaczy, dlaczego istnieją różne typy systemów operacyjnych i dla-
czego stale powstają nowe, coraz doskonalsze ich wersje: dla komputerów

5 Gdyby system zawierał kilka procesorów (tj. byłby wieloprocesorowy) – to było-


by możliwe, żeby te procesy wykonywały się rzeczywiście współbieżnie.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

454 18. System przerwań

osobistych, dla wielkich serwerów sieciowych, dla systemów pracujących


w czasie rzeczywistym, dla małych urządzeń mobilnych, dla systemów
wbudowanych i tak dalej.
Uważa się, że systemy operacyjne należą dziś do najbardziej logicznie
skomplikowanych tworów ludzkiego umysłu. Ich złożoność ma niestety też
drugą, ciemniejszą stronę, z którą zetknął się z pewnością każdy użytkow-
nik komputera, a mianowicie fakt, że kryją się w nim nieuchronne błędy.
Są wśród nich niesłychanie złośliwe i trudne do wykrycia (gdyż ujawniają
się tylko w wyniku rzadkich, losowo występujących koincydencji zdarzeń)
logiczne błędy w koordynacji procesów i zarządzaniu systemowymi zasoba-
mi: pamięcią, urządzeniami zewnętrznymi, systemowym stosem itd. To one
– nie zaś fizyczne awarie elektronicznego sprzętu – najczęściej powodują,
że system niespodziewanie odmawia posłuszeństwa, zawiesza się i przestaje
reagować na nasze polecenia.
W warunkach domowych zazwyczaj wystarcza wtedy wyłączenie
i ponowne włączenie systemu, a będzie on poprawnie pracował aż do chwi-
li, gdy owa niekorzystna sytuacja przypadkiem pojawi się znów: może jutro,
może za kilka miesięcy a może nigdy. Są jednak takie systemy, które muszą
być odporne na takie zdarzenia, ponieważ ich ewentualna awaria może za-
grażać ludzkiemu życiu lub powodować ogromne straty materialne. Systemu
samolotowej awioniki, systemu sterowania elektrownią atomową lub krajo-
wą siecią energetyczną nie można ot tak, po prostu wyłączyć i z powrotem
włączyć w nadziei, że za chwilę znów podejmie poprawne działanie. Jak
sobie poradzić z tym problemem?
Dodatkowy wymiar trudnościom nadaje fakt, że praktycznie wszyst-
kie systemy komputerowe pracują dziś (lub przynajmniej powinny umieć
pracować) w sieci, czy to w ramach lokalnej sieci danego przedsiębiorstwa,
czy w sieci o globalnym zasięgu. Biorąca w tym udział infrastruktura tele-
komunikacyjna jest sama w sobie też rozległą siecią komputerową, wyspe-
cjalizowaną w przenoszeniu pakietów cyfrowych danych. Oprogramowanie
systemowe naszego komputera nie tylko więc zarządza swoimi lokalnymi
zasobami i procesami, lecz także pośredniczy w komunikacji między aplika-
cjami działającymi w komputerach odległych o tysiące kilometrów.
Niezależnie od samej logicznej złożoności tych mechanizmów, wraz
z rozpowszechnieniem się sieci komputerowych pojawił się jeszcze inny
problem: bezpieczeństwa w sieci. Oprogramowanie systemowe powinno
chronić dane użytkownika i przekazywane przez niego komunikaty przed
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Programy i procesy 455

nieautoryzowanym dostępem ze strony kogoś, kto zechciałby je wykorzystać


dla zysku, z powodów politycznych czy choćby głupiego żartu. To obecnie
problem również bardzo palący i aktualny.
Nie będziemy dalej rozwijać tego tematu. Zasługiwałby on z pew-
nością na całą oddzielną książkę, a nawet wiele specjalistycznych książek.
Zakończmy więc ten rozdział nadzieją, że czytelnik przynajmniej poczuł
smak problemów, jakie wiążą się z projektowaniem systemowego softwa-
re’u, bez którego nawet najdoskonalszy zestaw scalonych procesorów, pa-
mięci, jednostek sterujących itd. – pozostaje jedynie bezużyteczną kupką
drogiego złomu.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej?

Przy omawianiu podstawowych zasad organizacji komputerowego sprzętu


posłużyliśmy się uproszczonym modelem, wzorowanym w znacznym stop-
niu na organizacji komputera PDP-11. Niedługo po wyprodukowaniu tego
minikomputera na rynek weszły układy LSI – o wielkiej skali integracji
– i potem nic już nie było takie przejrzyste i łatwe do wytłumaczenia jak
PDP-11.
I nie chodziło tylko o to, że cały procesor PDP-11 zamiast w sporym
stojaku dał się wkrótce zmieścić w jednej scalonej kostce: mikroprocesorze.
Choć rzeczywiście tak było (w tym konkretnym przypadku był to mikro-
procesor LSI-11), to postęp polegał nie tylko na zmniejszeniu fizycznych
rozmiarów sprzętu. Ważniejsze było to, że równolegle opracowywano coraz
lepsze metody i coraz sprawniejsze oprogramowanie wspomagające projek-
towanie bardzo złożonych układów logicznych. Zaprojektowane zależności
logiczne były od razu przekształcane na maski, służące bezpośrednio w pro-
cesie fabrykacji układu scalonego do nakładania (metodą fotolitografii) kolej-
nych warstw układu scalonego. Przedtem, po zaprojektowaniu sieci logicznej
czy układu sekwencyjnego, trzeba było jeszcze zaplanować rozmieszczenie
bramek logicznych, rejestrów i przerzutników na odpowiednich płytkach, po-
prowadzić łączące je metalizowane ścieżki, wykonać odpowiednie otworki
czy gniazda, poumieszczać tam scalone kostki (małej czy średniej skali inte-
gracji), na koniec wszystko to polutować i sprawdzić. Teraz – coraz więcej
tych połączeń wykonywano wewnątrz układu scalonego, co oznacza, że były
one planowane przez oprogramowanie, które przekształcało logiczny sche-
mat sieci wprost na rozmieszczenie różnych obszarów mikroelektronicznych
w poszczególnych warstwach układu scalonego.
Dało to projektantom komputerowego sprzętu niespotykaną wcześniej
swobodę i rozmach w urzeczywistnianiu pomysłów, które często były już
wcześniej znane, ale nie mogły być szerzej zastosowane w praktyce właśnie
ze względu na złożoność, pracochłonność i koszt ich technicznej realizacji.
Jeden z takich pomysłów dotyczył przetwarzania potokowego (ang.
pipeline processing). Sama idea była znana, a nawet do końca zrealizowana
w jednej z maszyn (IBM 7030), zbudowanej (w kilku zaledwie egzempla-
rzach) jeszcze w końcu lat pięćdziesiątych XX wieku (!), a więc jeszcze
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej? 457

w technice tranzystorowej. Wtedy okazała się zbyt skomplikowana i droga,


ale po dwudziestu latach powrócono do niej ze znakomitym skutkiem.
Na czym polega pomysł przetwarzania potokowego? W naszym przy-
kładowym procesorze układ sterowania zajmował się tylko jedną instruk-
cją na raz. Sprowadzał ją sobie na początku cyklu rozkazowego do rejestru
instrukcji i wykonywał do końca, zanim sięgnął po następny rozkaz ma-
szynowy. W przetwarzaniu potokowym jest inaczej. W układzie sterowa-
nia procesora jest miejsce od razu na kilka czy kilkanaście instrukcji ma-
szynowych. Wykonują się one wciąż kolejno, tak jak przewiduje program,
ale kiedy pierwsza z nich się rzeczywiście wykonuje, dla następnej już się
sprowadza potrzebną jej daną z pamięci, dla trzeciej – oblicza się adres bez-
względny, który będzie jej za chwilę potrzebny i tak dalej. Innymi słowy,
wiele instrukcji przechodzi kolejne etapy cyklu rozkazowego tak szybko, jak
to jest tylko możliwe, aby – kiedy już dojdzie do ostatecznego wykonania
instrukcji – nie było trzeba już na nic czekać1. Co więcej, często (częściej,
niż można by się spodziewać) zdarza się, że kolejne instrukcje mogą być
wykonywane dokładnie jednocześnie, gdyż operują na różnych argumen-
tach i instrukcja – powiedzmy – druga w kolejności wcale nie musi czekać
na wynik pierwszej, aby mogła się skutecznie wykonać.
Powie ktoś, że ta korzyść jest pozorna, gdyż jednostka arytmetyczno-
-logiczna i tak jest jedna i dlatego nie można przy jej użyciu jednocześnie
obliczać adresów i wykonywać na raz kilku operacji logicznych lub aryt-
metycznych. Ale kto powiedział, że musi być jedna? Przecież już nas nie
ogranicza technologia wykonania starej, poczciwej PDP-11. Jest potrzebny
oddzielny stałoprzecinkowy arytmometr do wyliczania adresów? Proszę bar-
dzo. Dobrze by było mieć kilka zmiennoprzecinkowych arytmometrów do
równoległego wykonywania niezależnych operacji? Proszę bardzo. A złożo-
na sieć logiczna, kontrolująca stan przygotowania każdej z kilkunastu kolej-
nych instrukcji i badająca możliwość ich niezależnego wykonania? Też da
się zrobić i zmieścić w tym samym scalonym układzie VLSI.
Oczywiście, to popularyzatorska przesada. Nic się nie działo ot tak,
łatwo, na zawołanie. Oprócz konieczności zaprojektowania bardzo złożo-

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

458 19. Co było dalej?

nych podzespołów, przetwarzanie potokowe pociąga za sobą także koniecz-


ność rozwiązania zupełnie nowych problemów. Co zrobić z kilkunastoma
już „napoczętymi” instrukcjami, jeśli okaże się, że poprzedzający je rozkaz
jest instrukcją skoku, program pójdzie gdzie indziej, a te instrukcje w ogóle
nie będą użyte? A co z nimi zrobić w momencie przerwania? A program
tłumaczący z języka programowania na język maszynowy? Powinien on
konstruować ciąg instrukcji maszynowych tak, żeby jak najwięcej z sąsied-
nich rozkazów dawało się wykonać równolegle z innymi. W rzeczywistości
znalezienie rozwiązań trwało wiele lat, kosztowało sporo intelektualnego
i technologicznego wysiłku, ale w końcu przetwarzanie potokowe stało się
powszechną zasadą działania wszystkich współczesnych mikroprocesorów.
Jego wprowadzenie pozwoliło na co najmniej kilkakrotne (powiedzmy
w uproszczeniu, że dziesięciokrotne) zwiększenie liczby instrukcji wykony-
wanych przez procesor w jednostce czasu.
Jednocześnie, mikroelektroniczne struktury stawały się coraz mniej-
sze, a więc również charakteryzowały się coraz krótszym czasem przełącza-
nia (zmiany stanu) dwustanowych elementów. To z kolei umożliwiało stop-
niowe powiększanie częstotliwości zegara: od pojedynczych megaherców
(MHz) w latach siedemdziesiątych XX wieku do ponad 100 MHz w począt-
ku lat dziewięćdziesiątych. Nałożenie się tych dwóch zjawisk – opanowanie
przetwarzania potokowego oraz znaczne zwiększenie częstotliwości zegara
– spowodowało, że mikroprocesory z lat dziewięćdziesiątych były już sto
czy dwieście razy szybsze (w sensie maksymalnej liczby instrukcji maszy-
nowych wykonywanych w jednostce czasu) niż ich przodkowie z początku
lat siedemdziesiątych.
Ale ludzie jakoś nigdy nie chcą zadowolić się tym, co mają. Co można
zrobić, żeby komputery były jeszcze szybsze? Od dawna wąskim gardłem był
dostęp do pamięci operacyjnej, a wyśrubowanie szybkości procesora sprawiło,
że efekt ten stał się jeszcze wyraźniejszy. Stosunkowo wcześnie sięgnięto więc
do pomysłu rozmieszczania programów i danych w kilku oddzielnych blokach
pamięci, z zastosowaniem przeplatania adresów (ang. address lub memory in-
terleaving), co pozwoliło na odwoływanie się do pamięci częściej, niż trwa
cykl odczytu/zapisu w pojedynczym bloku RAM.
Ale to również nie wystarczało. Bardziej radykalnym pomysłem było
wprowadzenie dodatkowego poziomu pamięci pośredniej. Bywa też ona na-
zywana pamięcią podręczną lub buforową, a najczęściej – z angielska – pa-
mięcią cache (ang. cache memory).
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej? 459

Pomysł (również nienowy) opierał się na dwóch spostrzeżeniach.


Po pierwsze, im pamięć większa – tym wolniejsza, gdyż zlokalizowanie
komórki o podanym adresie i dotarcie do znajdującej się tam informacji
trwa dłużej, jeśli tych komórek jest wiele. I odwrotnie: jeśli pamięć jest
mała, może za to być szybsza. Po drugie, nieczęsto się zdarza, żeby kolejne
odwołania do pamięci „skakały” po całej dostępnej procesowi przestrzeni
adresowej. Zwykle dotyczą one komórek sąsiednich albo leżących stosun-
kowo niedaleko od siebie, w ramach pewnego lokalnego, ograniczonego
obszaru pamięci. Informatycy nazywają to zasadą lokalności odwołań
(ang. locality of reference). W miarę wykonywania programu (procesu) ten
obszar przesuwa się, ale sama zasada lokalności odwołań wciąż pozostaje
w mocy.
Jeśli tak jest, to zbudujmy wewnątrz procesora pośrednią pamięć bu-
forową, liczącą na przykład kilka czy kilkadziesiąt tysięcy komórek, a więc
znacznie (niż cała pamięć operacyjna RAM) mniejszą, ale za to kilkakrotnie
szybszą. Będzie się do niej sprowadzało od razu cały spory lokalny frag-
ment programu i danych procesu, który się w danej chwili w procesorze
wykonuje. Układ sterowania będzie więc pobierał kolejne instrukcje i ich
dane nie wprost z RAM, ale z owej pamięci pośredniej. Kiedy wykorzysta
już całą zawartość tej pamięci albo kiedy będzie odwołanie do komórki spo-
za skopiowanego do niej obszaru – sprowadzi się do niej następny fragment
z RAM i tak dalej.
Korzyść jest taka, że pewien lokalnie potrzebny fragment programu
sprowadza się do pamięci cache jeden raz, a potem można się do niego wie-
lokrotnie odwoływać, ze znacznie krótszym czasem dostępu. Już z pierw-
szych rozdziałów tej książki wiemy, że w każdym programie z zasady wy-
stępują pętle i rekurencyjne wywołania funkcji, a więc takie wielokrotne
odwołania do instrukcji znajdujących się w szybkiej pamięci pośredniej po-
wodują w praktyce wielką oszczędność czasu. Przy okazji, znacznie odciąża
się systemową magistralę. Procesor nie angażuje jej już tak często, gdyż
ma wewnątrz duży zapas instrukcji i nie musi po wykonaniu każdej z nich
sięgać do pamięci operacyjnej.
Oczywiście, nic nie dzieje się za darmo. Rozkazy maszynowe wska-
zują swoją częścią adresową na komórki pamięci RAM. Jeżeli dany lokal-
ny fragment programu zostanie przeniesiony do pamięci cache (na miejsce,
które aktualnie jest w niej wolne) – to należy przekierować te odwołania tak,
by teraz dotyczyły odpowiednich adresów w pamięci cache. Procesor trzeba
więc wyposażyć w dość złożony sprzętowy układ dynamicznego odwzoro-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

460 19. Co było dalej?

wywania adresów. Trzeba także rozwiązać problem gospodarowania wol-


nym miejscem w pamięci cache (przydzielanie, zwalnianie itp.). Niemniej,
koncepcja pamięci pośredniej (cache) okazała się na tyle skuteczna i ko-
rzystna, że w późniejszych mikroprocesorach nawet ją rozszerzono, wpro-
wadzając dwa poziomy takiej buforowej pamięci: Cache 1 oraz Cache 2,
jedną dla instrukcji, drugą dla danych2.
Zmiany nie ograniczyły się jednak do zwiększania wydajności same-
go procesora i przyspieszania jego współpracy z pamięcią RAM. Bardzo
wyraźna była też tendencja do budowania specjalizowanych sprzętowych
jednostek sterujących dla całych grup urządzeń zewnętrznych oraz standary-
zacji sposobów ich komunikowania się z resztą systemu. Wpłynęło to oczy-
wiście na modyfikację generalnej architektury systemów.
Producenci mikroprocesorów oferują całe zestawy (ang. chipset) pasu-
jących do siebie układów o wielkiej skali integracji, które – dobudowane do
mikroprocesora na płycie głównej komputera – pośredniczą w komunikacji
pomiędzy centralnym procesorem (CPU) i magistralą systemową a innymi
jednostkami systemu, w tym urządzeniami zewnętrznymi i pamięcią RAM.
Choć takie chipsety są różne dla różnych mikroprocesorów (lub rodzin mi-
kroprocesorów) i ich producentów, jednak wytworzył się pewien wspólny
model architektury typowego systemu mikroprocesorowego, w uproszcze-
niu taki jak na rysunku 20.1.
Porównajmy schemat przedstawiony na rysunku 19.1 z jego przod-
kiem z epoki minikomputerów (rysunek 16.1). Czym on się od poprzednika
różni, oprócz tego, że jest narysowany pionowo, a nie poziomo? Co w nim
pozostało z zasad, które omawialiśmy w rozdziale 16?
Odpowiedź brzmi: pozostało właściwie wszystko, tyle tylko że to
wszystko zostało rozbudowane, przyspieszone, zwielokrotnione, zdecentra-
lizowane i uzupełnione o nowe rozwiązania oraz specjalistyczne jednostki
sterujące.

2 Termin cache jest też używany na określenie podręcznej, buforowej pamięci


niekoniecznie sprzętowo zrealizowanej wewnątrz procesora. Na przykład cache
wyszukiwarki przechowuje ostatnio sprowadzane z sieci strony www, po to, by
można było z nich szybciej skorzystać, bez potrzeby sprowadzania ich jeszcze
raz.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej? 461

Rys. 19.1. Blokowy schemat typowego systemu z jednym mikroprocesorem

Podstawowy układ scalony – mikroprocesor – oczywiście pozostał. Tak jak


poprzednio zawiera on CPU – z jej rejestrami, arytmometrem (lub kilko-
ma arytmometrami), układem sterowania itd. – oraz układ arbitra magistra-
li i układ przerwań. Oczywiście, choć na schemacie tego nie widać, teraz
układ sterowania CPU jest przystosowany do przetwarzania potokowego,
rejestrów jest więcej, jednostek arytmetycznych kilka, doszła pamięć cache,
a wszystko działa ze znacznie szybszym zegarem.
Podobnie jak poprzednio, z układu mikroprocesora wyprowadzona
jest na zewnątrz magistrala (szyna) systemowa. Ponieważ (jak się za chwilę
okaże) nie jest to już jedyna magistrala w systemie, dla odróżnienia od in-
nych nosi ona często bardziej złożoną nazwę, na przykład w architekturze
mikroprocesorów Intel nazywa się FSB (ang. Front Side Bus). Jej dalszy
przebieg jest też nieco inny niż dawniej, choć sama zasada jej działania
pozostaje w mocy.
W przykładowym komputerze z rozdziału 16 wszystkie urządzenia
zewnętrzne, centralny procesor i pamięć były dołączone do tej samej, jed-
nej, uniwersalnej systemowej magistrali. Teraz, do typowego chipsetu mi-
kroprocesora należą dwa układy scalone, które tworzą pomost pomiędzy
systemową magistralą a innymi, dołączonymi do niej jednostkami. Takie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

462 19. Co było dalej?

pomosty czy mosty (ang. bridges) są dwa: północny (Northbridge) i po-


łudniowy (Southbridge). Generalnie, most północny obsługuje urządzenia
szybkie (oraz RAM, choć u niektórych producentów pamięć RAM jest łą-
czona bezpośrednio z procesorem), zaś południowy – wolniejsze urządzenia
zewnętrzne.
Każdy z mostów jest bardzo złożonym układem, przygotowanym na
to, by można było doń dołączać jednostki sterujące (w żargonie informa-
tyków: kontrolery, ang. controllers) wielu typów urządzeń zewnętrznych,
w tym również całych grup urządzeń, zarządzanych przez jeden, wspól-
ny kontroler. Tak więc na przykład całą grupę kilku dysków można dołą-
czyć do specjalizowanej magistrali dyskowej (np. nazywającej się SCSI
lub SAS), grupę urządzeń wolnych – do innej magistrali (np. USB, ang.
Universal Serial Bus) i tak dalej. Kontroler każdej z tych lokalnych ma-
gistral zarządza daną grupą urządzeń i jednocześnie reprezentuje je przed
odpowiednim – północnym lub południowym – mostem, a poprzez niego
komunikuje się z główną magistralą systemu.
W interesie zarówno producentów układów scalonych, jak producen-
tów urządzeń zewnętrznych i wreszcie użytkowników komputerów – leżała
normalizacja sposobu komunikowania się urządzeń zewnętrznych z resztą
systemu. Dzięki niej można tworzyć konfiguracje, składające się z urządzeń
i podzespołów pochodzących od różnych producentów, lecz zgodnie współ-
pracujących ze sobą. W toku ostatnich kilku dziesięcioleci rozwoju techniki
komputerowej powstało wiele rozwiązań i norm z tego zakresu.
Przykładem (ale tylko przykładem, jednym z wielu możliwych) mogą
być wspomniane z nazwy na rysunku 19.1 normy PCI (ang. Peripheral
Component Interconnect) oraz PCIe (ang. Peripheral Component Interconnect
Express). W myśl pierwszej z nich, urządzenia zewnętrzne włączone są do
lokalnej, równoległej magistrali zarządzanej przez lokalnego arbitra i dzia-
łającej mniej więcej tak jak jedyna systemowa magistrala z rozdziału 16.
W przypadku PCIe, które jest rozwiązaniem nowszym – każde z urządzeń
jest połączone z kontrolerem magistrali PCIe oddzielnym, bardzo szybkim
dwukierunkowym połączeniem szeregowym. Darujmy sobie jednak szersze
omawianie tych i dziesiątek innych, podobnych norm. Niniejsza książka nie
jest na to dobrym miejscem.
Jednostki sterujące specjalizowanych magistral są w stanie samodziel-
nie (tzn. bez udziału procesora) wykonać wiele złożonych funkcji, takich
jak odwzorowywanie adresów logicznych bloków danych na ich fizyczne
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej? 463

adresy (np. na numery ścieżek i sektorów dyskowych), buforowanie całych


bloków danych, kontrola poprawności odczytu (z użyciem bitu parzystości
i kodów kontrolnych) itp. W ten sposób odciąża się procesor od wykony-
wania wielu czynności, które dawniej, w czasach minikomputerów, były
przezeń wykonywane jako często powtarzające się, rutynowe systemowe
podprogramy.
Wśród szybkich urządzeń na szczególną uwagę zasługują podzespo-
ły usprawniające wyświetlanie na ekranie obrazów tworzonych z udziałem
grafiki komputerowej. Tu dokonał się szczególnie duży postęp.
Dzisiaj każdy laptop, notebook, tablet, a nawet najmniejsze kieszonko-
we cyfrowe urządzenie jest wyposażone w kolorowy ekran lub wyświetlacz,
a graficzny tryb komunikowania się z nim wydaje się rozwiązaniem całko-
wicie naturalnym. Nie zawsze jednak tak było. Choć kolorowa telewizja
była znana od dawna, a mysz komputerową opatentowano jeszcze w 1970
roku, to czysto tekstowa forma komunikowania się człowieka z kompute-
rem dominowała w praktyce aż do lat osiemdziesiątych XX wieku. Istniały
oczywiście rozwiązania eksperymentalne i specjalne systemy wizualizacji
danych (na przykład w technice wojskowej i kosmicznej), ale prawdziwa
eksplozja zainteresowania grafiką komputerową nastąpiła wtedy, gdy na
masową skalę pojawiły się komputery osobiste i konsole do gier kompu-
terowych, a prywatnemu, niezbyt fachowemu użytkownikowi trzeba było
podsunąć prosty, obrazkowy sposób komunikowania się z komputerem.
Przy wyświetlaniu obrazów wykorzystuje się technikę znaną od lat
z telewizji. W telewizji analogowej sprawa była jeszcze – przynajmniej co
do zasady – stosunkowo prosta. Obraz na ekranie wyświetla się poziomy-
mi liniami. Jasność świecenia ekranu zmienia się wzdłuż każdej linii tak,
jak nakazuje analogowy przebieg takiej samej linii zdjęty przez telewizyjną
kamerę. W przypadku telewizji kolorowej – są to trzy równoległe przebie-
gi, odpowiadające trzem podstawowym barwom. Wszystkie linie na ekranie
trzeba od nowa wyświetlać (odświeżać) kilkadziesiąt razy na sekundę po to,
by zachować wrażenie ciągłości ruchu obiektów tworzących obraz.
W przypadku telewizji cyfrowej te zasady pozostają w mocy, z tym
że każda linia podzielona jest na dyskretne punkty, zwane – jak już chyba
wszyscy wiedzą – pikselami. Gdy filmowana jest rzeczywista scena, cy-
frowa kamera mierzy poziom jasności każdej z trzech podstawowych barw
każdego piksela i przedstawia go w postaci binarnej. Tak, linia po linii, ob-
raz telewizyjny jest przekazywany na odległość w postaci strumienia binar-
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

464 19. Co było dalej?

nych danych, zaś układ wyświetlania odbiornika przerabia z powrotem owe


binarne dane na jasność kolorowych punktów swojego ekranu.
Przedmiotem grafiki komputerowej są jednak nie rzeczywiste obra-
zy, zdejmowane wprost przez cyfrową kamerę, lecz obrazy wytworzone
sztucznie. Tu sytuacja jest bardziej skomplikowana. Każdy sztuczny trój-
wymiarowy obiekt jest w istocie matematycznym modelem, który skła-
da się z bardzo dużej liczby linii i wielokątów, opisanych odpowiednimi
równaniami, znanymi z geometrii analitycznej. Każdy wielokąt ma swoją
barwę, jasność, a także teksturę: jedne są gładkie i błyszczące, inne ma-
towe i chropowate, jedne półprzezroczyste i prześwitujące, inne nie. Ich
obracanie, przesuwanie, zmniejszanie, powiększanie polega na odpowied-
nim przeliczaniu ich trójwymiarowych współrzędnych. Cała trójwymiaro-
wa scena zawiera wiele takich obiektów: postaci, budynków, pojazdów, tła
i tak dalej, a każdy jest złożony z setek lub tysięcy wielokątów. Co więcej,
przynajmniej niektóre z nich poruszają się w sposób zadany dodatkowo
odpowiednimi równaniami ruchu.
Ten matematyczny model sceny trzeba przerobić na taką postać, by
układ wyświetlania mógł go odwzorować na ekranie, tak jak on to potrafi:
szeregowo, w postaci kolejnych linii, złożonych z pikseli. Znaczy to, że na
podstawie matematycznego modelu sceny trzeba w końcu wyliczyć dla każ-
dego piksela ekranu aktualną barwę i jasność każdej z trzech podstawowych
barw.
Jednocześnie trzeba pamiętać, że obraz na ekranie jest dwuwymia-
rowy. Jedne obiekty trójwymiarowej sceny przesłaniają inne, więc niektóre
nie są w ogóle widoczne, a inne tylko częściowo, choć za chwilę mogą
się pojawić spoza przeszkody. Trzeba to oczywiście uwzględnić w obli-
czeniach. Chcąc zachować realizm sceny, trzeba także uwzględnić cienie
i odbicia światła od widocznych powierzchni o różnym pochyleniu, różnym
współczynniku pochłaniania światła, różnej przezroczystości... Co więcej,
jeśli obraz ma się zmieniać dynamicznie, tworząc wrażenie rzeczywistego
ruchu – to takie wyliczenie trzeba powtarzać przed każdym odświeżeniem
ekranu, a więc kilkadziesiąt (w komputerowej praktyce grubo powyżej 50)
razy na sekundę.
Ten proces przetwarzania matematycznego modelu trójwymiarowej
sceny na postać nadającą się do wyświetlenia na dwuwymiarowym ekranie
nazywa się renderowaniem (ang. rendering). Oznacza on oczywiście ko-
nieczność szybkiego wykonywania ogromnej ilości numerycznych obliczeń.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej? 465

Kiedyś, na początku ery komputerów osobistych, zajmował się nimi central-


ny (i zresztą jedyny) procesor systemu. Starsi użytkownicy komputerów pa-
miętają, co to wtedy oznaczało: nieskomplikowane kanciaste ludziki z tru-
dem ganiały się po monochromatycznym ekranie o małej rozdzielczości.
Specjaliści od grafiki komputerowej opracowali jednak po niedługim
czasie (i zresztą nadal opracowują) sposoby tworzenia i modyfikowania ma-
tematycznych modeli bardzo realistycznych scen i różnorodnych wizualnych
efektów. Powstało także specjalistyczne oprogramowanie do renderingu.
Wkrótce zapoznała się z nim szersza publiczność. Zostało ono zastosowane
w głośnych i nagradzanych filmach, takich jak np. Jurassic Park (1993 r.).
Jednak przy wymaganej, właściwej dla filmu rozdzielczości (a więc licz-
bie pikseli „do obsłużenia” wielokrotnie większej niż w przypadku prostego
ekranu komputerowego) i przy założeniu, że wszystkie efekty mają wyglą-
dać naprawdę realistycznie – nie było wówczas mowy o renderingu w cza-
sie rzeczywistym. Każda klatka filmu była renderowana oddzielnie, przez
wiele godzin pracy bardzo dobrych ówczesnych komputerów, ale końcowy
efekt był rzeczywiście imponujący.
W przypadku gier komputerowych trudno natomiast zgodzić się na
to, by obraz na ekranie wyświetlał się inaczej niż w czasie rzeczywistym,
tak jak – zgodnie z regułami scenariusza – poruszają się bohaterowie gry
i otaczające ich obiekty. Zaczęto więc budować akceleratory graficzne, czyli
specjalizowane sprzętowe jednostki, które przyspieszały proces przetwarza-
nia matematycznego modelu sceny na jego ekranową postać. Z czasem po-
wstały całe rozbudowane podsystemy, które znamy pod nazwą kart graficz-
nych (ang. graphics card, video card). Obecnie są to właściwie całe specjali-
zowane komputery o ogromnej mocy obliczeniowej, z własnym procesorem
graficznym (GPU, ang. Graphics Processing Unit), pamięcią, układem wy-
świetlania (wraz z jego przetwornikami cyfrowo-analogowymi), chłodze-
niem itd.
Karty graficzne są dołączane do systemu poprzez północny most
(Northbridge) i w decydujący sposób odciążają procesor centralny (CPU)
od numerycznych obliczeń związanych z renderowaniem obrazu. Centralny
procesor zajmuje się więc scenariuszem gry i komunikacją z człowiekiem,
tworząc i odpowiednio modyfikując matematyczny model graficznej sceny.
Przekazuje go potem karcie graficznej, która doprowadza go od postaci stru-
mienia wielokątów aż do poziomu opisu poszczególnych pikseli – i powo-
duje wyświetlenie ich na ekranie. Taki podział funkcji dotyczy zresztą nie
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

466 19. Co było dalej?

tylko gier: w analogiczny sposób wyświetlane są dokumenty zawierające


obrazy i tekst, internetowe strony, i tak dalej.
Zauważono także, że operacje związane z renderowaniem poddają się
stosunkowo łatwo przyspieszaniu i zrównoleglaniu. Jeżeli w karcie graficz-
nej ma się do dyspozycji kilka procesorów, to strumień wielokątów można
przetwarzać bardzo szybko, gdyż obliczenia dla poszczególnych wielokątów
są praktycznie wzajemnie niezależne. Nawet na etapie, gdy decyduje się
o przesłanianiu jednych wielokątów przez inne (a więc wówczas, gdy jednak
są od siebie zależne), można podzielić ekran na przykład na cztery ćwiartki
i nadal obrabiać je jednocześnie, równolegle, a pewne problemy wynikają
jedynie na granicy owych ćwiartek. Takie wieloprocesorowe karty graficzne
rzeczywiście powstały. Każda z nich jest w istocie specjalizowanym, wielo-
procesorowym systemem komputerowym o ogromnej mocy obliczeniowej.
Co ciekawe, wieloprocesorowe karty graficzne wykorzystuje się
obecnie nie tylko w grafice, lecz także w innych masywnych, równoleg-
łych obliczeniach numerycznych: w optymalizacji, obliczeniach z użyciem
algorytmów ewolucyjnych, w modelowaniu zjawisk fizycznych itp., i to nie
obok, lecz wręcz zamiast procesora centralnego.
Na tej samej zasadzie co karty graficzne – pojawiły się też w kom-
puterach karty dźwiękowe. One również uwalniają centralny procesor od ru-
tynowych obliczeń związanych z obsługą odczytywania i zapisywania róż-
nych formatów cyfrowego dźwięku, korektą widma sygnału, wytwarzaniem
efektów dźwiękowych i tak dalej. Tu nakład obliczeń jest mniejszy i karty
graficzne – jako urządzenia wolniejsze – są dołączane poprzez most połu-
dniowy.
Podobnie jak poprzednio, wszystkim opisanym zjawiskom towarzy-
szył wyraźny postęp w dziedzinie technologii projektowania i wytwarza-
nia układów scalonych. Częstotliwość zegara również nieustannie rosła i na
początku XXI wieku osiągnęła poziom pojedynczych GHz (gigaherców).
Oznacza to prawie tysiąckrotne przyspieszenie (samego zegara) w stosunku
do rozwiązań typowych dla lat siedemdziesiątych XX wieku. Wydaje się
jednak, że właśnie teraz, na naszych oczach, dalszy postęp w tej dziedzinie
natknął się na bardzo trudną do pokonania przeszkodę. Tak jak w latach
pięćdziesiątych XX wieku przeszkodą rozwoju komputerów była bariera
niezawodnościowa – tak teraz drogę przegrodziła bariera cieplna.
Wszyscy wiemy, że każde urządzenie elektryczne czy elektroniczne
rozprasza część energii w postaci ciepła. Mówiąc po prostu – nagrzewa się
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej? 467

podczas pracy. Jeśli ma odpowiednio dużą powierzchnię, to wypromienio-


wuje nadmiar ciepła do otoczenia. Jeśli powierzchnia jest zbyt mała – można
zamontować radiator, który tę powierzchnię zwiększy i zastosować wiatra-
czek, przyspieszający wymianę powietrza. Jednak we współczesnym ukła-
dzie scalonym tranzystory i inne mikroelektroniczne struktury są bardzo gę-
sto upakowane w bardzo małej objętości i zwykły wentylator nie wystarcza.
Chłodzenie nagromadzonych w niewielkiej przestrzeni układów sca-
lonych już w przeszłości sprawiało kłopoty. Już dwadzieścia lat temu bu-
dowano superkomputery o wielu procesorach, które musiały być chłodzone
– jak potężna lodówka – ciekłym freonem. Teraz jednak problem zszedł na
poziom pojedynczych układów. Polega on na tym, że zwiększanie częstot-
liwości zegara powyżej pewnej granicy intensyfikuje ruch ładunków elek-
trycznych w niewyobrażalnie blisko siebie ułożonych strukturach, tak że
odprowadzenie na zewnątrz wydzielającego się tam ciepła staje się w ogóle
praktycznie niemożliwe. Układ, który by pracował w takich warunkach, po
prostu wyparuje w ciągu ułamka sekundy. Jak się wydaje, tę termiczną gra-
nicę właśnie osiągnięto.
Czy oznacza to koniec postępu w technice komputerowej? Oczywiście,
nie. Możliwe, że uda się jeszcze przesunąć granicę cieplną nieco dalej, przy
wykorzystaniu technologicznych wynalazków, które dziś są trudne do prze-
widzenia. Najbardziej prawdopodobne jest jednak, że częstotliwość zegara
pozostanie na poziomie zbliżonym do dzisiejszych pojedynczych gigaher-
ców, zaś dalsze powiększanie mocy obliczeniowej komputerów będzie się
odbywało poprzez wykorzystanie obliczeń równoległych, z użyciem rosną-
cej liczby procesorów w systemie.
Tendencję tę widać już dzisiaj. Są już powszechnie dostępne na
rynku mikroprocesory wielordzeniowe (ang. multi-core microprocessors),
które są niczym innym, jak właśnie scalonymi systemami wieloproceso-
rowymi. Rdzeniem3, jak gdyby podstawową jednostką przetwarzającą, jest
w tym kontekście nazywane to, co do tej pory nazywaliśmy mikroproceso-
rem. Wielordzeniowy mikroprocesor zawiera wewnątrz kilka takich rdzeni
(a więc naszych dotychczasowych mikroprocesorów) plus system połączeń
między nimi.

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

468 19. Co było dalej?

Każdy z rdzeni może w zasadzie pracować zupełnie niezależnie od


pozostałych. Może być też jednak tak, że wszystkie razem wykonują jeden,
wspólny program, dzieląc się między sobą tymi jego fragmentami, które
dają się wykonać równolegle. W miarę potrzeby komunikują sobie nawza-
jem wyniki cząstkowych obliczeń i znów podejmują – jeśli to możliwe
– równoległą pracę. W takim przypadku jeden z rdzeni odgrywa zazwyczaj
rolę koordynatora, który zarządza pozostałymi. Rozdziela między nie zada-
nia do wykonania, decyduje o komunikacji między nimi, o przekazywaniu
rezultatów poprzez wspólną pamięć lub za pomocą komunikatów biegną-
cych łączącymi je magistralami itd.
Korzystanie z systemów wieloprocesorowych wymusza dość radykal-
ną zmianę podejścia do stylu programowania takich urządzeń. Przekształcając
algorytm na program, trzeba od razu planować go tak, by proces jego wyko-
nania dzielił się na możliwie równoległe wątki, które zbiegają się w dobrze
dobranych momentach, a struktury danych – by także umożliwiały jedno-
czesny dostęp do danych. Wiele dzisiejszych programów, projektowanych
w tradycyjny, czysto sekwencyjny sposób, tej właściwości nie ma, a bez niej
nie można w pełni wykorzystać możliwości wieloprocesorowego systemu.
Szczęściem, te zagadnienia też nie są dla informatyki nowe. Nie pisa-
liśmy o tym, ale systemy wieloprocesorowe budowano już ponad pół wieku
temu, z myślą o specjalnych zastosowaniach wymagających bardzo szyb-
kich obliczeń numerycznych, na przykład przy rozwiązywaniu matematycz-
nych modeli bardzo złożonych zjawisk meteorologicznych, geologicznych,
fizycznych, chemicznych i biologicznych, a także w kryptografii, w lotnic-
twie oraz w technice kosmicznej i wojskowej.
Nazywano je od początku superkomputerami (ang. supercomputers),
gdyż pod względem mocy obliczeniowej (i niestety również ceny) zawsze
wyprzedzały o kilka klas współczesne im komputery „głównego nurtu”, czyli
takie, które działały nie w pojedynczych, utajnionych ośrodkach rządowych
i wojskowych, lecz w cywilnych przedsiębiorstwach, uczelniach, a potem
– w prywatnych domach. Na potrzeby superkomputerów opracowywano
odpowiednie techniki (i przystosowane do nich języki) programowania
równoległego (ang. parallel programming), a potem – przy ich użyciu –
oprogramowanie aplikacyjne.
Superkomputery istnieją oczywiście również dzisiaj i krąg ich zasto-
sowań jest w gruncie rzeczy podobny. Jednak modele, które rozwiązują,
są znacznie bardziej złożone. Współczesne superkomputery mają bowiem
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

19. Co było dalej? 469

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

Dotarliśmy do końca tej i tak już przydługiej książki. Poruszyliśmy w niej


wiele spraw: od podstawowych zagadnień teoretycznych – do wiadomości
o budowie i zasadach działania współczesnych komputerów, nie omijając
przy tym również historycznego i personalnego kontekstu, w jakim rozgry-
wała się (i rozgrywa nadal) nasza światowa przygoda z informatyką.
Wiem, że zabrakło w tej książce miejsca dla wielu tematów, zasługu-
jących na omówienie. Poza może krótkimi wzmiankami, nie zajęliśmy się
w tej książce ani bazami danych, ani systemami operacyjnymi, ani metoda-
mi sztucznej inteligencji, sieciami neuronowymi, superkomputerami, tech-
nologiami internetowymi, grami komputerowymi, systemami wbudowany-
mi, internetowymi wyszukiwarkami, komputerami kwantowymi… Zaledwie
wspomnieliśmy o nabierających obecnie szczególnej dynamiki badaniach
inspirowanych zjawiskami występującymi w świecie istot żywych (ang. na-
tural computing)… Tę listę można by długo ciągnąć.
No cóż, nie można napisać o wszystkim. Miał to być jedynie Wstęp
do informatyki – i jest, przynajmniej w takim kształcie, jak wyobrażał go
sobie autor.
Wypada więc podziękować za uwagę i pożegnać się, wyrażając na-
dzieję, że treść tej książki nie tylko okazała się dla czytelnika zrozumiała
i sama w sobie ciekawa, ale także – że ułatwi ona dalsze samodzielne stu-
diowanie tym, którzy poczują się zainteresowani bardziej zaawansowanymi
zagadnieniami informatyki.
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Skorowidz nazwisk

Aiken Howard 349 Jobs Steve 365, 367


Aleksander Wielki (Macedoński) 161 Jordan Frank 313
Allen Paul 365 Karnaugh Maurice 334
Appel Kenneth 62 Kasparow Garri 186
Arystoteles 20, 237, 274 Kennedy John Fitzgerald 357, 369
Babbage Charles 181, 342, 380 Kennedy Robert 369
Backus John 195 Kilby Jack 320, 352
Bardeen John 351 King Martin Luther 369
Battain Walter 351 Kotielnikow Władimir 142
Bell Alexander Graham 126 Lagrange Joseph 160
Berliner Emile 126 Laplace Pierre 160
Berners-Lee Timothy 368 Leibniz Gottfried 342
Bonaparte Napoleon 160, 161 Lovelace Augusta Ada 344, 345
Boole George 276 MacCluskey Edward 339
Brown Gordon 187 Marconi Guglielmo 127
Burks Albert 376 Mauchly John 348, 376
Byron Ada 344 Mealy George 221
Byron George 344 Monge Gaspard 160
Castro Fidel 355 Moore Edward 221
Chomsky Noam 194, 213, 214 de Morgan Augustus 345
Chruszczow Nikita 356 Morse Samuel 127, 260
Churchill Winston 184, 287 Naur Peter 195
Church Alonzo 177 von Neumann John 21, 94, 285, 286, 341,
Clarke Arthur 361 349, 376, 377, 383
Eccles William 313 Nixon Richard 370
Eckert John Adam Presper 348, 376 Noyce Robert 352
Edison Thomas 126 Nyquist Harry 142
Einstein Albert 278, 286, 377 Oppenheimer Robert 378
de Forest Lee 127 Pascal Blaise 342
Fourier Jean Baptiste Joseph 144, 159 Perelman Grigorij 66
Frege Gottlob 277 Popow Aleksander 127
Gagarin Jurij 356 Quine Willard 339
Gates Bill 365, 366, 367 Reagan Ronald 372
Gödel Kurt 286, 377 Rejewski Marian 182
Goldstine Henry 376 Roosevelt Franklin 278, 287
Gorbaczow Michaił 362, 374 Różycki Jerzy 182
Grey Frank 335 Russell Bertrand 277
Guthrie Francis 61 Sacharow Andriej 362, 373, 378
Haken Wolfgang 62 Schickard Wilhelm 341
Hollerith Herman 346, 381 Shannon Claude 142, 278, 282, 286, 287,
Jacquard Joseph Marie 343 292, 350, 376, 388
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

472 Skorowidz nazwisk

Shockley William 351 Whittaker Edward 142


Sołżenicyn Aleksander 373 Wiener Norbert 135, 136
Szestakow Wiktor 285 Wojtyła Karol 373
Tesla Nicola 127 Wozniak Steve 365, 367
Thatcher Margaret 372 Zuse Konrad 285
Turing Alan Mathison 163, 181, 284, 286, Zygalski Henryk 183
350, 376
Watt James 128
Weaver Warren 288
Whitehead Alfred 277
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

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

474 Skorowidz rzeczowy

BNF (zobacz: notacja BNF) Drzewo


Boolowska formuła 301 — decyzyjne 48, 74
Bramki logiczne 20, 273, 295, 296 — rozbioru 208
— iloczynu AND 297 Dyskretyzacja sygnału 139
— NAND 297 Dysk z Fajstos 268
— negacji NOT 297 Dziedzina
— NOR 297 — czasu 150
— sumy OR 297 — częstotliwości 151
— XOR 297 Dziel i zwyciężaj 71
BTS (bazowa stacja nadawczo-odbiorcza) 59
E
C EDVAC 285, 350
Całka Eksplozja wykładnicza 52
— nieoznaczona 92 Elementy wykonawcze 128
— oznaczona 92 ENIAC 285, 347, 376, 381
Chomsky’ego hierarchia Enigma 182
— gramatyk 210
— języków 227 F
Ciało funkcji 80, 81
Faza (cyklu rozkazowego)
COBOL 394, 395
— pobierania instrukcji 427
Colossus 183, 284 — wykonania instrukcji 428
Cybernetyka 135, 136 FIFO 433
Cykl Fonograf 126
— Hamiltona 50, 55, 56 FORTRAN 394, 395
— rozkazowy 383, 385, 426 Funkcja 80
— szyny 408 — boolowska 299
— zegara 314 — przenoszenia (transmitancja) 154
Czas obsługi 97 — wyjścia (automatu) 220
Część — wzbudzeń (automatu) 332
— adresowa rozkazu 389, 392
— operacyjna rozkazu 389, 392 G
Częstotliwość zegara 314, 458, 466
Czujnik 127, 139 Generacje komputerów
— druga 352
D — pierwsza 350
— trzecia 359
Daisy chain 418 Generator liczb
Dalekopis 260 — przypadkowych 96
Dekoder 311, 359, 389, 391, 428 — pseudolosowych 95
Don’t care 328, 331 Gęstość amplitudy 148, 149
Dopełnienie 289 Gigabajt 241
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Skorowidz rzeczowy 475

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

476 Skorowidz rzeczowy

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

Skorowidz rzeczowy 477

Notacja Postać kanoniczna


— BNF 195, 213, 362 — funkcji boolowskiej 299
— heksadecymalna (szesnastkowa) 258 — iloczynu 299
— uzupełnień do 1 (U1) 252 — sumy 299, 334
— uzupełnień do 2 (U2) 245 Powrót
— znak–moduł (znak-wartość) 245 — z podprogramu 432
— z przerwania 449
O Poziom opisu architektury systemu
Obliczenia 23 — PMS 403, 419
— graficzne 24 — RTL 419
— numeryczne 24 Prawa de Morgana 291
— symboliczne 24 Próbkowanie 140
Obwód drukowany 352 Problem
Ochrona pamięci 453 — Collatza 42
Okres próbkowania 139, 152 — kolorowania grafu 58
— kolorowania map 61
P — komiwojażera 47, 73, 76
— optymalnego rozkroju 58
Pamięć
— plecakowy 56
— cache 458, 459, 460
Problemy NP 40, 47, 53, 55, 63, 303
— dyskowa 404, 405
— NP-zupełne 47, 63
— główna 404 Procedura 80
— operacyjna 21 Proces 450
— o dostępie swobodnym 404 Procesor 21, 401
— półprzewodnikowa 363 — graficzny 465
— pośrednia 458 Produkcja (podstawienie) 194
— RAM 363, 459 Program
— rdzeniowa (ferrytowa) 363 — maszynowy 393
— stanu 332 — obsługi przerwania 442, 445, 448
Paradygmat programowania 396 — szeregujący 439
Parser 208, 213 — tłumaczący (translator) 213, 394
Pascal (język) 362 — w języku maszynowym 12, 14
PDP-11 402, 419, 456 — źródłowy 14, 213, 396
Programowanie
Perpetuum mobile 64
— imperatywne 396
Pętla 27
— obiektowe 397
Pismo klinowe 268
— równoległe 468
Płyta główna 403 — strukturalne 362, 397
Podział czasu 59, 439, 450 Projekt „Bomba" 183
Pogłos 154 Przekaźnik 279
Półprzewodnictwo 353 — NC 280
Półsumator 305 — NO 280
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

478 Skorowidz rzeczowy

Przekształcenie (transformata) Fouriera 145, Relacja


149, 152, 158 — następnego stanu 220
Przeniesienie 243 — przejść 220
Przeplatanie adresów 458 Renderowanie 464
Przerwanie 416, 437 Rewolucja informacyjna 15
Przerzutnik 20, 273, 313 Równoważność 276
— asynchroniczny 314, 315 Rozbiór zdania 208
— Eccles–Jordana 313, 391 Rozdzielczość 141
— synchroniczny 314 Rozkaz maszynowy (zobacz: instrukcja)
— typu D 318, 321, 332
— typu JK 319 320
S
— typu RS 318 „Środek z kwadratu" 94
— typu T 316 Satelity telekomunikacyjne 358
Przesuwanie Ścieżka Hamiltona 50
— arytmetyczne 249 Sekwenser 426
— cykliczne 250 Semantyczny model języka 212
— logiczne 250 Semantyka 201
Przetwarzanie potokowe 456 Serwer 98
Przydział szyny (magistrali) 408 Serwomechanizmy 128
Sieć
Pseudokod 83
— działań 28
PSW (zobacz: słowo stanu procesora)
— kolejkowa 99
Punkt kodowy (Unicode) 267
— logiczna 294, 295
R — obsługi 99
— semantyczna 212
Rachunek zdań 276, 388 Silnia 41, 51
Radary Składowa harmoniczna 145
— geologiczne 156 Skok
— meteorologiczne 156 — bezwarunkowy 29
Regulator odśrodkowy 128 — do podprogramu 432
Reguła gramatyczna — warunkowy 29, 429
— leksykalna 205 Słownik (gramatyki) 190, 194
— nawiasowa 231 Słowo (maszynowe) 241
— rekursywna 203 Słowo stanu procesora 445
— składniowa 195, 205 Sonar 156
Rejestr 320, 359 Sortowanie 32
— arytmetyczny 378 — ze scalaniem 78
— buforowy 414 Southbridge 462
— instrukcji (RI) 421 Spójnik logiczny 274
— licznika rozkazów (PC) 384 Sprzężenie zwrotne
— przesuwny 323 — dodatnie 130, 134
— uniwersalny 421, 422 — ujemne 130, 134
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

Skorowidz rzeczowy 479

Sputnik 356, 373 — sterowania w czasie rzeczywistym 399


Stan — wbudowany 399
— akceptujący 228 — wieloprocesorowy 468
— sterowania 168 — zabezpieczeń 282
Standard Szereg Fouriera 145
— liczb zmiennoprzecinkowych IEEE 257 Sztuczna inteligencja 186
— magistrali (szyny) 409 Szum kwantowania 141, 142
— nagrań CD 153 Szyna systemowa (zobacz: magistrala)
— rejestracji sygnałów audio i wideo 158
Sterowniki 132
T
Stos 86, 449 Tabela
— LIFO 431, 432, 450 — Karnaugha 302, 334, 359
Strategia — prawdy 275, 290
— szeregowania zadań 99 — przejść i wyjść 223
— zstępująca i wstępująca 72 — sąsiedztwa 39
Strona kodowa 266 Tabliczka mnożenia 236
Struktura danych 23, 26, 69 Tabulator 346
Suma Taśma
— boolowska 289 — perforowana 349
— logiczna 275 — wejściowa 218
Sumator 303, 359 Telefon 126
Superkomputer 468 Telefonia GSM 60
Suwak logarytmiczny 124, 125, 126, 362 Telegraf 127
Sygnał 141
Teoria
— analogowy 138
— automatów 136
— dyskretny 138
— informacji 288
— nieokresowy 148
— kolejek 101
— zegarowy 314
— masowej obsługi 101
Symbol
— sterowania 131, 136
— końcowy 194
— translacji 395
— niekońcowy 195
Terabajt 241
— początkowy 194
Test Turinga 186
Symulacja 102, 103
Teza Churcha–Turinga 163, 177
Synteza logiczna 20, 273
Tożsamość (tautologia) 291
System
— algebry Boole'a 291
— cyfrowej telefonii (GSM, DCS) 158
— dziesiętny 238 Transformata Fouriera 147
— obsługi 98 — dyskretna 149
— operacyjny 438, 453, 454 — szybka 150
-- OS/360 371 Translator 13, 87, 393
— przerwań 22, 437 Tranzystor 351
Norbert Niziołek (norbert.niziolek@gmail.com). Zamówienie numer: Kamami/073782

480 Skorowidz rzeczowy

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

You might also like