Podręcznik

Strona: SEZAM - System Edukacyjnych Zasobów Akademickich i Multimedialnych
Kurs: Kody i szyfry
Książka: Podręcznik
Wydrukowane przez użytkownika: Gość
Data: piątek, 4 kwietnia 2025, 20:54

1. Pojęcia podstawowe

Pojęcia podstawowe

1.1. Alfabet

Alfabet to dowolny niepusty zbiór skończony V (niekiedy dodajemy zbiór symboli lub zbiór liter). Elementy alfabetu nazywamy literami lub symbolami. Alfabet tak jak zbiór oznaczamy najczęściej dużymi literami alfabetu łacińskiego np. V, A itd.

Alfabet binarny (inaczej alfabet dwójkowy) to alfabet złożony z 2 elementów: np. V = {0, 1}, V = {L, H}.
Alfabetami są zbiory skończone: V = {a, b, c}, V = {0, 1, 2, ..., 9}, V = {a, b, c, ..., x, y, z}, 
V = {a, b, c, ..., x, y, z, A,  B, C, 0, 1, 2, ..., 9} 
Przy formalnym opisie języka programowania pierwszym krokiem jest zawsze sprecyzowanie skończonego zbioru symboli czyli alfabetu, którym będziemy się posługiwać. Podanie, sprecyzowanie alfabetu (a ściślej alfabetów) powinno stanowić również zawsze pierwszy krok w definicji automatu skończonego, czy też w definicji kodu. Pojęcia kodu i automatu skończonego będą omawiane w dalszej części wykładu.

Warto zwrócić uwagę, na fakt że to co nazywamy symbolem lub literą jest kwestią umowy. W systemach cyfrowych symbole często utożsamiamy z pewnymi łatwo rozróżnialnymi sytuacjami fizycznymi, stanami układu fizycznego np. stanami układu elektronicznego zwanego przerzutnikiem bistabilnym (układ przerzutnika bistabilnego ma 2 łatwo rozróżnialne stabilne stany).

Dla nas symbolem będzie np. wartość napięcia w określonym węźle układu cyfrowego (H - wyższe napięcie np. 5 V lub L - niższe napięcie np. 0 V) a alfabetem zbiór V = {L, H}. Typowe wartości napięcia H i L w systemach cyfrowych to H = 5 V i L = 0 V (np. tak jest w tzw. układach TTL i CMOS o małej skali scalenia) ale wewnątrz układu scalonego o dużej skali scalenia stosuje się z reguły napięcia niższe np. H = 3,3 V, L = 0 V. Oczywiście rozsądek techniczny każe uznawać za napięcie H wszystkie wartości napięć z pewnego niewielkiego przedziału IH zawierającego nominalną wartość napięcia H (czyli napięcia zbliżone do H) i podobnie za napięcie L przyjmujemy wszystkie napięcia z niewielkiego przedziału  IL zawierającego nominalną wartość napięcia L (czyli napięcia zbliżone do L). Musimy przy tym spełnić oczywiście warunek by I∩ LH = 0.

Symbolem może być również przemagnesowanie określonego miejsca nośnika magnetycznego lub brak takiego przemagnesowania. Uzyskujemy w tej sytuacji 2 symbole, które łatwo możemy zapisywać na powierzchni magnetycznej za pomocą tzw. głowicy magnetycznej.

Symbolem może być też drobne wgłębienie na powierzchni CD-ROM-u (tzw. pit) rozpraszający światło promienia lasera głowicy odczytującej lub brak pit-u na powierzchni CD-ROM-u powodujący odbicie promienia laserowego. Odbity promień lasera trafia do detektora (diody detekcyjnej) wywołując impuls elektryczny odpowiadający 1.

Symbolem może być również stan przełącznika mechanicznego o 2 (czy ogólnie n pozycjach stabilnych).

Cechą charakterystyczną symboli czyli liter wchodzących w skład alfabetu jest łatwość ich zapisywania (czyli zapamiętywania) i odczytywania bez pomyłek. Stąd zresztą popularność w praktyce alfabetu binarnego. Układ fizyczny mający tylko dwa dobrze wyodrębnione stany utożsamiane odpowiednio z 0 lub 1 okazuje się w praktyce pewniejszy, bardziej niezawodny.

Reasumując, powyżej opisaliśmy 4 najczęściej stosowane sposoby reprezentacji w systemach cyfrowych symboli 0 i 1 :

  • sposób elektryczny wykorzystujący 2 poziomy napięcia L i H w określonym węźle układu elektronicznego (jest to tzw. kodowanie potencjałowe „0” i „1”)
  • sposób magnetyczny wykorzystujący przemagnesowanie nośnika magnetycznego
  • sposób optyczny wykorzystujący zmianę własności optycznych powierzchni odbijającej światło laserowe
  • sposób mechaniczny wykorzystujący stan przełącznika mechanicznego.
Istota rzeczy: Właściwie pojęcie alfabetu jest intuicyjnie jasne a rola dokładnie taka sama jaką pełni alfabet w języku naturalnym. Alfabet służy do zapisywania słów. Warto zwrócić uwagę, na fakt, że to co nazywamy symbolem w formalnej definicji alfabetu jest oczywiście kwestią umowy.

1.2. Słowo

Słowo nad alfabetem V (ang. word) to dowolny ciąg skończony o wartościach w zbiorze V. Czasami słowo nad ustalonym alfabetem nazywamy tekstem lub napisem (a w językach programowania również łańcuchem lub stringiem). Słowo a1, a2, ..., an zapisujemy z reguły bez przecinków tzn. jako: a1a2...an. Upraszcza to notację nie prowadząc na ogół do nieporozumień.

Jeśli V1 = { a, b, c, } jest ustalonym alfabetem, to aabbba, dcba są słowami nad alfabetem V1.  Słowo abbde nie jest słowem nad alfabetem V1 ale jest słowem nad alfabetem V = a, b, c, d, e }. Słowo 11100011 jest słowem nad alfabetem V2 = { 0, 1 }.
Istota rzeczy: Formalne pojęcie "słowo nad alfabetem" z powyższej definicji odpowiada słowu języka naturalnego, jednak z tak rozumianym bardzo formalnie słowem nie musimy wiązać żadnego znaczenia.

Ilość wyrazów ciągu a1a2...anazywamy długością słowa a1a2...anp. słowo abccd nad alfabetem V = { a, b, c, d } ma długość 5 a słowo ala długość 3.

Jeśli V = { 0, 1 } to słowa nad tym alfabetem nazywamy słowami binarnymi. Słowo binarne o długości 8 bitów np. 10101010 nazywamy bajtem (ang. byte).

Zamiast słowa bajt (lub byte) używamy skrótu B. Właśnie w bajtach podajemy najczęściej pojemność różnego typu pamięci. Każdy adept inżynierii komputerowej wie, że typowa pojemność tzw. pamięci operacyjnej komputera klasy PC jest rzędu jednego gigabyte'a (1 GB czyli jeden gigabyte to 109 B), a typowa pojemność napędu twardego dysku jest rzędu 100 GB . Pojemności tzw. macierzy dyskowych czy streamerów mierzymy w terabajtach (1 TB czyli jeden terabyte to 1012 B).

Reasumując: typowe jednostki, w których mierzymy pojemność pamięci to

- B (bajt, 1 B)
- kB (kilobajt 103 B)
- MB (megabajt 106 B)
- GB (gigabajt 109 B)
- TB (terabajt 1012 B)

Pewnego komentarza wymaga jeszcze pojęcie bitu. Bit to symbol (litera) 0 lub 1. Mówimy, też, że bit to cyfra dwójkowa. Często jednak pojęcie bitu wiążemy z ustalonym słowem binarnym np. a1a2...an. Możemy wówczas mówić o k-tym bicie słowa binarnego, gdzie k ≤ n, którym z definicji jest k-ty wyraz ciągu a1a2...aczyli ak. W innym jeszcze znaczeniu słowo "bit" oznacza jednostkę ilości informacji definiowaną w teorii informacji.

W terminologii stosowanej przez firmę Intel słowo (ang. word) oznacza również słowo binarne 16 bitowe (2 bajty) a podwójne słowo (ang. double word) oznacza słowo binarne 32 bitowe (4 bajty).

1.3. Język

Zbiór wszystkich słów nad ustalonym alfabetem V oznaczamy symbolem V* a dowolny niepusty podzbiór L tego zbioru nazywamy językiem. Do zbioru Vzaliczamy również słowo puste (oznaczane na ogół symbolem ε). Słowo puste ma długość 0. Zbiór Vjest oczywiście nieskończony ale przeliczalny, mamy bowiem

V* = {ε} ∪ V ∪ V∪ ... ∪ Vn ∪ ...

W zbiorze V* definiujemy działanie dwuargumentowe \circ V× V→ Vtzw. konkatenację (ang. concatenation). Jeśli α, β ∈ V* i α = a1a2...an β = b1b2...bto z definicji mamy

a \circ b = a1a2...an \circ b1b2...bn \overset{df}=  a1a2...anb1b2...bn

Jak widać konkatenacja jest zestawianiem słów np. ala konkatenowane z makota daje alamakota, podobnie kot z let daje kotlet.

Jak łatwo sprawdzić działanie konkatenacji jest łączne, tzn. dla każdego  α, β, γ ∈ V* mamy (α \circ β) \circ γ = α \circ (β \circ γ). Mamy ponadto dla każdego α ∈ V*; α \circ ε = ε \circ α = α  zatem słowo puste ε jest jedynką działania \circV× VV*. Inaczej mówiąc konkatenacja ze słowem pustym dowolnego słowa α ∈ Vnie zmienia tego słowa. Zbiór ∈ V* z działaniem konkatenacji \circV× VV jest więc monoidem (czyli półgrupą z jednością). 

Widać natychmiast, że konkatenacja na ogół nie jest działaniem przemiennym w V*, ale zawsze jest działaniem łącznym, tzn. na ogół \cdot y ≠ y \cdot x  dla x, y ∈ V*, ale zawsze tzn. dla każdego x, y, z ∈ V*, mamy:

(\cdot y)  \cdot z  = x \cdot (y  \cdot z)

Język to dowolny podzbiór zbioru V*. Podzbiór ten można definiować na wiele sposobów np. za pomocą wyrażeń regularnych, notacją Backusa-Naura, gramatyką, automatem skończonym itd.. Wszystkie te sposoby poznamy w dalszym ciągu. Istnieje również wiele typów języków m.in. języki regularne, języki bezkontekstowe itd.

Jeśli V ={0,1} to zbiór {0,001 ,100001} jest językiem. 
Sam alfabet V jest również językiem ponieważ jest to zbiór wszystkich jednoliterowych słów nad alfabetem V
Zauważmy, że Vjest zbiorem przeliczalnym, zatem również każdy język jest co najwyżej zbiorem przeliczalnym.
Istota rzeczy: Jeśli weźmiemy kilka słów a może nieskończoną liczbę słów to mamy język. Możemy 2 słowa zestawiać razem tworząc nowe słowo. Nazywa się to konkatenacją.

Złożeniem dwóch języków K i L, gdzie K, LV* nazywamy język

KL \overset{df}{=} { x, y ∈ <em>V<sup>*</sup></em>;<em> </em>x, <em>y</em> ∈ <em>K</em> oraz <em>y</em> ∈ <em>L</em>}

Potęga języka. Potęgę Ljęzyka definiujemy indukcyjnie następująco

L\overset{df}{=} {ε}, L\overset{df}{=} L,  L\overset{df}{=} LLLn+1 \overset{df}{=} LLn, ... 

Potęga liter. Potęgę liter definiujemy indukcyjnie następująco. Niech a będzie dowolną ustaloną literą aVwówczas a\overset{df}{=} {ε}, a\overset{df}{=} aa\overset{df}{=} aa, ..., an+1 \overset{df}{=} aan, ... 

Potęga słów. Potęgę słów definiujemy indukcyjnie następująco. Niech w będzie dowolnym ustalonym słowem, wV*, wówczas w\overset{df}{=} {ε}, w\overset{df}{=} ww\overset{df}{=} ww, ..., wn+1 \overset{df}{=} wwn, ... 

Domknięcie języka (gwiazdeczka Kleen'a) L*. Niech V będzie ustalonym alfabetem a L językiem LV*. Domknięciem języka L nazywamy zbiór L* ⊂ V*, gdzie

L* \overset{df}{=} LL1 ∪ L2 ∪ ...,  ∪ Ln ∪ ...

1.4. Metryka Hamminga

Niech będzie dowolnym ustalonym alfabetem. Wprowadźmy w tym alfabecie metrykę dyskretną ρd: V × → R+. Z twierdzenia 1 z Dodatku (p. Metryka Hamminga) wynika, że funkcja ρH: Vn × Vn → Rzdefiniowana wzorem: dla każdego y, x ∈ Vn

ρ(x, y) = \displaystyle\sum^n_{i+1} ρ(xi, yi)

jest metryką. Nazywamy ją metryką lub odległością Hamminga w przestrzeni Vn (Vn jest to przestrzeń wszystkich słów o długości n nad alfabetem V). Najczęściej rozważamy metrykę Hamminga dla Vn = {0, 1}. Oczywiście ρHVn × Vn N ∪ {0} czyli ρH jest funkcją o wartościach w zbiorze liczb całkowitych nieujemnych.

Wartość ρ(x, y) = \displaystyle\sum^n_{i+1} ρ(xi, yi) dla 2 słów y, x ∈ Vn (czyli 2 słów o długości n nad alfabetem V) jest równa liczbie pozycji na, których słowa x i y się różnią. Jeśli np. przesyłamy słowo x przez kanał telekomunikacyjny i na wyjściu tego kanału otrzymujemy słowo y to ρ(x, y) podaje liczbę błędów transmisji słowa x.
Jeśli V = {0,1} oraz = 01110000 i = 10001111 to odległość Hamminga tych 2 słów jest równa 8.

Waga słowa binarnego. Wagą słowa binarnego z przestrzeni {0,1}n (lub ogólniej z {0,1}*) nazywamy liczbą jedynek w tym słowie. W ten sposób definiujemy na {0,1}* funkcję w: {0,1}→ N ∪ {0}. Oczywiście dla a ∈ {0,1}mamy w(a) = ρ(0, a) gdzie ρjest metryką Hamminga. Można wyrazić metrykę Hamminga ρH w {0,1}za pomocą funkcji wagi w.

Niech x, y ∈ {0,1}n, x = (x1, x2, ..., xn), y = (y1, y2, ..., yn), wówczas mamy

ρ(x, y)\overset{df}{=} \displaystyle\sum^n_{i=1} ρ_d(x_i, y_j) \overset{df}{=} \displaystyle\sum^n_{i=1} w(x_i-_2y_i) = w(x-y)=w(x+y)

gdzie xi - 2 yi oznacza różnicę modulo 2, xy oznacza różnicę modulo 2 po współrzędnych a x + y sumę modulo 2 po współrzędnych.


Waga słowa 11111111 jest równa 8, tzn. w(11111111)=8, w(00)=0, w(001)=1.

 

1.5. Kody

Niech V1 oznacza dowolny ustalony zbiór niepusty a V2 dowolny alfabet. Zbiór V1 będziemy nazywać zbiorem obiektów kodowanych. Istnieje kilka definicji pojęcia kodu. Najogólniejsza definicja jest taka. Kod to relacja dwuargumentowa k ⊆ V× V*1 spełniająca warunek: dla każdego x1V1 czyli dla każdego elementu ze zbioru obiektów kodowanych istnieje takie x2V*2 , że (x1, x2) ∈ k. Elementy przeciwdziedziny relacji k czyli elementy zbioru {<em>x</em><sub>2</sub> ∈ <em>V<sup>*</sup></em><sub>2</sub>; (<em>x</em><sub>1 </sub><em>,x</em><sub>2</sub>) ∈ <em>k</em>} nazywamy w tej sytuacji słowami kodowymi. Jeśli V2 = {0, 1} to kod nazywamy kodem binarnym lub kodem dwójkowym. W szczególności dowolne odwzorowanie f: V1V*2 jest oczywiście kodem.

Druga definicja kodu nieco bardziej "wymagająca" jest taka: kod to dowolne odwzorowanie f: V1V*2 żądamy przy tym z reguły by funkcja f była różnowartościowa i nie przyjmowała wartości ε i tak najczęściej rozumiane jest pojęcie kodu. W dalszym ciągu pod pojęciem kodu będziemy rozumieć tę najbardziej restryktywną definicję. Sam proces obliczania czy ustalania wartości funkcji f dla elementów zbioru V nazywamy kodowaniem. Wartość f(x1) ∈ V*jest słowem kodowym, kodującym element x1V1.

Jeśli istnieje takie nN, że dla każdego xV1 słowo kodowe ma stałą długość n to kod nazywamy kodem o stałej długości (ang fixed length code) lub dokładniej kodem o stałej długości n słowa kodowego. Oczywiście mamy wówczas  f: V1Vn2. Najczęściej w systemach cyfrowych mamy do czynienia z takimi właśnie kodami. Przykładem takiego kodu jest znany 8 bitowy kod alfanumeryczny ASCII.

Istota rzeczy: Obiekty chcemy "etykietować", opisywać za pomocą słów nad ustalonym alfabetem i czynimy to za pomocą kodu. Jest to wygodne i naturalne zwłaszcza jeśli chcemy reprezentować obiekty różnego typu w systemie komputerowym. Z każdym obiektem wiążemy dokładnie jedno słowo.

Kod redundancyjny (lub nadmiarowy). Niech  f: V1V*2 będzie kodem. Oznaczmy przez zbiór obiektów kodowanych słowami o długości nN tzn. niech . Jeśli istnieje takie 1{;()nnAxVfxV=∈∈2 } nN∈, że V czyli zbiór NiefA jest właściwym podzbiorem zbioru V
to kod nazywamy redundancyjnym lub nadmiarowym. Najkrócej: kodem redundancyjnym nazywamy dowolny kod 2n12:fVV∗→ taki, że funkcja różnowartościowa f nie jest „na”.

Istotą rzeczy jest tu istnienie niepustego słowa w zbiorze V2∗, które nie koduje żadnego obiektu. W powyższej definicji nie interesuje nas czy zbiór obiektów kodowanych jest skończony czy nieskończony.

Kod redundancyjny (lub nadmiarowy) o stałej długości słowa kodowego. Jeśli rozważamy kod o stałej długości (implikuje to skończoność zbioru obiektów kodowanych ) i odwzorowanie f nie jest "na" (czyli jest właściwym podzbiorem zbioru ) to kod taki nazywamy kodem redundancyjnym lub nadmiarowym o stałej długości słowa kodowego. W takim kodzie nie wykorzystujemy wszystkich możliwych słów kodowych o długości n do kodowania obiektów kodowanych. nVVf21:→1V)(1VfnV2

Kody redundancyjne umożliwiają

  • 1.sprawdzenie czy dane słowo jest poprawnym słowem kodowym (wykrycie błędu)
  • 2. ewentualną korekcję błędu jeśli taki błąd wystąpił na którejś pozycji słowa

Kody redundancyjne znajdują więc zastosowanie wszędzie tam gdzie zależy nam na wykrywaniu lub korekcji błędów występujących w słowie kodowym. Błędy takie pojawiają się czasami podczas transmisji lub przechowywania danych a wynikają z nieuniknionych w realnym środowisku fizycznym szumów i zakłóceń a również co może się zdarzyć z uszkodzenia układów elektronicznych przetwarzających informację. Każdy kod wykrywający błędy i każdy kod korygujący błędy jest kodem nadmiarowym.

Zauważmy jeszcze, że istota kodu polega na wiązaniu z obiektem kodowanym pewnego słowa nad ustalonym alfabetem V. Możemy sobie wyobrażać, że to słowo w pewien sposób opisuje wybrany obiekt, daje jego syntetyczną charakterystykę. Jednak ilość wszystkich słów nad dowolnym alfabetem V jest przeliczalna. Zatem zakładając, że kod jest funkcją różnowartościową dostajemy, że zbiór obiektów kodowanych musi być przeliczalny. Nie ma więc np. kodu kodującego wszystkie liczby rzeczywiste (bo zbiór liczb rzeczywistych nie jest przeliczalny) choć jak pokażemy w dalszym ciągu łatwo zdefiniować kod dla zbioru wszystkich liczb wymiernych. 22

Często przez „kod”, dokładniej "kod programu”, rozumie się zapis programu w ustalonym języku programowania, najczęściej w assemblerze. Rzecz jasna słowo kod w tym sensie ma niewiele wspólnego z powyżej definiowanym pojęciem kodu. Mówimy często "kod źródłowy", "kod wynikowy", "kod wykonywalny".

1.6. Podział kodów

Jeśli zbiorem obiektów kodowanych V1 jest zbiór znaków to kod nazywamy kodem alfanumerycznym. Jeśli zbiorem obiektów kodowanych V1 są liczby to kod nazywamy kodem numerycznym. Kod numeryczny to inaczej kod liczbowy, kod arytmetyczny, zapis liczbowy, notacja liczbowa, lub system zapisu liczb. 11

Podział kodów:

  • Kody α- numeryczne o stałej długości (np. ASCII, Unicode)
  • Kody α- numeryczne o zmiennej długości (np. kod Huffmana)
  • Kody liczbowe o stałej długości (np. kod NKB, zapis uzupełnień do 2)
  • Kody liczbowe o zmiennej długości (np. zwykły zapis dziesiętny)
  • Kody wykrywające i korygujące błędy (np. kod Hamminga, kod Reeda-Solomona)
  • Szyfry (ściślej są to indeksowane kluczem rodziny kodów) (np. szyfr RSA, szyfr AES)
  • Inne kody (np. kody paskowe, kody służące do zapisywania informacji na twardych dyskach, kody używane w transmisji danych itd. ).d

 

2. Kody numeryczne i arytmetyka cyfrowa

Kody numeryczne i arytmetyka cyfrowa

2.1. Kody numeryczne

Kod numeryczny (lub inaczej kod liczbowy, kod arytmetyczny, zapis liczbowy, notacja liczbowa, lub system zapisu liczb). Jeśli zbiorem obiektów kodowanych V1 są liczby to kod nazywamy kodem numerycznym Typowe alfabety V2 dla kodu numerycznego to V2 = {0,1}, V2 = {1,2,3,4,5,6,7}, V2 = {0,1,2,3,...,9}, V2 = {0,1,...,9, A, B, C, D, E,  F} a w kryptografii V2 = {abc, ..., z}.

W tym momencie mogłoby się zrodzić w umyśle Uważnego Czytelnika straszliwe podejrzenie, że na świecie mamy tylko 2 kategorie kodów kody alfanumeryczne i numeryczne. Na szczęście nie grozi nam nuda i mamy jeszcze wiele, wiele różnych rodzajów kodów np. kody paskowe (inaczej kreskowe). W kodach paskowych (są różne rodzaje tych kodów ale wszystkie są kodami o stałej długości słowa kodowego) obiektami kodowanymi są np. towary znajdujące się w handlu a literami alfabetu, w którym zapisujemy słowo kodowe są paski o różnej grubości (grubość paska jest ściśle związana z jedną z cyfr 0,1,2,3,4,5,6,7,8,9 . Przykładem takiego kodu jest np. kod EAN 13 .

O wartości i praktycznej przydatności kodu numerycznego czyli zapisu liczbowego decyduje łatwość wykonywania pięciu elementarnych działań na reprezentowanych danym zapisem liczbach. Konkretnie chodzi o działanie wyznaczania liczby przeciwnej do danej liczby (działanie jednoargumentowe), dodawania, odejmowania, mnożenia i dzielenia liczb (działania dwuargumentowe). Bardzo istotna jest również łatwość porównywania liczb w danym zapisie. Pewnego komentarza wymaga tu sformułowanie "łatwość wykonywania", które oznacza łatwość realizacji algorytmu przez człowieka lub przez hardware albo software komputera.

2.2. Naturalne kody wagowe dla liczb naturalnych i zera

Podstawowy fakt z arytmetyki umożliwiający skonstruowanie dużej klasy kodów liczbowych tzw. naturalnych kodów wagowych jest następujący:

Twierdzenie: Dla dowolnej liczby N i ustalonego ciągu (m_i)^\infty_{i=0}, gdzie mi ∈ {2,3,...} istnieje dokładnie jeden ciąg skończony ak, ak-1, ..., a1, a0 taki, że

1) \qquaddla każdego i = 0,1,...,k, a∈ {0,1...<em>m<sub>i </sub></em><sub>-1</sub>}, ak ≠0

2) \qquadm = akmk-1mk-2 ⋅  ... ⋅ m0 + ak-1mk-2mk-3 ⋅ ...⋅  m0 + ...a2m1m0 + a1m0 + a0

Dowód: Prosty dowód pozostawiamy Czytelnikowi (por. też D.E.Knuth [5]) 

Wniosek: Dla dowolnej liczby mN i ustalonej liczby W ∈ {2,3} istnieje dokładnie jeden ciąg skończony ak,ak-1...a1a0 taki, że

1) ak,ak-1...a1a0 ∈ {0,1...<em>W</em>-1}, a {0}

2) makWk . ak-1Wk-1 a1a0

Powyższy wniosek pozostaje również z pewnymi modyfikacjami prawdziwy dla W = -2 (tzw. zapis minusdwójkowy) i innych wag ujemnych (por. [10]). Wagi ujemne nie mają jednak obecnie większego praktycznego znaczenia

Liczba W nosi nazwę wagi lub podstawy zapisu wagowego (ang. radix). W praktyce na oznaczenie liczb {0,1...<i>W </i>- 1} używamy ustalonych symboli z pewnego równolicznego z {0,1...<i>W </i>- 1} alfabetu V, nazywając je cyframi. Wygodnie jest niekiedy te symbole utożsamiać z liczbą ze zbioru  {0,1...<i>W </i>- 1} w tym sensie, że raz traktujemy je jak symbole, raz jak liczby. Możemy też wręcz przyjąć, że V = {0,1...<i>W </i>- 1}.

Odwzorowanie K: N ∪ {0} ∋ → K(m) = akak-1...a1a0 ∈ V* określone na zbiorze ∪ {0} i zdefiniowane w powyższym wniosku nazywamy kodem wagowym z wagą W ≥ 2 przy czym przyjmujemy K(0) = 0.

Typowe najczęściej używane wagi to 2, 8, 10, 16, 26 (26 to liczba małych liter w alfabecie angielskim). Wag W=26 i W=256 używamy w kryptografii do utożsamienia tekstu z liczbą ze zbioru N ∪ {0}.

Zapisy wagowe dla wag 2, 8, 10, 16, noszą następujące nazwy:

Zapisy wagowe dla wag 2, 8, 10, 16, noszą następujące nazwy:

W=2, kod NKB - naturalny kod binarny lub zapis dwójkowy naturalny (lub zwykły zapis dwójkowy), mamy dwie cyfry: 0, 1

W=8, zapis oktalny, mamy 8 cyfr 0, 1, 2, 3, 4, 5, 6, 7

W=10, zapis dziesiętny, mamy 10 cyfr 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

W=16, zapis hexadecymalny (lub szesnastkowy) , mamy 16 cyfr 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F (stosujemy dodatkowe cyfry A, B, C, D, E, F)

Jeśli mamy wątpliwości, jakiego zapisu używamy, to po zapisie liczby stosujemy znak spoza alfabetu w jakim zapisujemy liczbę, np.: B – dla zapisu binarnego, O - dla zapisu oktalnego, D - dla zapisu dziesiętnego i H - dla zapisu hexadecymalnego. Czasami z kontekstu wynika jakim zapisem operujemy więc dodatkowe wyjaśnienia nie są potrzebne. Zapis dziesiętny będziemy w dalszym ciągu traktować zwyczajowo jako wyróżniony i z reguły literę D będziemy pomijać.

101(B) lub 101B dla zapisu binarnego oraz 1A2(H) lub 1A2H dla zapisu hexadecymalnego.

Wygodnie jest też ciąg K(m) = akak-1...a1a0 ∈ V*utożsamiać jeśli trzeba z liczbą m tak jak to zwyczajowo czynimy dla zapisu z wagą W=10 czyli dla dobrze znanego zapisu dziesiętnego.

Zapis binarny : 5= 1001 B, 8= 1000 B, 7=111 B
Zapis hexadecymalny: 8= 8 H, 7=7 H, 15=F H
Kod NKB (naturalny kod binarny) dla zbioru liczb {0,1,...,7}.
 Kod NKB (naturalny kod binarny) dla zbioru liczb {0,1,...,7}.

0→0
1→1
1→10
3→11
4→100
5→101
6→110
7→111

Warto podkreślić, że zbiorem obiektów kodowanych jest dla rozważanych w tym punkcie kodów zbiór liczb naturalnych z zerem tzn. N ∪ {0} a więc rozważane kody są kodami numerycznymi. Jednocześnie słowa kodowe mogą być dowolnie długie. Nie są to więc kody ze stałą długością słowa kodowego. Dla ustalonej liczby a ∈ N ∪ {0} i ustalonej wagi W ∈ {2,3...} łatwo można obliczyć długość słowa kodowego l(a) reprezentującego liczbę a. Jeśli a = 0, to l(a) = 1, jeśli a ≠ to

l(a) = \lfloor log_wa\rfloor+1\qquad(*)

gdzie \lfloor \cdot \rfloor RZ jest tzw. funkcją podłogi (lub jak mówimy krótko podłogą). Jeśli x ∈ R to \lfloor x \rfloor jest największą liczbą całkowitą nie większą od x .

Przykład: Obliczmy korzystając ze wzoru (*) za pomocą ilu bitów zapisujemy w kodzie NKB liczby 2n + 1, 2n - 1. Ponieważ log22n = log102 i funkcja logarytmiczna jest ściśle monotoniczna łatwo obliczamy kolejno:

l(a) = \lfloor log_22^n\rfloor + 1 = n +1 \\ l(a) = \lfloor log_2(2^n+1)\rfloor + 1 = n +1 \\ l(a) = \lfloor log_2(2^n-1)\rfloor + 1 = n

gdzie a oznacza słowo binarne reprezentujące liczbę.

Ile cyfr hexadecymalnych potrzeba do zapisu liczby 2n? Stosując wzór (*) dostajemy l(a) = \lfloor \text {log}_{16}2^n\rfloor + 1 = \lfloor n/4 \rfloor +1.
Obliczmy korzystając ze wzoru (*) za pomocą ilu cyfr dziesiętnych zapisujemy w naturalnym kodzie wagowym dziesiętnym (czyli jak mówimy krótko w zapisie dziesiętnym) liczby 2n, dla n=10. Ponieważ log102n = n log102 łatwo obliczamy kolejno: l(a) = \lfloor log_{10}2^{10}\rfloor + 1 = \lfloor \text{log}_{10}2\rfloor +1 = 4. Podobnie można wykazać, że taka sama liczba cyfr dziesiętnych jest potrzebna do zapisu liczby 2+ 1 i 2n  − 1 przy = 10.

Algorytm dodawania w naturalnym zapisie wagowym z wagą W. Algorytm dodawania w naturalnym zapisie wagowym z wagą W jest w zasadzie identyczny jak w przypadku naturalnego zapisu dziesiętnego. Jeśli dodajemy w naturalnym zapisie wagowym z wagą W 2 liczby a i b reprezentowane słowami

a = anan-1...a1a0 \qquadi\qquad b = bkbk-1,...b1b0

to żeby uzyskać sumę s = snsn-1...s1s0 postępujemy tak:

1) Obliczamy s0 = a0 + b0 (mod W). Jeśli s0 = a0 + b0 ≥ W to podstawiamy c1 := 1 a jeśli s0 = a0 + b0W to podstawiamy c1 := 0 

2) Obliczamy s= a1 + b1+ c1 (mod W). Jeśli s= a1 + b1 + c1 ≥ W to podstawiamy c2 := 1 a jeśli s= a1 + b1 + c1 < W to podstawiamy c2 := 0

.....

i) Obliczamy si = ai + b+ ci (mod W). Jeśli si = ai + b+ ciW to podstawiamy ci+1 := 1 a jeśli si = ai + b+ ci < W to podstawiamy ci+1 := 0

......

n) Obliczamy sn = an + b+ cn (mod W). Jeśli sn = an + b+ cnW to podstawiamy cn+1 := 1 a jeśli sn = an + b+ cn < W to podstawiamy ci+1 := 0.

Algorytm mnożenia w naturalnym zapisie wagowym z wagą W jest w zasadzie identyczny jak w przypadku naturalnego zapisu dziesiętnego ale używamy „tabelki mnożenia” właściwej dla W tzn. używamy „tabelki mnożenia” dla par liczb <0,W−1>×<0,W−1>.

Istota rzeczy: algorytmy dodawania i mnożenia w naturalnych zapisach wagowych pozostają właściwie takie same jak w przypadku dodawania liczb w zapisie dziesiętnym.

Przykład: Mamy 2 liczby w zapisie NKB 10101010a=11111111b=. Stosując opisany algorytm otrzymujemy sumę:

             10101010
Suma:     11111111
            110101001

Mamy 2 liczby w zapisie NKB a = 1010 b =1111. Stosując opisany algorytm mnożenia otrzymujemy iloczyn:

                       1010
                       1111
                      1010
                    1010
                  1010
Iloczyn:    1010
              10010110 

2.3. Naturalne kody wagowe o stałej długości słowa kodowego

Jeśli operujemy słowami kodowymi o stałej długości n i V2 = {0,1, <span class="equation" style="width:100%;;;;">W</span>−1}, gdzie W ∈ {2,3...} to różnych słów kodowych jest W^n. Tyle jest n elementowych wariacji z powtórzeniami ze zbioru W elementowego. Zatem zbiór obiektów kodowanych może zawierać co najwyżej W^n elementów. 

Naturalny kod wagowy o stałej długości słowa kodowego to kod, które dla ustalonego W koduje liczby ze zbioru {0,1,2,...,<span class="equation" style="width:100%;;;;">W^n</span>−1} w taki sposób, że słowa kodowe należą do iloczynu kartezjańskiego W^n. Algorytm tworzenia słów kodowych dla takiego kodu numerycznego polega na uzupełnianiu słowa utworzonego dla naturalnego zapisu wagowego zerami z lewej strony aż do uzyskania wymaganej długości słowa kodowego n.

Z kodami numerycznymi o stałej długości (w szczególności z kodami wagowymi o stałej długości) wagowymi wiążemy pojęcie zakresu zapisu. Zakres zapisu to zbiór liczb reprezentowanych w tym zapisie. Jeśli rozważany kod jest binarny i słowo kodowe n bitowe ma postać an-1...a1a0 ∈ {0,1}n to bit nazywamy bitem najbardziej znaczący lub bitem MSB (Most Significant Bit) a bit a0 bitem najmniej znaczącym lub bitem LSB (Least Significant Bit).

Przykład: Kod NKB (naturalny kod binarny) o długości 3 dla zbioru liczb {0,1,...,7}. Kod NKB czyli naturalny kod binarny nazywa się też naturalnym kodem dwójkowym, lub naturalnym (lub zwykłym) zapisem dwójkowym.

0 → 000
1 → 001
2 → 010
3 → 011
4 → 100
5 → 101
6 → 110
7 → 111
Przykład: Naturalny kod binarny o długości 4 podany niżej ma zakres <0,15>.

0→ 0000
1→ 0001 
2→ 0010
3→0011
4→ 0100
5→ 0101
6→ 0110
7→ 0111
8→ 1000
9→ 1001
10→1010
11→1011
12→1100
13→1101
14→1110
15→1111

2.4. Konwersja naturalnych zapisów wagowych

Chcemy przejść z zapisu naturalnego wagowego z wagą W1 na zapis naturalny wagowy z wagą W2. Postępujemy według schematu

Zapis akak-1...a0 z wagą W\overset{(1)}{\longrightarrow} wartość m \overset{(2)}{\longrightarrow} Zapis a'rar'-1...a'0 z wagą W

(1) Obliczamy po prostu wartość m zgodnie ze wzorem

m = akW1+ ak-1W1W-1 + ... a1W + a0

Wygodnie w tym celu posłużyć się schematem Hornera, czyli algorytmem obliczenia wartości wielomianu akxk + ak-1xk-1 + ...a0 w punkcie x.

Schemat Hornera: (...((akx + ak-1)x + ak-2)x + ... + a1)x + a= akxk + ... + a0

(2) Dzielimy z resztą liczbę m przez W2 otrzymując ciąg reszt a'rar'-1...a'0 , dokładniej:

m = m1⋅ W2 + a'0
m1 = m⋅ W2 + a'1
m2 = m3⋅ W2 + a'2

.
.

.
mr-1 = mr⋅ W2 + a'r-1

itd. aż kolejne mr = 0 co stanowi regułę stopu algorytmu konwersji.

Ostatecznie uzyskujemy zapis a'rar'-1...a'0 z wagą W2

2.5. Uzupełnienie do W-1

Dokładniej uzupełnienie do W-1 (gdzie WN, W ≥ 2) słowa anan-1an-2...a0 nad alfabetem {0,1...<em>W−</em>1}. Jest to słowo bnbn-1bn-2...b0 takie, że ai + bi = W−1 dla i=0,1,2,...,n, gdzie dodawanie jest zwykłym dodawaniem ze zbioru liczb całkowitych.

Dla ustalonego n ∈ N ∪ {0} uzupełnienie do W-1 jest więc odwzorowaniem:

U1: {0,1,...,W−1}n+1 → {0,1,...,W−1}n+1

Łatwo sprawdzić, że odwzorowanie to jest idempotentne tzn. U1 \circ U= id, czyli uzupełnienie do W-1 zastosowane dwa razy do danego słowa daje to samo słowo.

Przykład: Weźmy W=2 i słowo ośmiobitowe 01010101, uzupełnieniem do W-1 czyli uzupełnieniem do jedności tego słowa jest słowo 10101010.
Przykład: Niech W=10 i niech będzie dane słowo 01010101, uzupełnieniem do W-1 czyli uzupełnieniem do dziewięciu tego słowa jest słowo 98989898.
Przykład: Niech W=16 i niech będzie dane słowo 01010101, uzupełnieniem do W-1 czyli uzupełnieniem do piętnastu tego słowa jest słowo FEFEFEFE. 

Najważniejsze w praktyce jest uzupełnienie do jedności (ang. 1's complement)

2.6. Uzupełnienie do W

Dokładniej uzupełnienie do W (gdzie WN, W ≥ 2) słowa anan-1an-2...a0 nad alfabetem {0,1...<em>W−</em>1}. Niech  słowo bnbn-1bn-2...b0 będzie uzupełnieniem do W-1 słowa anan-1an-2...a0. Potraktujmy słowo bnbn-1bn-2...b0 jak liczbę w zapisie naturalnym wagowym z wagą W i dodajmy do tej liczby 1 arytmetycznie zaniedbując ewentualne przeniesienie na pozycję n+1 . Uzyskana w ten sposób liczba cncn-1cn-2...c0 (w zapisie naturalnym wagowym z wagą W ) nosi nazwę uzupełnienia do W.

Dla ustalonego n ∈ N ∪ {0} uzupełnienie do W jest więc odwzorowaniem:

U2: {0,1,...,W−1}n+1 → {0,1,...,W−1}n+1

Łatwo sprawdzić, że odwzorowanie to jest idempotentne tzn. Uw \circ Uw = id, czyli uzupełnienie do W zastosowane dwa razy do danego słowa daje to samo słowo.

Weźmy W=2 i słowo ośmiobitowe 01010101, uzupełnieniem do W-1 czyli uzupełnieniem do dwóch tego słowa jest słowo (10101010 B) +1=10101011.
Niech W=10 i niech będzie dane słowo 01010101, uzupełnieniem do W-1 czyli uzupełnieniem do dziewięciu tego słowa jest słowo (98989898 D)+1=98989899.

Najważniejsze w praktyce jest uzupełnienie do dwóch (ang. 2's complement)

2.7. Zapis moduł znak

W powyżej przedstawiony w punktach 1-4 sposób reprezentujemy łatwo słowami nad ustalonym alfabetem liczby ze zbioru N ∪ {0} czyli liczby naturalne i zero. Zajmiemy się teraz sposobami reprezentowania liczb ujemnych. Rozszerzymy więc zbiór V1 liczb reprezentowanych na wszystkie liczby całkowite Z.

Omówimy 3 zasadnicze kody numeryczne służące do tego celu: tzw. zapis moduł znak (ang. sign magnitude notation), zapis uzupełnień do W, gdzie W jest wagą lub jak mówimy podstawą zapisu (w szczególności dla W=2 uzyskujemy zapis uzupełnień do 2), zapis uzupełnień do W-1 i jego szczególny przypadek zapis uzupełnień do 1.

Załóżmy, że mamy ustaloną wagę W ≥ 2 i chcemy zapisać liczbę całkowitą a za pomocą zapisu moduł znak. Postępujemy wówczas tak: zapisujemy za pomocą naturalnego zapisu wagowego z wagą W liczbę |a| uzyskując słowo an-1ak-1...a0, gdzie  ai ∈ <0,W−1>.

Słowem reprezentującym liczbę a będzie słowo anan-1ak-1...a0, gdzie  an ∈ {0,1} przy czym jeśli a ≥ 0 to an = 0 a jeśli a < 0 to an = 1.

Zauważmy, że zapis moduł znak jest na dobrą sprawę tym samym pomysłem co zapis liczb dziesiętnych ze znakiem jakim posługujemy się na co dzień.

Zapis moduł znak oczywiście może oczywiście tak zmodyfikować by był kodem o stałej długości słowa kodowego. Istotny staje się wówczas zakres zapisu moduł znak. Jeśli używamy n+1 bitów do zapisu słowa kodowego to zakres ten jest równy <−2n + 1,2n−1>.

Zapiszmy liczbę –5 w zapisie binarnym moduł znak (ze słowem kodowym o długości 8). Liczbę 5 zapisujemy wstępnie jako słowo 7 bitowe 0000101 i ponieważ –5<0 jako pierwszy z lewej strony bit wpisujemy 1 uzyskując: 10000101.

2.8. Zapis uzupełnień do W i zapis uzupełnień do 2

Dokładniej, omówimy zapis uzupełnień do W (kod numeryczny uzupełnień do W) dla liczb całkowitych n ∈ Z (a więc również ujemnych). Wykorzystamy następujący fakt.

Fakt: Wybierzmy dowolne m ∈ Z, m ≠ 0 i ustalmy wagę W (WN, W ≥ 2). Wówczas istnieje dokładnie jeden ciąg: anan-1...a0, gdzie n > 0, taki że dla każdego i = 1,2,...n−1 ai ∈ {0,1,...W−1}, an ∈ {0,1,...W−1}, oraz an = 0 i an-1 ≠ 0 i lub an = 1 taki, że:

n = -(\text{sgn } a_n)W^n + \displaystyle\sum_{i=0}^{n-1}a_iW^i\qquad(*)

Dowód: Prosty dowód pozostawiamy Czytelnikowi jako ćwiczenie.

Dla przypomnienia: funkcja sgn: R → {−1,0,1} (tzw. funkcja signum, czyli znak),

zdefiniowana jest dla wzorem: sgn(x) = \left\{ \begin{array}{ll} { \text{1 gdy x > 0} \\ \text{0 gdy x = 0}\\\text{-1 gdy x < 0}} \end{array} \right.

Jak widać dla mN, m ≠ 0 dzięki powyższemu faktowi możemy zdefiniować odwzorowanie f: Z \ {0} → <0,W−1>* wzorem f (m= anak...a∈ <0,W−1>*. Jeśli przyjmiemy dodatkowo, że f (0) = 0 ∈ <0,W−1>*, to określimy odwzorowanie

f → <0,W−1>*

nazywane kodem uzupełnień do W.

Widać od razu, że współrzędna n (a dokładniej an) określa znak reprezentowanej liczby.

1. Jeśli a= 0 (tzn. słowa kodowe są postaci 0an-1an-2...a0) to liczba jest nieujemna

2. Jeśli a= W−1 (tzn. słowa kodowe są postaci (W−1)an-1an-2...a0) to liczba jest nieujemna

Algorytm obliczania liczby przeciwnej do danej jest w zapisach uzupełnień do W jest następujący. Jeśli mi słowo anan-1...areprezentuje tę liczbę to słowo reprezentujące −m uzyskujemy jako uzupełnienie do W słowa anan-1...a0.

Powyżej zdefiniowany kod uzupełnień do W jest kodem o zmiennej długości słowa kodowego. Zdefiniujemy teraz kod uzupełnień do W o stałej długości słowa kodowego.

Wykorzystamy następujący fakt:

Fakt: Wybierzmy dowolne m ∈ <−Wn,Wn−1>, gdzie n > 0 i ustalmy wagę W (WN, W ≥ 2). Wówczas istnieje dokładnie jeden ciąg: anan-1...a0, taki że dla każdego i = 1,2...,n, ai ∈ {0,1,...,<i>W</i>−1} taki, że:

n = −(sgn an)W\displaystyle\sum^{n-1}_{i=0}a_iW^i\qquad(*)

Dowód: Prosty dowód pozostawiamy Czytelnikowi jako ćwiczenie.

Odwzorowanie f :<Wn,Wn−1>→<0,W−1>n zdefiniowane w powyższym twierdzeniu nosi nazwę zapisu uzupełnień do W (o stałej długości słowa kodowego).

Zakres tak zdefiniowanego kodu jest równy oczywiście <Wn,Wn−1>.

Algorytm obliczania liczby przeciwnej do danej jest w zapisach uzupełnień do W (o stałej długości) jest następujący. Jeśli m ∈ <−Wn,Wn−1> i słowo anan-1...areprezentuje tę liczbę to słowo reprezentujące  −m uzyskujemy jako uzupełnienie do W słowa anan-1...a0.

W systemach cyfrowych stosujemy głównie zapis uzupełnień do 2 (ang. two`s complement notation), który nazywany jest również zapisem U2. Jest to obok zapisu NKB najczęściej stosowany w technice mikroprocesorowej kod numeryczny. Popularność zapisu U2 wynika m.in. z faktu, że algorytmy dodawania i odejmowania w U2 dają się zrealizować a sposób naturalny w zwykłym, prostym sumatorze sumującym liczby w kodzie NKB.

W kodzie U2 (o stałej długości słowa n+1) słowo binarne anan-1...a0 związane jest z reprezentowaną tym słowem liczbą za pomocą wzoru

n = −(sgn an)2\displaystyle\sum^{n-1}_{i=0}a_i2^i = −an2n \displaystyle\sum^{n-1}_{i=0}a_i2^i

a zakres reprezentowanych liczb jest równy <−2n,2n−1>

Bit an nazywamy bitem najbardziej znaczącym lub bitem MSB od ang. Most Significant Bit) a bit a0 bitem najmniej znaczącym lub bitem LSB od ang. Least Significant Bit.

W zapisie U2, żeby obliczyć –m mając kod U2 liczby m (czyli rozwinięcie, jak czasem mówimy) negujemy wszystkie bity i na najmniej znaczącej pozycji dodajemy arytmetycznie 1 (tzn. z uwzględnieniem przeniesień). Uzyskany wynik, tzn. słowo binarne reprezentuje tzn. jest kodem U2 liczby –m. Jest to bardzo wygodny algorytm ale uwaga –m musi należeć do zakresu zapisu.

Przykład: Zapiszmy liczbę –5 w 8 bitowym zapisie uzupełnień do 2 (tzn. ze słowem kodowym o długości 8). Liczbę 5 zapisujemy wstępnie jako słowo 8 bitowe 00000101 (w 8 bitowym kodzie NKB). Ponieważ –5<0 obliczamy uzupełnienie do 2 słowa 00000101 i uzyskujemy ostatecznie 11111011.
Przykład: Zapiszmy liczbę –1 w 16 bitowym zapisie uzupełnień do 2 (tzn. ze słowem kodowym o długości 8). Liczbę 1 zapisujemy wstępnie jako słowo 16 bitowe \underset{n+1}{\underbrace{00...1}} (w 16 bitowym kodzie NKB). Ponieważ –1<0 obliczamy uzupełnienie do 2 słowa \underset{15}{\underbrace{00...001}} (15 zer i jedynka na końcu) i uzyskujemy ostatecznie \underset{16}{\underbrace{11...1}}. Czyli -1 to 16 jedynek. 

 

2.9. Zapis uzupełnień do W–1 i zapis uzupełnień do 1

Zapis uzupełnień do W-1 służy do zapisywania liczb całkowitych. Jeśli liczba mZ jest nieujemna, tzn. m ≥ 0, to zapisujemy ją tak:

m = 0an-1an-2...a0

gdzie an-1an-2a...ajest naturalnym zapisem wagowym z wagą W liczby m, a więc:

m = \displaystyle\sum^{n-1}_{i=0}a_iW^i.

Jeśli liczba mZ jest ujemna (lub równa 0), tzn. LZ, to wstępnie obliczamy jak wyżej słowo kodowe odpowiadające liczbie |m| zapisujemy je tak: |m| = bnbn-1bn-2...b0 a następnie jako słowo kodowe odpowiadające m przyjmujemy słowo będące uzupełnieniem do W-1 słowa bnbn-1bn-2...b0.

Jeśli w opisanej wyżej procedurze stosujemy zamiast naturalnego zapisu wagowego z wagą W zapis naturalny wagowy z wagą W o stałej długości n słowa kodowego to uzyskujemy zapis uzupełnień do W-1 ze stałą długością słowa kodowego. Zakres takiego zapisu uzupełnień do jedności jest równy <−Wn+1,Wn−1>.

W systemach cyfrowych wykorzystuje się najczęściej zapis uzupełnień do 1 ze słowem kodowym o stałej długości. Dla takiego zapisu przy słowie o długości n+1 zakres zapisu jest równy <−2n+1,2n−1>. Zapis uzupełnień do 1 nazywa się też zapisem U1.

Zauważmy, że z definicji zapisu uzupełnień do W-1 wynika różnowartościowość zdefiniowanego kodu na zbiorze Z \ {0} lub <−Wn+1,Wn−1> \ {0} (w przypadku zastosowania słowa o długości n+1) ale liczba 0 może mieć 2 reprezentacje. Np. w przypadku zastosowania kodu uzupełnień do 1 o stałej długości słowa n+1 liczba 0 reprezentowana jest każdym ze słów: \underset{n+1}{\underbrace{00...0}} oraz \underset{n+1}{\underbrace{11...1}} czyli mamy 2 różne słowa oznaczające 0.

Algorytm obliczania liczby przeciwnej jest w zapisie uzupełnień do W-1 wyjątkowo łatwy. Obliczmy po prostu uzupełnienie do W-1 słowa wejściowego. W przypadku U1 sprowadza się to do zanegowania bitów.

Zapiszmy liczbę –5 w 8 bitowym zapisie uzupełnień do 1 (tzn. ze słowem kodowym o długości 8). Liczbę 5 zapisujemy wstępnie jako słowo 8 bitowe 00000101 (w 8 bitowym kodzie NKB). Ponieważ –5<0 obliczamy uzupełnienie do 1 słowa 00000101 i uzyskujemy ostatecznie 11111010. 

2.10. Zapis stałoprzecinkowy

Już wiemy z poprzednich punktów jak można reprezentować liczby naturalne i 0 w systemie cyfrowym oraz jak można reprezentować liczby całkowite czyli mówiąc niezbyt precyzyjnie liczby ze znakiem. Często jednak chcemy reprezentować w systemie cyfrowym ułamki (liczby wymierne) i do tego celu wykorzystujemy koncepcję zapisu stałoprzecinkowego (ang. fixed point notation).

Oczywiście naturalnym pomysłem jest reprezentowanie liczby wymiernej jako pary uporządkowanej liczb całkowitych (m,n), gdzie m, nZ. Tak czasem się robi ale algorytmy wykonywania działań arytmetycznych (i porównywanie liczb) okazuje się bardziej skomplikowane niż w zdefiniowanym poniżej zapisie stałoprzecinkowym.

Do uwzględnienia znaku można użyć zapisu uzupełnień do W, uzupełnień do W-1 lub zapisu moduł znak. W szczególności możemy użyć zapisu U2 lub U1. Dla ustalenia uwagi zakładamy, że używamy zapisu uzupełnień do W.

Zapis stałoprzecinkowy (uzupełnień do W) ze stałą długością słowa kodowego definiujemy następująco. Będziemy reprezentować liczby ze zbioru = {−<em>W<sup>n</sup> + k</em>⋅<em>W</em><sup>−<em>r</em></sup>; <em>k</em> ∈ < 0, <em>W</em><sup><em>n</em>+<em>r</em>+1</sup>>} (gdzie n ≥ 1, r ≥ 0, n, r ∈ Z). Liczbę n ∈ A reprezentujemy słowem anan-1...a0a-1...a-r (słowem nad alfabetem <0, W−1>) tak by był spełniony wzór

n = -(\text{sgn } a_n)W^n + \displaystyle\sum_{i=-r}^{n-1}a_iW^i\qquad(*)

Jeśli założymy, że an  ∈ {0,<em>W</em>−1} i a ∈ {0,<em>W</em>−1} to takie słowo anan-1...a0a-1...a-jest wyznaczone jednoznacznie.

 

Rys. 1. Rozmieszczenie na osi liczbowej liczb reprezentowanych w zapisie stałoprzecinkowym. Warto zwrócić uwagę na równomierne rozmieszczenie reprezentowanych liczb. ”Skok” czyli odległość sąsiednich liczb wynosi Wr.

Liczba dodatnia reprezentowana jest więc ciągiem 0an-1an-2...a0a-1...a-r i możemy zapisać

0an-1an-2...a0a-1...a-r = \displaystyle\sum^{n-1}_{r=-r}a_iW^i

a liczba ujemna ciągiem

anan-1an-2...a0a-1...a-r = -W^n + \displaystyle\sum^{n-1}_{r=-r}a_iW^i

gdzie an  = W −1.

Zauważmy, że słowo o długości n+r+1 zostało podzielone na 3 części, 3 pola: „znak”, „część całkowitą” (tutaj przecinek lub kropka) i „część ułamkową” (por. Rys. 2 dla przypadku W=2). Wprowadzamy przecinek lub kropkę pomiędzy część całkowitą i ułamkową nie zapisując jednak formalnie w specjalny sposób tego symbolu. Poprzestajemy na umowie, że kropka znajduje się w określonym miejscu słowa. Czasami w zapisie stałoprzecinkowym nie ma części całkowitej, czasami nie ma ułamkowej.

Dla W=2 czyli dla zapisu binarnego mamy 1 bit znaku, n bitów części całkowitej i r bitów części ułamkowej.

Algorytm obliczania liczby przeciwnej do danej jest identyczny jak w przypadku zapisu uzupełnień do W. W celu znalezienia liczby przeciwnej obliczamy uzupełnienie do W słowa wejściowego.

Analogicznie jak wyżej można zdefiniować zapis stałoprzecinkowy o zmiennej długości słowa kodowego.

Oczywiście zapisy U2, U1 i moduł znak tak jak zdefiniowano je w punktach poprzednich można traktować jako zapisy stałoprzecinkowe z odpowiednio umieszczonym przecinkiem.

Rys. 2. Koncepcja zapisu stałoprzecinkowego

 

2.11. Zapis zmiennoprzecinkowy

Zapis zmiennoprzecinkowy lub notacja zmiennoprzecinkowa (czasem mówimy zapis zmiennopozycyjny) to jeden z najczęściej stosowanych w mikroprocesorach kodów numerycznych. Angielskie nazwy to floating point notation lub scientific notation.

Zasadniczy pomysł jest bardzo prosty. Liczby bardzo duże i bardzo małe wygodnie jest zapisywać w następującej wykładniczej postaci np. x = −1,5 ⋅ 1015 lub ogólniej w postaci = (−1)fWe. Możemy zakładać, że W ∈ N, W ≥ 2 ale w praktyce wykorzystujemy W = 10 lub W = 2.

W dalszym ciągu będziemy zakładać, że W = 2 i będziemy rozważać zapis zmiennoprzecinkowy binarny. Liczba s ∈ {0,} reprezentuje znak liczby x. Liczbę f ≥ 0 nazywamy mantysą (lub częścią ułamkową ang. mantissa lub fraction) a liczbę e wykładnikiem (lub cechą ang. exponent). Tak więc liczbę = (−1)fWmożemy reprezentować jako trójkę uporządkowaną (s, f, e) czyli trójkę (znak,mantysa,wykladnik).

Oczywiście jeśli nie narzucimy dodatkowych warunków na f i e to takie przedstawienie nie jest jednoznaczne. Liczby f i e muszą być ponadto zapisane w ustalonych kodach numerycznych. Zakładamy w dalszym ciągu, że są to kody numeryczne binarne.

Przedstawienie liczby staje się jednoznaczne jeśli liczba ma tzw. postać znormalizowaną. Mówimy, że liczba reprezentowana w zapisie ma postać znormalizowaną jeśli mantysa f zapisana w zapisie stałoprzecinkowym bez znaku (z przecinkiem za pierwszym bitem) ma postać: 1.ciąg bitów czyli

(jedynka przed przecinkiem jako pierwszy bit, przecinek (czyli kropka), ciąg bitów)

co odpowiada wartości f z przedziału [1,2). Ponieważ liczby pamiętamy w postaci znormalizowanej nie ma potrzeba pamiętania pierwszego bitu mantysy f.

Najczęściej zapis zmiennoprzecinkowy używany jest zgodnie ze standardem IEEE 754. Poniżej opiszemy formaty tego standardu dla słów 32 i 64 bitowych.

Rys. 3. Rozmieszczenie na osi liczbowej liczb reprezentowanych w zapisie zmiennoprzecinkowym. Warto zwrócić uwagę na charakterystyczne nierównomierne rozmieszczenie reprezentowanych liczb. Odległość sąsiednich liczb jest najmniejsza w pobliżu zera i rośnie wraz ze wzrostem odległości od 0.

Rys. 4. Format standardu zapisu zmiennoprzecinkowego IEEE 754 dla pojedynczej precyzji (ang. single precision). Słowa są 32 bitowe.

Rys. 5. Format standardu zapisu zmiennoprzecinkowego IEEE 754 dla podwójnej precyzji (ang. double precision). Słowa są 64 bitowe.

Część ułamkowa f zgodnie uwagą o postaci znormalizowanej pozbawiona jest w standardzie IEEE 754 pierwszej 1. Chcąc więc odtworzyć rzeczywistą wartość f należy poprzedzić ciąg bitów f jedynką i odczytać (1.f) jako liczbę zapisaną w zapisie stałoprzecinkowym bez znaku.

Z kolei bit znaku s=1 oznacza liczb ujemną a równy 0 liczbę dodatnią.

W dosyć specjalnym kodzie zapisany jest wykładnik e jest to mianowicie tzw. obciążony kod NKB. W ogólnym przypadku dla słowa n bitowego jest to kod numeryczny przyporządkowujący liczbom całkowitym ze zbioru <−2n−1+1,2n−1> kolejne słowa kodowe kodu n bitowego NKB. W standardzie IEEE 754 dla słowa 32 bitowego na zapamiętanie e mamy 8 bitów więc stosujemy do kodowania e obciążony 8 bitowy kod NKB.

2.12. Kody BCD

Ogólnie rzecz biorąc, kod BCD (od ang. Binary Coded Decimal czyli cyfra dziesiętna zakodowana binarnie) to każde odwzorowanie k: {0,1,...9}→{01}n. Zbiór obiektów kodowanych jest więc zbiorem V1 = {0,1,...9} cyfr dziesiętnych. Kod BCD można traktować jako kod alfanumeryczny lub kod liczbowy. Kody BCD nazywa się również w literaturze polskiej kodami dwójkowo-dziesiętnymi.

Ponieważ zależy nam na tym, żeby kod był możliwie krótki, kody BCD są z reguły funkcjami k: {0,1,...9}→{01}n.. Oczywiście nie możemy przyjąć tu krótszych słów kodowych niż 4 bitowe (bo funkcja k nie będzie różnowartościowa) tak więc n ≥ 4.

Wszystkich możliwych kodów 4 bitowych BCD jest bardzo dużo bo tyle ile 10 elementowych wariacji bez powtórzeń ze zbioru 16 elementowego czyli

\frac{16!}{(16-10)!} = \frac{16!}{6!}

a różnych kodów BCD n bitowych dla ≥ 4 mamy tyle ile 10 elementowych wariacji bez powtórzeń ze zbioru 2n elementowego czyli 

\frac{2^n!}{(2^n - 10)!}

Kodów BCD używanych w praktyce jest jednak zaledwie kilka

  • kod BCD 8421 – kod naturalny, najbardziej popularny kod BCD.
  • kod BCD 2421 tzw. kod Aikena (kod 4 bitowy)
  • kod z nadmiarem 3 tzw. kod excess 3 (kod 4 bitowy)
  • kod 2 z 5 (kod 5 bitowy)
  • kod 1 z 10 (kod 10 bitowy)
  • kod Graya z nadmiarem 3 (kod 4 bitowy)
  • kod wskaźników cyfrowych 7 –mio segmentowych (kod 7 bitowy)

Kod BCD 8421 jest następujący

0 → 0000
1 → 0001
2 → 0010
3 → 0011
4 → 0100
5 → 0101
6 → 0110
7 → 0111
8 → 1000
9 → 1001

Czterobitowy kod BCD w naturalny sposób umożliwia zapisanie 2 słów kodowych BCD na jednym bajcie. Taką sytuację nazywamy spakowanym kodem BCD. Czasem jednak wygodnie jest w jednym bajcie mieć tylko jedno słowo kodowe BCD na mniej znaczących bitach. Taką sytuację nazywamy nie spakowanym kodem BCD. Kod BCD 8421 w postaci nie spakowanej ma tę zaletę, że słowa kodowe dają się łatwo i w naturalny sposób zastąpić odpowiednimi słowami kodowymi kodu alfanumerycznego ASCII.

Zapiszmy w postaci spakowanej w jednym bajcie w kodzie BCD 8421 liczbę 45. Odszukujemy w tabelce definiującej kod BCD 8421 słowa kodowe odpowiadające liczbie 4 i liczbie 5, zestawiamy te słowa razem i dostajemy: 01000101 

Korekcja wyniku dodawania 2 cyfr zapisanych w kodzie BCD 8421. Sumator mikroprocesora z reguły nie ma specjalnego sumatora sumującego w kodzie BCD i używa do sumowania zwykłego sumatora sumującego w kodzie NKB. Dlatego też wynik dodawania 2 cyfr (zwykłego dodawania 4 bitowych słów binarnych) nie musi być wynikiem prawidłowym. Jeśli uzyskany wynik jest mniejszy równy 9 (w kodzie NKB) wynik jest prawidłowy. Jeśli jest większy od 9 lub pojawiło się przeniesienie na pozycję 4-tą (przy numeracji bitów od 0). wynik należy skorygować. Korekcja polega na dodaniu do 4 bitowego wyniku liczby 6.

Kody BCD używane są z reguły połączeniu z innymi kodami numerycznymi np. BCD i naturalny kod dziesiętny. Algorytm dodawania tak zapisanych liczb dziesiętnych kodowanych kodem BCD 8421 polega na kolejnym dodawaniu tetrad (tetrada to słowo 4 bitowe) z ewentualną korekcją wyniku.

W systemach mikroprocesorowych korekcję wyniku przy operacjach w kodzie BCD 8421 realizuje specjalny rozkaz zwykle o mnemonice (czyli symbolicznym oznaczeniu rozkazu) DAA (Decimal Addition Adjust).

W kodzie BCD 8421 korekcja może być „łańcuchowa” w tym sensie, że korekcja na najmniej znaczącej tetradzie może powodować korekcję w następnej, i tak dalej. Ale o potrzebie korekcji na danej pozycji dowiemy się po skorygowaniu poprzedniej. Czas ustalania poprawnej wartości wyniku, przy dodawaniu z użyciem tego kodu, może być więc długi.

Kod BCD excess 3. Kod excess 3 uzyskujemy dodając arytmetycznie 3 (czyli 0011) do słów kodu BCD 8421. Kod BCD excess 3 jest następujący

0 → 0011
1 → 0100
2 → 0101
3 → 0110
4 → 0111
5 → 1000
6 → 1001
7 → 1010
8 → 1011
9 → 1100

Algorytm korekcji wyniku dodawania dla kodu excess 3 jest następujący

  • Jeśli nie pojawiło się przeniesienie z danej pozycji, to odejmij 3 od wyniku.
  • Jeśli pojawiło się przeniesienie z danej pozycji, to dodaj 3 do wyniku.

Algorytm korekcji jest więc sterowany jest przeniesieniem, jest to proste i wygodne, (tzn. potrzeba korekcji sygnalizowana jest przez przeniesienie). Korekcja przeprowadzana jest „jakby na boku”. Maksymalne opóźnienie tego algorytmu może być znacznie mniejsze niż w przypadku algorytmu dla kodu BCD 8421. Uzasadnienie opisanego algorytmu korekcji pozostawiamy Czytelnikowi.

Kod BCD excess 3 to tzw. kod samouzupełniający się. Jest to bardzo użyteczna własność polegająca na tym, że negacja bitów słowa kodowego daje uzupełnienie do 9.

Kod 2 z 5 . Kod 2 z 5 (należy do grupy kodów BCD i kodów o stałej liczbie jedynek w słowie kodowym tzw. kodów m z n). Słowo kodowe dla kodu 2 z 5 ma długość 5. Zbiór obiektów kodowanych V1 = {0,1,...9} czyli jest taki jak w każdym kodzie BCD. Alfabet kodu V= {0,1} :

\left.\begin{array}{}{0 → 00011\\ 1 →00101\\ 2 → 01001\\ 3 → 10001} \end{array}\right\}  4 słowa z 1 na pozycji 5

\left.\begin{array}{}{4 → 00110\\ 5 →01010\\ 6 → 10010} \end{array}\right\}  3 słowa z 1 na pozycji 4 i 0 na pozycji 5

\left.\begin{array}{}{7 → 01100\\ 8 →10100} \end{array}\right\}  2 słowa z 1 na pozycji 3 i 0 na pozycji 5 i 4

\left.\begin{array}{}{9 → 11000} \end{array}\right\}  1 słowo z 1 na pozycji 2 i 0 na pozycji 5,4 i 3

Cechy charakterystyczne kodu 2 z 5:

  • długość podstawowego słowa kodowego jest zawsze równa 5
  • mamy w słowie kodowym stałą równą 2, liczbie jedynek,
  • kod 2 z 5 może być traktowany jako alfanumeryczny, jeśli 0,1,...,9 uważamy za symbole lub jako liczbowy, jeśli 0,1,...,9 uważamy za liczby;
  • jako kod numeryczny, kod 2 z 5 jest kodem niewagowym;
  • 16
  • kod 2 z 5 jest kodem redundancyjnym.

Kod 1 z 10. Dla takiego kodu mamy jak dla każdego kodu BCD V1 = {0,1,2,3,...,8,9}

0→1000000000
1→0100000000
2→0010000000
3→0001000000
4→0000100000
5→0000010000
6→0000001000
7→0000000100
8→0000000010
9→0000000001

Zasada tworzenia kolejnych słów kodowych jest przesuwająca się jedynka od lewej strony do prawej. Kod 1 z 10 jest oczywiście przykładem redundancyjnego kodu BCD.

2.13. Kody Graya

Kody Graya (w najprostszym ujęciu) to kody numeryczne, które liczbom całkowitym ze zbioru <0,2n−1> (gdzie n ≥ 1) przyporządkowują słowa kodowe n bitowe w taki sposób, że sąsiednie liczby (tzn. różniące się o 1 lub 2n−1) mają słowa kodowe różniące się tylko na jednej pozycji. Oznacza to, że odległość Hamminga słów kodowych sąsiednich liczb jest równa 1. Można pokazać, że kod Graya jest kodem niewagowym.

Poniżej podany jest przykład 4 bitowego kodu Graya kodującego liczby ze zbioru <0,15>

0 → 0000
1 → 0001
2 → 0011
3 → 0010
4 → 0110
5 → 0111
6 → 0101
7 → 0100
8 → 1100
9 → 1101
10 → 1111
11 → 1110
12 → 1010
13 → 1011
14 → 1001
15 → 1000

Sposób tworzenia tego kodu dla zbioru <0,2n+1−1>  jeśli już mamy kod Graya dla zbioru< wyjaśnia Rys. 6. Opisany na Rys. 6. mechanizm umożliwia zdefiniowanie kodu Graya dla dowolnego zbioru liczb postaci <0,2n+1−1>. Definicja ma charakter indukcyjny. Punktem wyjścia jest oczywiście stwierdzenie, że kod dla zbioru liczb <0,1> = {0,1} zdefiniowany jako funkcja realizująca przyporządkowanie 0 → 0, 1 → 1 jest kodem Graya.

Rys. 6. Sposób tworzenia kodu Graya dla zbioru liczb <0,2n+1−1> jeśli mamy kod Graya dla zbioru liczb .<0,2n−1>.

Kody Graya stosowane są w urządzeniach do cyfrowego pomiaru położenia w urządzeniach mechanicznych np. w tarczach kodowych do pomiaru kąta określającego położenie wału obrotowego.

Uogólnieniem koncepcji kodów Graya są kody Graya na grafach. Chodzi w nich o takie przyporządkowanie słów kodowych wierzchołkom grafu by połączone gałęzią wierzchołki miały słowa kodowe różniące się tylko na jednej pozycji.

2.14. Kody m z n

Kody m z n są kodami numerycznymi binarnymi o stałej długości n charakteryzujące się stałą liczbą m jedynek na n pozycjach w słowie kodowym. Przy ustalonym m i n liczba takich słów kodowych jest równa \left(\! \begin{array}{c} n \\ m \end{array} \!\right) = \frac{n!}{m!(n-m)!}. Zaletą takich kodów jest stosunkowo łatwa kontrola poprawności słowa kodowego.

Do najprostszych kodów m z n należy kod 1 z n. W rozdziale poświęconym kodom BCD poznaliśmy również kod 2 z 5 oraz kod 1 z 10.

Kod 1 z n. Zbiór obiektów kodowanych to V1 = {0,1,...,<em>n</em>−1}. Alfabet kodu V2 = {0,1}. Kod 1 z n to odwzorowanie różnowartościowe V1Vn2  zadane wzorem:

V_1 \ni m \to 
\underset{n}{
\underbrace
{
00...0 
\underset{m+1}{\underbrace{ 1 } 
} 
0...00} }
\in \text{{0,1}}^n

Czyli m kodujemy za pomocą pojedynczej jedynki umieszczonej na pozycji m + 1 licząc od lewej strony.

Kod 1 z 8 . Dla takiego kodu mamy V1 = {0,1,2,...,7}

0 → 100000000
1 → 01000000
2 → 00100000
3 → 00010000
4 → 00001000
5 → 00000100
6 → 00000010
7 → 00000001

Zasada tworzenia kolejnych słów kodowych to przesuwająca się jedynka od lewej strony do prawej.

Kod 1 z n bywa definiowany bez 0 w zbiorze V1, tzn. zbiór obiektów kodowanych ma postać V= {1,2,...,<em>n</em>}
Kod 1 z n jest kodem numerycznym, redundancyjnym o stałej długości słowa równej n choć może być traktowany jako kod alfanumeryczny jeśli traktujemy elementy zbioru V1 jako cyfry.

2.15. Zapisy resztowe

Zapis resztowy to inaczej zapis rezidualny lub zapis RNS (od ang. Residue Number System). Koncepcja zapisu rezidualnego (lub nieco ogólniej arytmetyki ) pochodzi od A.Svobody i M.Valacha i została podana w pracy zamieszczonej w czeskim czasopiśmie ”Stroje na Zapracovani Informaci” w roku 1955. Niezależnie od A.Svobody i M.Valacha pomysł zapisu rezidualnego podał w 1959 r. H.L.Garner w pracy opublikowanej w IRE Transactions EC-8.

Zapis resztowy umożliwia realizację szybkiej arytmetyki (dokładniej: dodawania, odejmowania i mnożenia) bez propagacji przeniesień. Mnożenia które są dosyć złożonymi działaniami (a np. w cyfrowym przetwarzaniu sygnałów mnożeń wykonujemy bardzo dużo) można jak zobaczymy wykonywać bardzo szybko i prosto. Skomplikowane stają się natomiast w zapisie RNS algorytmy dzielenia i porównania liczb.

Symbolem [x]_m lub x (mod m) będziemy w dalszym ciągu oznaczać resztę z dzielenia liczby xZ przez liczbę mN, mN, m ≥ 2. Potrzebne nam będzie ponadto pojęcie kongruencji. Niech mN, m ≥ 2, mówimy, że dwie liczby a, bZ przystają modulo m jeśli dają tę samą resztę z dzielenia przez m. Zapisujemy to pisząc ≡ b (mod m). Zapis taki nazywamy kongruencją modulo m.

Podstawą zapisu resztowego jest następujące chińskie twierdzenie o resztach.

Twierdzenie (chińskie twierdzenie o resztach dla pierścienia liczb całkowitych Z)
Niech liczby naturalne m1,m2,...mn większe od jedności będą parami względnie pierwsze (tzn. dla każdego  ij mamy NWD(mi,mj) = 1 wówczas dla dowolnych n liczb całkowitych x1,x2,...xn ∈ Z układ kongruencji liniowych

x x1 (mod mi)

x x2 (mod m2)\qquad(1)

...

x xi (mod mn)

ma dokładnie jedno rozwiązanie x w zbiorze <a,a + M − 1>, gdzie aZ, M = m1m1⋅...mn. Rozwiązanie to jest dane wzorem

x = a + \left[ \displaystyle\sum^n_{i=1}k_ix_i \right]_M\qquad(2)

gdzie ki dla = 1,2,3,...n są liczbami całkowitymi spełniającymi warunek

ki ≡ i (mod mi) dla i = 1,2,3,...,n

(3)

ki ≡ 0 (mod mj) dla ij

Dowód: Możemy znaleźć n liczb c1c2,...cn takich, że ciZ{_m}_{_i}oraz c\otimes _{m_i} \left[ \frac{M}{m_i} \right]_{m_{i}} = 1 (lub równoważnie cjest elementem odwrotnym do elementu \left[ \frac{M}{m_i} \right]_{m_i} w pierścieniu Z_{m_{i}} czyli c\left[ \frac{M}{m_i} \right]^{-i}_{m_{i}}). Łatwo sprawdzić, że ki = ci \frac{M}{m_i} = 1,2,3,...,n spełniają warunek (**). Wynika to z tego, że NWD(m_i,\left[ \frac{M}{m_i} \right]_{m_i}) = 1.

Oczywiście rozwiązaniem układu kongruencji (*) będzie również każda taka liczba całkowita y dla której mamy xy (mod m1m2...mn) zbiór {x + <em>kM</em>; <em>k</em> ∈ Z} jest jednocześnie zbiorem wszystkich rozwiązań układu kongruencji (*).
Łatwo można sprawdzić, że \displaystyle\sum^{n}_{i=1}k_i ≡ 1 (mod M)

Niech będzie dana liczba xZ i niech liczby naturalne m1,m2,...mn większe od jedności będą parami względnie pierwsze. Zapisem resztowym liczby x nazywamy ciąg skończony

([x]_{m_1},[x]_{m_2},...x]_{m_n})\qquad(4)

W ten sposób definiujemy kod numeryczny f:  → Z{_{m_1}} \times Z{_{m_2}} \times ... \times Z{_{m_n}}. Zakres tak zdefiniowanego zapisu RNS jest oczywiście równy .

Wybieramy moduły takie: m1 = 3, m2 = 5, m3 = 7, m4 = 8. Możemy wówczas reprezentować 840 liczb (ponieważ m1m2m3m4 = 840). Wszystkie 4 współrzędne dają się zapisać za pomocą 3 bitowego słowa co daje w sumie 11 bitów. Widać pewną redundancję w stosunku do naturalnego zapisu binarnego NKB (10 bitów)

Dodawanie, odejmowanie i mnożenie liczb w zapisie resztowym jest wykonywane po współrzędnych. Dokładniej, jeśli mamy 2 liczby zapisane w zapisie resztowym a = ([a]_{m_1},[a]_{m_2},[a]_{m_n})  = (a_1, a_2,..., a_n)b = ([b]_{m_1}, [b]_{m_2},..., [b]_{m_n} = (b_1, b_2, ..., b_n) to wówczas (jeśli wynik działania należy do zakresu zapisu) algorytmy dodawania, odejmowania i mnożenia dane są wzorami

a+b=(b_1,b_2,...b_n) + (b_1, b_2, ...,b_n) = (a_1\oplus_{m_1}b_1, a_2\oplus_{m_2}b_2, ... a_n\oplus_{m_n}b_n)\qquad(5)

a-b=(b_1,b_2,...b_n) - (b_1, b_2, ...,b_n) = (a_1-_{m_1}b_1, a_2-_{m_2}b_2, ... a_n-_{m_n}b_n)\qquad(6)

a \cdot b=(b_1,b_2,...b_n) \cdot (b_1, b_2, ...,b_n) = (a_1\oplus_{m_1}b_1, a_2\oplus_{m_2}b_2, ... a_n\oplus_{m_n}b_n)\qquad(7)

gdzie a_i \oplus _{m_i} b_ia_i - _{m_i} b_ia_i \otimes _{m_i} b_i są odpowiednio dodawaniem, odejmowaniem i mnożeniem modulo m_i.


Przyjmijmy m1 = 3 m2 = 5 m3 = 7 i niech x = 3, y = 6. Stosując zapis resztowy mamy x = (0,3,3,0), y = (0,1,6).


Dodając liczby x = (0,3,3,0), y = (0,1,6) w zapisie resztowym według zaproponowanego wyżej algorytmu mamy

x + y = (0,3,3,0) + (0,1,6) = (0 \oplus _{m_1} 0,3 \oplus _{m_2}1,3 \oplus _{m_3}6 )= (0,4,2) 

Zauważmy, że x + y = 9 i zapisując 9 w zapisie resztowym mamy (0,4,2) a więc z zaproponowanego algorytmu otrzymaliśmy wynik prawidłowy.

Odejmując liczby y = (0,1,6) i x = (0,3,3), w zapisie resztowym według zaproponowanego wyżej algorytmu mamy

 y − = (0,1,6) − (0,3,3,0) = (0-_{m_1}0, 1-_{m_2}3, 6-_{m_n}3) = (0,3,3)

Zauważmy, że y − = 3 i zapisując 3 w zapisie resztowym mamy (0,3,3) a więc z zaproponowanego algorytmu otrzymaliśmy wynik prawidłowy.


Mnożąc liczby x = (0,3,3,0), y = (0,1,6) w zapisie resztowym według zaproponowanego wyżej algorytmu mamy

 y ⋅ = (0,1,6) ⋅ (0,3,3,0) = (0 \otimes _{m_1}0, 1 \otimes _{m_2}3, 6\otimes _{m_n}3) = (0,3,4)

 

Zauważmy, że y ⋅ = 18 i zapisując 18 w zapisie resztowym mamy (0,3,4) a więc z zaproponowanego algorytmu otrzymaliśmy wynik prawidłowy.

Konwersja zapis NKB liczby x → na zapis RNS liczby x sprowadza się do obliczenia n reszt z dzielenia liczby x przez moduły m1, m2, m3, ..., mn.

Konwersja zapis RNS →  zapis NKB jest nieco bardziej złożona. Podamy dwa algorytmy konwersji.

Podstawowy algorytm konwersji jest na ogół formułowany w samym chińskim twierdzeniu o resztach. Przypomnijmy algorytm:

Niech a1, a2, ..., an będzie zapisem RNS liczby x oraz Mm1, m2, ..., mn.

I. Znajdujemy liczby bj ∈ Z takie, że:

bj ⋅ \frac{M}{m_j} = 1 (mod m_j) dla j = 1,2,...n).\qquad(8)

II. Jeśli x ∈ [0, M], to

x = [a1 ⋅b1 ⋅ \frac{M}{m_1} + a2 ⋅b2 ⋅ \frac{M}{m_2} + ... +  an ⋅bn\frac{M}{m_n}]M,\qquad(9)

a ogólnie, jeśli x ∈ [a, a + m), to

x = a + (a1 ⋅b1 ⋅ \frac{M}{m_1} + a2 ⋅b2 ⋅ \frac{M}{m_2} + ... +  an ⋅b− a)(mod M).\qquad(10)

Niech m1 = 3, m2 = 5, m3 = 7 oraz niech x = (0,3,3). Wyznaczamy stałe b1, b2, b3, spełniające warunek (8) otrzymując b1 = 1, b2 = 1, b3 = 1. Korzystając ze wzoru (9) otrzymujemy x = 3.

Konwersja wykorzystująca „mixed radix representation” (algorytm Garnera).

I. Niech (a1, a2, a3) będzie zapisem RNS liczby x. Definiujemy  \binom{n}{2} stałych cij (liczymy je tylko jeden raz – wstępnie).

cij mi ≡ 1 (mod mj) dla  1 ≤ i < j ≤ n,\qquad(11)

lub co na jedno wychodzi, poszukujemy odwrotności elementu [m_i]_{m_j} w pierścieniu Z_{m_j}, czyli takiego cij, że cij \otimes_{m_j} mj = 1 w pierścieniu Z_{m_j}.

II. Obliczamy ciąg v1, v2,...vn gdzie v1 ∈ [0, mi, −1) będący zapisem mixed radix szukanej liczby x,

v1 := a1 (mod m1)

v2 := (a− v1)c12(mod m2)

v3 := ((a− v1)c13v1)c23(mod m3)\qquad(12)
.
.
.
vr := ((u− v1)c1− v2)c2− ...− vr−1)cr−1,n (mod mn)

III. Obliczamy

x = vn ⋅mn−1 ⋅ ...⋅ m1  + vn−1 ⋅mn−2⋅ ...⋅m1  +...+ v3 ⋅m2 ⋅m1 + v2 ⋅m1 ⋅v1 \qquad(13)

Łatwo zauważyć, że w zapisie RNS złożoność konwersji z RNS → zapis binarny; zapis binarny RNS w pewnym stopniu zależy od wyboru modułów m1,m2,m3,...mn.

Dla modułów postaci 2− 1 konwersja liczby x na jej zapis RNS jest bardzo prosta.

Ogólnie rzecz biorąc, jeśli mamy liczbę Z otrzymujemy jej reprezentację resztową przez dzielenie x przez kolejne moduły mi, co daje ciąg (a1,a2,...ar). Jednak dla modułów postaci 2ei − 1, można zrobić tak:

x = atAt + at−1At−1 +...+ a1A + a0\qquad(14)

gdzie = 2ei i 0 ≤ ak < 2ej i dla każdego k = 0,1,...t.

Zatem:

u = a1at −1 + ... + a1a(mod 2e− 1)\qquad(15)

ponieważ ≡ (mod 2e− 1) i ostatecznie:

ui = a1 \oplus at −1 \oplus...\oplus a10\qquad(16)

ui = atat −1 + ... + a(mod (2e− 1))\qquad(17)

Zajmijmy się modułami postaci mj = 2e− 1, gdzie ejNej ≥ 2, dokładniej. Pokażemy jakie korzyści wynikają z wyboru modułów m1,m2,...mtak, by było mj = 2e− 1, ejNej ≥ 2. Zależy nam na tym, żeby były to moduły parami względnie pierwsze. Następujące twierdzenie stanowi kryterium na to, by dwie liczby postaci 2^f− 1 i 2e − 1 były względnie pierwsze.

Dla e,fN mamy NWD(2e − 1),(2^f− 1) = 2NWD(e, ^f− 1.

Dowód: 1. Zauważmy, że zachodzi równość:

((2e − 1)(mod 2^f− 1)) = 2e, (mod ^f− 1\qquad(18)

Jeśli e ≤ f, to jest to oczywiste. Niech e>f. Równość (18) jest równoważna do kongruencji

(2e − 1) ≡ 2e (mod ^f− 1 (mod (2^f− 1))\qquad(19)

i dalej

2e ≡ 2e mod ^f (mod (2^f− 1))\qquad(20)

2e(mod ^f) 2^f...q^f ≡ 2e mod ^f (mod (2^f− 1))\qquad(21)

ale: 2^f ≡ 1 (mod (2^f− 1)) zatem prawdziwa jest równość (18).

2. Wracamy do dowodzonej równości. Niech e>f (dla e=f równość jest oczywista) wówczas na mocy (18) i algorytmu Euklidesa NWD(2e − 1,2^f− 1) = NWD(2^f− 1,2e mod ^f − 1).

Kolejno stosujemy wzór (18), czyli obliczamy resztę z dzielenia przez liczbę mniejszą, zauważmy przy tym, że NWD(e, f) = NWD(f,e (mod f)).

Postępując zgodnie z algorytmem Euklidesa otrzymamy wreszcie, w kolejnym kroku, iloraz reszt =0.

Będziemy więc mieli: NWD(2e − 1,2^f− 1) = NWD(2a− 1,2− 1) = 2a − 1

oraz  NWD(e, f) = NWD(a0) = a

Wniosek: Liczby 2e − 1 i 2^f − 1 są względnie pierwsze wtedy i tylko wtedy, gdy e i f są względnie pierwsze.

Możemy wybrać jako moduły np.: m1 = 235 − 1, m2 = 234 − 1, m3 = 233 − 1, m4 = 231 − 1, m5 = 229 − 1. Jest to bardzo wygodne jeśli mamy słowo maszynowe 35 bitowe. Możemy w tej sytuacji reprezentować liczby z zakresu od 0 do m1 ⋅ m2 ⋅ m3 ⋅ ... ⋅  m5 > 2161. 

Słowo maszynowe k-bitowe, ogólnie rzecz biorąc może być jednak takie, że 2k > mi lub 2kmi . Jeśli moduły dają się zapisać w kodzie NKB na słowie maszynowym to jest to oczywiście wygodne, ale nie jest to niezbędne.

Zasadnicze znaczenie dla szybkości wykonywania działań arytmetycznych w zapisie resztowym mają sposoby wykonywania działań modulo m. Działania modulo m powinny być realizowane za pomocą odpowiednio zaprojektowanych układów.

Uogólnieniem zapisu RNS na liczby zespolone jest zapis resztowy QRNS (Quadratic Residue Numer System)

Istotę zapisu resztowego w języku algebry można wyrazić tak. Pierścień ilorazowy Z / (m1m2...mnZ) i i suma prosta pierścieni ilorazowych (Z / m1Z) ⊕ (Z / m2Z) ⊕ ...⊕ (Z / mnZ) są izomorficzne tzn.

(Z / m1m2...mnZ) ≅ (Z / m1Z) ⊕ (Z / m2Z) ⊕ ...⊕ (Z / mnZ)

i izomorfizm zadany jest wzorem γ(x) = ([x]_{m_1},[x]_{m_2},...,[x]_{m_n}). Jest to nieco inne sformułowanie chińskiego twierdzenia o resztach.

2.16. Kody wagowe ze zmienną wagą

Kody wagowe ze zmienną wagą lub zapisy ze zmienną podstawą (ang Mixed Radix Notation lub Mixed Radix Representation) to kody liczbowe, zasadniczo służące do reprezentowania (czyli zapisu) liczb ze zbioru ∪ {0}.

Przypomnijmy podstawowe twierdzenie umożliwiające konstruowanie kodów wagowych. Jest to jednocześnie zasadnicze twierdzenie, na którym opiera się definicja kodów wagowych ze zmienną wagą.

Twierdzenie: Dla dowolnej liczby mN i ustalonego ciągu (mi)i=0, gdzie m∈ {2,3...} istnieje dokładnie jeden ciąg skończony ak,ak-1,...,a1, a0 taki, że

1) dla każdego i = 0,1,...ka∈ {0,1...<em>m<sub>i </sub></em>−1} , ak ≠ 0

2) m = akmk−1mk−2 ... m0 + ak−1mk−2mk−3... m0 + a2m1m0 + a1ma0

Oznaczmy Ai = {0,1...<em>m<sub>i </sub></em>−1} dla i = 0,1,2 i niech A = \displaystyle\bigcup^\infty_{i=0} Ai oraz

B = A∪ A× A∪ A× A× A∪... ∪ A× A× A× ... × Ak ∪ ...

Odwzorowanie K: ∪ {0} ∋ m → K(m) = akak−1...a1aB (gdzie a ∈ Ai) określone na zbiorze ∪ {0} i zdefiniowane w powyższym twierdzeniu nazywamy kodem wagowym ze zmienną wagą lub zapisem wagowym ze zmienną wagą (ang. mixed radix notation) przy czym przyjmujemy K(0) = 0. Ta przejrzysta i prosta definicja wymaga jednak kilku uwag. 

Elementy zbioru A = \displaystyle\bigcup^\infty_{i=0} Ai nazywamy cyframi i często utożsamiamy je, czy wiążemy z pewnymi symbolami np. pisanymi na papierze. Raz więc możemy na element ze zbioru A = \displaystyle\bigcup^\infty_{i=0} Ai patrzeć jak na liczbę raz jak na symbol. Jest to w praktyce dosyć wygodne. Oczywiście byłoby lepiej gdyby A = \displaystyle\bigcup^\infty_{i=0} Ai było zbiorem skończonym. Posługiwanie się nieskończonym zbiorem symboli jest bowiem wysoce niepraktyczne.

Zauważmy, że jeśli ciąg (mi)i=0 jest ograniczony to A = \displaystyle\bigcup^\infty_{i=0} Ai jest zbiorem skończonym a więc alfabetem. Ponadto istnieje takie k, że mi ≤ mk dla każdego i = 0,1,2... oraz AiAk = A dla każdego = 0,1,2.... Istnieje więc taki alfabet A, że odwzorowanie K przyjmuje wartości z A* a więc kod wagowy ze zmienną wagą jest kodem w sensie przyjętej definicji kodu.

Jeśli ciąg (mi)i=0  nie jest ograniczony to  A = \displaystyle\bigcup^\infty_{i=0} Ai = ∪ {0} a więc A nie jest zbiorem skończonym i formalnie rzecz biorąc zapis wagowy K ze zmienną wagą nie jest kodem w sensie przyjętej definicji. Jeśli jednak użyć jakiegokolwiek pomocniczego kodu numerycznego K'∪ {0} → V* do zapisywania liczb z ze zbioru A = \displaystyle\bigcup^\infty_{i=0} Ai = ∪ {0}, gdzie V jest jakimkolwiek alfabetem (np. K'∪ {0} → V* może być zwykłym zapisem dziesiętnym znanym ze szkoły) to łatwo odwzorowanie  K'∪ {0} ∋ K(m) = akak−1...a1atak zmodyfikować by uzyskać odwzorowanie będące kodem. Wystarczy przyjąć: 

\tilde K∪ {0} ∋ → \tilde K(m) = K'(ak), K'(ak−1), ...K'(a1), K'(a0) ∈ (∪ {,})*

Niech mi = pi (mod 10), gdzie p1p2 są kolejnymi liczbami pierwszymi, wówczas 21 w zapisie ze zmienną wagą oznacza liczbę 5 (w zapisie dziesiętnym). Oczywiście alfabet  A = {0,1,2,3,4,5,6,7,8,9}
W oczywisty sposób zapisy ze zmienną wagą jest uogólnieniem naturalnych zapisów wagowych. Wystarczy przyjąć m= W dla i=1,2,...

Podobnie jak ma to miejsce w zapisie wagowym z ustaloną wagą W, możemy w zapisie ze zmienną wagą reprezentować również liczby ułamkowe „wprowadzając kropkę”.

Zapis ze zmienną wagą jest wykorzystywany w jednym z algorytmów konwersji zapisu RNS na zapis wagowy.

3. Kody alfanumeryczne

Kody alfanumeryczne

3.1. Uwagi wstępne

Jeśli zbiorem obiektów kodowanych V1 jest zbiór znaków to kod fVV*(dla ustalonego alfabetu Vna ogół binarnego) nazywamy kodem alfanumerycznym (ang. alphanumeric code).

W tym podrozdziale poznamy szereg binarnych kodów alfanumerycznych. Najpopularniejsze binarne kody alfanumeryczne to ośmiobitowy kod ASCII (American Standard Code for Information Interchange) i szesnastobitowy kod Unicode. Są to oczywiście kody o stałej długości słowa kodowego.

Znany każdemu telegrafiście i harcerzowi kod Morse'a jest również kodem alfanumerycznym binarnym ale kodem zmiennej długości. Kody alfanumeryczne ASCII zmiennej długości mają specyficzne zalety opisane bliżej w podrozdziale o kompresji danych.

Kod ASCII został wprowadzony w USA w 1963 roku jako kod 7 bitowy. Kod ASCII w swej podstawowej wersji jest więc w zasadzie 7-mio bitowym kodem ale uzupełnionym z reguły bitem parzystości. Jako kod 8 bitowy ma jednak szereg odmian tzw. wersji narodowych.

Kod ASCII jest równoważny z kodem ISO-7 (ISO to skrót od International Organization for Standardization) . Kod ISO-7 został opisany w normie ISO 646 wydanej w roku 1973. Norma ISO 646 przewiduje wprowadzenie znaków narodowych co wiąże się z rozszerzeniem kodu ASCII do kodu 8 bitowego

ISO-8859-2 to ogólnie już przyjęty standard kodowania polskich liter wg polskich norm PN stanowiący rozszerzenie ASCII.

Kod alfanumeryczny UNICODE jest standardem ISO/IEC-10646. Jest to kod 16 bitowy (pierwsza wersja) lub 31 bitowy (druga wersja). UNICODE umożliwia zapisanie wszystkich znaków z alfabetów narodowych (również cyrylicy, alfabetu chińskiego i alfabetu japońskiego)

Kod ASCII w wersji podstawowej koduje 128 znaków. Pierwsze 33 znaki (uporządkowane wg wartości w kodzie NKB słowa kodowego to tzw. znaki sterujące takie jak CR (Carriage Return czyli „powrót karetki”) lub LF (Line Feed czyli „od nowego wiersza”). Służą one do sterowania systemem drukowania lub wyświetlania znaków. Pozostałe znaki to m.in. małe i duże litery alfabetu angielskiego, cyfry, znaki przestankowe oraz kilka symboli matematycznych takich jak nawiasy i znak równości.

3.2. Kod ASCII

W technice cyfrowej i wywodzącej się z niej inżynierii komputerowej dominują obecnie kody alfanumeryczne oparte na standardzie ASCII (American Standard Code for Information Interchange), znanym także jako ANSI X3.4 oraz ISO-646-US i US-ASCII. W standardzie tym 128 podstawowym znakom, takim jak litery alfabetu łacińskiego, cyfry, znaki przestankowe oraz kody sterujące, przyporządkowano liczby z zakresu od 0 do 127. Z uwagi na oczywiste problemy z sortowaniem tak różnorodnych znaków tabele kodowe zestawia się według wartości kodów (dziesiętnych i szesnastkowych) - jak w poniższej tabeli.

Dec Hex Char
0 00 NUL null
1 01 SOH start of heading
2 02 STX start of text
3 03 ETX end of text
4 04 EOT end of transmission
5 05 ENQ enquiry
6 06 ACK acknowledge
7 07 BEL bell
8 08 BS backspace
9 09 TAB horizontal tabulation
10 0A LF line feed
11 0B VT vertical tabulation
12 0C FF form feed
13 0D CR carriage return
14 0E SO shift out
15 0F SI shift in
16 10 DLE data link escape
17 11 DC1 device control one
18 12 DC2 device control two
19 13 DC3 device control three
20 14 DC4 device control four
21 15 NAK negative acknowledge
22 16 SYN synchronous idle
23 17 ETB end of transmission block
24 18 CAN cancel
25 19 EM end of medium
26 1A SUB substitute
27 1B ESC escape
28 1C FS file separator
29 1D GS group separator
30 1E RS record separator
31 1F US unit separator
Dec Hex Char
32 20 space
33 21 !
34 22 "
35 23 #
36 24 $
37 25 %
38 26 &
39 27 '
40 28 (
41 29 )
42 2A *
43 2B +
44 2C ,
45 2D -
46 2E .
47 2F /
48 30 0
49 31 1
50 32 2
51 33 3
52 34 4
53 35 5
54 36 6
55 37 7
56 38 8
57 39 9
58 3A :
59 3B ;
60 3C <
61 3D =
62 3E >
63 3F ?
Dec Hex Char
64 40 @
65 41 A
66 42 B
67 43 C
68 44 D
69 45 E
70 46 F
71 47 G
72 48 H
73 49 I
74 4A J
75 4B K
76 4C L
77 4D M
78 4E N
79 4F O
80 50 P
81 51 Q
82 52 R
83 53 S
84 54 T
85 55 U
86 56 V
87 57 W
88 58 X
89 59 Y
90 5A Z
91 5B [
92 5C \
93 5D ]
94 5E ^
95 5F _
Dec Hex Char
96 60 `
97 61 a
98 62 b
99 63 c
100 64 d
101 65 e
102 66 f
103 67 g
104 68 h
105 69 i
106 6A j
107 6B k
108 6C l
109 6D m
110 6E n
111 6F o
112 70 p
113 71 q
114 72 r
115 73 s
116 74 t
117 75 u
118 76 v
119 77 w
120 78 x
121 79 y
122 7A z
123 7B {
124 7C |
125 7D }
126 7E ~
127 7F DEL

Tab. 1. US-ASCII

ASCII jest kodem 7-bitowym, przy czym oryginalny standard nie definiuje roli ósmego bitu, który w czysto sprzętowych realizacjach cyfrowych układów sterowania ciągle bywa wykorzystywany do kontroli parzystości lub do przechowywania dodatkowego atrybutu (np. podświetlenia). Najczęściej jednak ósmy bit służy rozszerzeniu podstawowego kodu ASCII o niezbędne znaki alfabetów narodowych, symbole matematyczne, znaki semigraficzne itp.. Niegdyś próbowano dopasować kod ASCII do warunków lokalnych poprzez odmienne wykorzystanie niektórych wartości kodów; np. w ISO-646-DE pod wartościami z zakresu 123-126 (7B-7E) kryją się odpowiednio znaki ä, ö, ü i ß. W miarę, jak rosły potrzeby, pojawiały się kolejne kody o rozmaitych sposobach i zakresach implementacji dodatkowych znaków, a wielu producentów sprzętu i oprogramowania wprowadzało własne "standardy". Z tych najbardziej znane jest rozszerzenie kodu ASCII wprowadzone przez firmę IBM, czyli CP437 (IBM PC Extended ASCII czyli DosLatinUS), które zainicjowało całą serię tzw. stron kodowych, począwszy od CP850 (DosLatin1) i CP852 (DosLatin2), obecnych w kartach graficznych, drukarkach i w MS-DOS.

Tab. 2. CP437

Tab. 3. CP850

Tab. 4. CP852

ISO 8859 to zestaw kilkunastu tabel wykorzystujących wszystkie 256 wartości 8-bitowego słowa kodowego i rozszerzających właściwą tabelę ASCII (kody od 0 do 127 pozostają bez zmian) o znaki pochodzące z poszczególnych regionów Europy, alfabet grecki, cyrylicę 3 itp. Podobnie jak w podstawowym kodzie ASCII, pierwsze 32 pozycje każdej rozszerzonej tabeli ISO 8859-n to niedrukowalne znaki sterujące. Odpowiednikiem CP850 jest ISO 8859-1, czyli Latin1, dedykowany Europie Zachodniej. Odpowiednikiem CP852 jest ISO 8859-2 (Latin2) zawierający komplet polskich znaków, a np. greckie litery można znaleźć w ISO 8859-7 (Greek)

Dec Hex Char
128 80 PAD padding character
129 81 HOP high octet preset
130 82 BPH break permitted here
131 83 NBH no break here
132 84 IND index
133 85 NEL next line
134 86 SSA start of selected area
135 87 ESA end of selected area
136 88 HTS character tabulation set
137 89 HTJ character tabulation with justification
138 8A VTS line tabulation set
139 8B PLD partial line forward
140 8C PLU partial line backward
141 8D RI reverse line feed
142 8E SS2 single-shift two
143 8F SS3 single-shift three
144 90 DCS device control string
145 91 PU1 private use one
146 92 PU2 private use two
147 93 STS set transmit state
148 94 CCH cancel character
149 95 MW message waiting
150 96 SPA start of guarded area
151 97 EPA end of guarded area
152 98 SOS start of string
153 99 SGCI single graphic character introducer
154 9A SCI single character introducer
155 9B CSI control sequence introducer
156 9C ST string terminator
157 9D OSC operating system command
158 9E PM privacy message
159 9F APC application program command
Dec Hex Char
160 A0  
161 A1 Ą
162 A2 ˘
163 A3 Ł
164 A4 ¤
165 A5 Ľ
166 A6 Ś
167 A7 §
168 A8 ¨
169 A9 Š
170 AA Ş
171 AB Ť
172 AC Ź
173 AD soft hyphen
174 AE Ž
175 AF Ż
176 B0 °
177 B1 ą
178 B2 ˛
179 B3 ł
180 B4 ´
181 B5 ľ
182 B6 ś
183 B7 ˇ
184 B8 ¸
185 B9 š
186 BA ş
187 BB ť
188 BC ź
189 BD ˝
190 BE ž
191 BF ż
Dec Hex Char
192 C0 Ŕ
193 C1 Á
194 C2 Â
195 C3 Ă
196 C4 Ä
197 C5 Ĺ
198 C6 Ć
199 C7 Ç
200 C8 Č
201 C9 É
202 CA Ę
203 CB Ë
204 CC Ě
205 CD Í
206 CE Î
207 CF Ď
208 D0 Đ
209 D1 Ń
210 D2 Ň
211 D3 Ó
212 D4 Ô
213 D5 Ő
214 D6 Ö
215 D7 ×
216 D8 Ř
217 D9 Ů
218 DA Ú
219 DB Ű
220 DC Ü
221 DD Ý
222 DE Ţ
223 DF ß
Dec Hex Char
224 E0 ŕ
225 E1 á
226 E2 â
227 E3 ă
228 E4 ä
229 E5 ĺ
230 E6 ć
231 E7 ç
232 E8 č
233 E9 é
234 EA ę
235 EB ë
236 EC ě
237 ED í
238 EE î
239 EF ď
240 F0 đ
241 F1 ń
242 F2 ň
243 F3 ó
244 F4 ô
245 F5 ő
246 F6 ö
247 F7 ÷
248 F8 ř
249 F9 ů
250 FA ú
251 FB ű
252 FC ü
253 FD ý
254 FE ţ
255 FF ˙

Tab. 5. ISO 8859-2 (Latin2)

Wprowadzenie standardu ISO 8859 miało w założeniu uporządkować zasady kodowania rozszerzeń ASCII. Niestety, nie tylko nie zapobiegło definitywnie dalszemu mnożeniu się stosowanych kodów alfanumerycznych, lecz wręcz zapoczątkowało kolejną ich falę, tak w ramach samego standardu (wystarczy prześledzić ewolucję obsługi języków krajów nadbałtyckich od ISO 8859-4 (Latin4) poprzez ISO 8859-10 (Latin6) do ISO 8859-13 (Latin7 Baltic Rim)), jak i incjatyw zewnętrznych (na przykład Microsoft zmodyfikował tabelę ISO 8859-2 i wprowadził ją do Windows jako stronę kodową CP1250 (WinLatin2)). Główną tego przyczyną są obiektywne ograniczenia wynikające ze zbyt małej ilości słów kodu 8-bitowego.

Tab. 6. CP1250 (WinLatin2)

3.3. Unicode

W zapisie UTF-8 każdy znak UCS/Unicode jest przedstawiany w postaci sekwencji od jednego do sześciu 8-bitowych bajtów, zależnie od wartości samego znaku. Poniższa tabela obrazuje zasadę, na jakiej to się odbywa:

 

Liczba bajtów UTF-8 1. bajt 2. bajt 3. bajt 4. bajt Liczba bitów Unicode Maksymalna wartość  
            Hex Dec
1 0xxxxxxx       7 7F 127
2 110xxxxx 10xxxxxx     11 7FF 2047
3 1110xxxx 10xxxxxx 10xxxxxx   16 FFFF 65535
4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 21 1FFFFF 2097151
5 111110xx 10xxxxxx itd.     26 3FFFFFF 67108863
6 1111110x 10xxxxxx itd.     31 7FFFFFFF 2147483647

 

Tab. 7. UTF-8

 

0xxxxxxx
10xxxxxx
110xxxxx
1110xxxx
11110xxx
111110xx
1111110x

: pierwszy i jedyny bajt sekwencji
: drugi i dalsze bajty sekwencji
 : pierwszy bajt sekwencji 2-bajtowej
: pierwszy bajt sekwencji 3-bajtowej
: pierwszy bajt sekwencji 4-bajtowej
: pierwszy bajt sekwencji 5-bajtowej
: pierwszy bajt sekwencji 6-bajtowej

Tab. 8. Znaczenie bajtu w zapisie UTF-8

W wielobajtowej sekwencji bity oznaczone 'x'-ami czytane od 1-szego, najstarszego bajtu tworzą właściwą wartość znaku UCS/Unicode. Z kolei wartość bieżącego bajtu wskazuje na jego miejsce w sekwencji UTF-8. W ten sposób zapis UTF-8 jest kompatybilny z 7-bitowym US-ASCII i zachowuje względną zwartość tekstów o niewielu znakach rozszerzonych, pozwalając jednocześnie na zapis nawet 31-bitowych wartości i na łatwą do realizacji synchronizację i interpretację przetwarzanych sekwencji.

Oto kilka przykładów 16-bitowych wartości Unicode w zapisie UTF-8:
Unicode UTF-8
Hex Hex
0001 01
007F 7F
0080 C2 80
07FF DF BF
0800 E0 A0 80
0FFF E0 BF BF
1000 E1 80 80
FFFF EF BF BF

 

Zapis UTF-8 jest nadmiarowy, bowiem np. wartościom 16-bitowym przyporządkowuje wartości 24-bitowe (3-bajtowe). W efekcie istnieje wiele ciągów bajtów, które nie są legalnymi sekwencjami UTF-8, nawet jeżeli wykonanie przekształcenia odwrotnego jest technicznie możliwe.

Ciąg bajtów C2 00 nie jest sekwencją UTF-8, ponieważ jego drugi bajt nie jest zgodny z wzorcem 10xxxxxx.
Ciąg bajtów C0 80 nie jest legalną sekwencją UTF-8, ponieważ jest dłuższy, niż trzeba: po zastosowaniu do niego maski binarnej 110xxxxx 10xxxxxx uzyskujemy wynik 00000 000000, czyli zerową wartość kodu, którą w UTF-8 zapisuje się poprawnie w jednym bajcie.
sekwencja C0 80 11000000 10000000
maska   110xxxxx 10xxxxxx
wynik 00 00000 000000

Oprócz zapisu UTF-8 spotyka się czasem UTF-7 wykorzystujący tylko 7 bitów używanego słowa oraz UTF-16 będący po prostu zapisem kolejnych kodów.

A oto reprezentacja polskich „ogonków” w Unicode:

ą 0105 Ą 0104
ć 0107 Ć 0106
ę 0119 Ę 0118
ł 0142 Ł 0141
ń 0144 Ń 0143
ó 00F3 Ó 00D3
ś 015B Ś 015A
ź 017A Ź 0179
ż 017C Ż 017B

 

Tab. 9. Polskie znaki w Unicode

 

4. Kody wykrywające i korygujące błędy

Kody wykrywające i korygujące błędy

4.1. Dlaczego stosujemy kody wykrywające i korygujące błędy

Całkowicie pewne, bezbłędne przesyłanie słów nad ustalonym alfabetem (z reguły słów binarnych) przez tzw. kanał komunikacyjny nie jest możliwe z uwagi na obecność w środowisku fizycznym szumów i różnego typu zakłóceń. Podobnie przechowywanie, magazynowanie słów w pamięci nie jest pozbawione błędów. Wie o tym dobrze każdy doświadczony (przez los) użytkownik pamięci (własnej). Kody wykrywające i korygujące błędy stosowane są po to by zminimalizować błędy w interpretacji słowa kodowego przenoszącego informację pomimo ewentualnych błędów jakie mogą się pojawić na niektórych pozycjach wewnątrz odebranego słowa kodowego. Kody potrafiące wykrywać tylko błędy nazywamy kodami wykrywającymi błędy a kody potrafiące wykrywać i korygować błędy nazywamy kodami korekcyjnymi ale często dla uproszczenia obie grupy kodów nazywamy kodami korekcyjnymi.

Uwaga: Kody korekcyjne stosowane są głównie w:

  • telekomunikacji i sieciach komputerowych
  • pamięciach półprzewodnikowych RAM
  • systemach zapisu danych na płytach CD

Rys. 1. Przesyłanie słów przez kanał komunikacyjny z zakłóceniami. Ze względu na szumy i zakłócenia musimy liczyć się z wystąpieniem błędów (przekłamań) w odebranym w odbiorniku słowie.

4.2. Wykrywanie błędów metodą kontroli parzystości

Wykrywanie błędów metodą kontroli parzystości jest najprostszą często stosowaną metodą wykrywania błędów.

Załóżmy, że w systemie cyfrowym chcemy przesyłać dane porcjami za pomocą słów n bitowych. Niech x1x2...xn ∈ {0,1}n będzie takim n bitowym słowem. Przed wysłaniem uzupełniamy to słowo jeszcze jednym bitem xn+1 tzw. bitem parzystości przy czym 

xn+1 = 1 jeśli liczba jedynek w słowie x1x2...xn ∈ {0,1}n jest nieparzysta 

xn+1 = 0 jeśli liczba jedynek w słowie x1x2...xn ∈ {0,1}n jest parzysta

Zatem słowo przesyłane x1x2...xnxn+1 zawiera zawsze parzystą liczb jedynek. Łatwo sprawdzić, że układ generujący bit parzystości jest n wejściową sumą modulo 2.

xn+1 = x1 ⊕ x2 ⊕ ... ⊕ xn

Nadajnik wysyła słowo n+1 bitowe x1x2...xnxn+1 a w odbiorniku dostajemy słowo być może z błędami (przekłamaniami) y1y2...ynyn+1 ∈ {0,1}n+1. Sprawdzamy parzystość liczby jedynek w słowie binarnym odebranym y1y2...ynyn+1. Układ sprawdzający czy liczba jedynek jest parzysta jest n+1 wejściową sumą modulo 2 (por. Rys.2). Jeśli liczba jedynek w słowie y1y2...ynyn+1 jest parzysta to na wyjściu układu mamy 0 jeśli nieparzysta to 1.

Jeśli wysłaliśmy parzystą liczbę jedynek a otrzymaliśmy w odbiorniku nieparzystą tzn., że podczas przesyłania wystąpił błąd. Sprawdzając parzystość wykryjemy każdą nieparzystą ilość błędów.

Zamiast funkcji xn+1 = x1 ⊕ x2 ⊕ ... ⊕ xmożna wykorzystać funkcję xn+1 = \overline{x_{n+1} = x_1 ⊕ x_2 ⊕ ... ⊕ x_n}, wtedy przesyłane słowo będzie zawierało nieparzystą liczbę jedynek.

Pewną modyfikacją opisanej wyżej koncepcji są kody ze stałą liczbą jedynek m w słowie kodowym o stałej długości n tzw. „kody m z n” (por. podrozdział „kody numeryczne”). Pojedyncza zmiana 0 na 1 lub odwrotnie zmienia w takich kodach „bilans” jedynek i zer” i możemy wykryć błąd.

Rys. 2. Układy sprawdzające parzystość słowa binarnego x1x2x3x4x5x6x7x8

4.3. Podstawowe twierdzenia o kodach wykrywających i korygujących błędy

Definicja (m,n) kodu i kodu liniowego. Zakładamy, że chcemy przesyłać słowa nad alfabetem GF(pk) (alfabetem jest ciało skończone mające  pelementów, gdzie p jest liczbą pierwszą w szczególności może to być ciało GF(2) = Z2 = {0,1}. Słowa przesyłane mają długość m, chcemy więc przesyłać słowa z (GF(pk))m. Koncepcja kodu wykrywającego lub korygującego błędy polega na wykorzystaniu kodu redundancyjnego fsmutnyGF(pk))→ (GF(pk))n, gdzie n > m. Taki kod nazywamy (m,n) kodem. Obiektami kodowanymi są tu słowa o długości m nad alfabetem GF(pk) a słowami kodowymi słowa z (GF(pk))m. Zbiory (GF(pk))m i (GF(pk))n są przestrzeniami liniowymi nad ciałem GF(pk). Jeśli odwzorowanie fsmutnyGF(pk))→ (GF(pk))n jest liniowe to kod nazywamy kodem liniowym

Liniowość odwzorowania oznacza, że istnieje taka macierz A o współczynnikach w ciele GF(pk) mająca n wierszy i m kolumn, że dla wektora a ∈ (GF(pk))mamy

f(a) = Aa

gdzie wektor a jest traktowany jako macierz kolumnowa.

Kod wykrywający błędy powinien zachowywać się tak, że gdy odbieramy zamiast słowa kodowego f(a) słowo z przekłamaniami na pewnej niewielkiej liczbie co najwyżej r pozycji (oznaczmy to słowo z przekłamaniami przez br(f(a))) to oglądając br(f(a)) będziemy mogli stwierdzić czy nastąpiły przekłamania.

Powiemy, że kod fsmutnyGF(pk))→ (GF(pk))wykrywa fakt popełnienia co najwyżej r błędów jeśli dla każdego a ∈ (GF(pk))zmiana cyfr na co najwyżej r pozycjach w słowie kodowym f(a) nie powoduje przejścia słowa kodowego w słowo f(b) dla pewnego b ∈ (GF(pk))m,  ab.

Praktycznie więc wykrywanie błędu może polegać na porównaniu br(f(a)) z wszystkimi możliwymi słowami kodującymi f(c) dla c ∈ (GF(pk))m. Jeśli nie stwierdzamy zgodności, to stwierdzamy, że słowo br(f(a)) zawiera przekłamanie.

Kod korygujący błędy powinien zachowywać się tak, że gdy odbieramy zamiast słowa kodowego f(a) słowo z przekłamaniami na pewnej niewielkiej liczbie co najwyżej r pozycji (tzn. słowo br(f(a))) to oglądając  br(f(a)) będziemy mogli skorygować błędy (stosując pewien algorytm) uzyskując słowo f(a) a więc w konsekwencji stwierdzamy, że zostało nadane słowo a.

Zasadnicza koncepcja na jakiej opierają się kody korekcyjne jest taka: Niech fsmutnyGF(pk))→ (GF(pk))będzie (m,n) kodem kodującym słowa o długości m za pomocą słów o długości n, gdzie nm. Zakładamy, że podczas transmisji słowa f(a) o długości m może powstać co najwyżej r błędów. Dla każdego a ∈ (GF(pk))kodujemy przy tym słowo a takim ciągiem a ∈ (GF(pk)), że

\underset{a,b \in (GF(p^k))^m, a \neq b}{\forall} = K(f(a),r) ∩ K(f(b),r) = ∅\qquad(1)

gdzie K(x,r) oznacza kulę o promieniu r w przestrzeni metrycznej GF(pk)) z metryką Hamminga dH. Warunek (1) oznacza, że każde dwie kule rodziny kul K(f(x),r);  GF(pk))są rozłączne. Jeśli tak jest to aby stwierdzić jaka informacja została nadana wystarczy zastosować do słowa odebranego bk(f(a));  (GF(pk))regułę decyzyjną d: GF(pk))nGF(pk)) zdefiniowaną wzorem

d(br(f(a))) = a 

wtedy i tylko wtedy, gdy

 dH(f(a),br,f(a)) = \underset{x \in (GF(p^k))^m}{min}{<d<sub><em>d<sub>H</sub></em>(<span class="equation">f</span>(<em>x</em>),<em>b<sub>r</sub></em>(<span class="equation">f</span>(<em>a</em>)) }\qquad(2)

Innymi słowy jako informację nadaną przyjmujemy takie a (GF ( pk ))m , że odebrane słowo br (f(a)) (GF( pk ))n jest najbliższe f(a) tzn.

\underset{a,b \in (GF(p^k))^m, a \neq b}{\forall} dH (f(a), b) dH ( f(x), b)\qquad (3)

Jest oczywiste, że powyższa reguła decyzyjna przy warunku (1) jest poprawna w tym sensie, że pozwala na odtworzenie wiadomości nadanej przy ograniczonej do r ilości błędów transmisji.

Warto zauważyć, że istotą powyższego pomysłu na kod korekcyjny jest to, że wokół każdego słowa kodującego f(a) (gdzie a (GF ( pk ))m ) tworzymy otoczenie (w metryce Hamminga), którego elementy (oprócz f(a)) nie są wykorzystywane do kodowania słów z a (GF ( pk ))m . Możemy jednak odebrać (w wyniku wprowadzenia błędów podczas przesyłania słowa f(a)) dowolny element z tego otoczenia a mimo to nie tracimy orientacji w sytuacji, potrafimy wykryć i skorygować przekłamania.

Niech będzie dany (m,n) kod f : (GF ( pk ))m (GF ( pk ))n i ustalona liczba r  0, n . Kod f wykrywa fakt popełnienia co najwyżej r błędów wtedy i tylko wtedy gdy dla każdego a, b (GF ( pk )m , a b mamy f (a) K ( f (b), r) (czyli dH ( f (a), f (b)) r ).
Kod f : (GF ( pk ))m (GF ( pk ))n koryguje popełnienia co najwyżej r błędów wtedy i tylko wtedy, gdy dla każdego a, b (GF ( pk ))m , a b mamy dH ( f (a), f (b)) 2r 1 (lub równoważnie spełniony jest warunek (1)).
Klasycznym przykładem kodu korekcyjnego jest kod s krotnie powtórzony. Powstaje on przez przyporządkowanie słowu kodowanemu a (GF ( pk ))m słowa kodowego \underset{s}{\underbrace{aa...aa}} (GF ( pk ))ms . Zdolności korekcyjne tak zdefiniowanego kodu są oczywiste. Widać s od razu, że popełnienie co najwyżej r błędów w słowie kodowym (gdzie r < \frac{s}{2} ) nie przeszkadza w poprawnym odczytaniu takiego słowa kodowego. Kod s krotnie powtórzony jest (m, s m) kodem.

Kod blokowy. Niech V będzie ustalonym alfabetem. Kodem blokowym nazywamy kod f : Vm Vn , w którym słowom o długości m (obiektom kodowanym) przyporządkowujemy słowa o długości n, gdzie n m .

(m,n) kod jest kodem blokowym.

Kod blokowy nazywamy kodem grupowym, jeśli słowa kodowe kodu tworzą grupę addytywną.

Różnych typów kodów korekcyjnych (ang. ECC od Error Correcting Codes) jest dosyć dużo. Z bardziej znanych warto wymienić:

  • kody cykliczne (kody CRC - ang. Cyclic Redundancy Check)
  • kody Hamminga
  • kody Reeda-Solomona
  • kody Bose-Chaudhuri-Hocquenghema (kody BCH)
Kodowanie stosowane w płytach kompaktowych (płytach CD) to kod Reeda-Solomona, a ściślej CIRC (CIRC - ang. cross interleaved Reed-Solomon code). 

5. Szyfry i kryptografia

Szyfry i kryptografia

5.1. Główne cele kryptografii

Ponieważ szyfr to w pewnym uproszczeniu rodzina kodów indeksowana kluczem, więc w naturalny sposób szyfry wpisują się w zagadnienia związane z kodami i kodowaniem.

Kryptologia bazuje na czterech działach matematyki: teorii liczb, algebrze, teorii algorytmów komputerowych i teorii prawdopodobieństwa. Kryptologia dzieli się na kryptografię (sztukę szyfrowania i tworzenia wyrafinowanych zabezpieczeń systemów komputerowych) oraz kryptoanalizę (sztukę łamania szyfrów i zabezpieczeń). Obecnie często nazywa się kryptologię niezbyt precyzyjnie kryptografią i takie uproszczoną nieco terminologię będziemy stosować w dalszym ciągu rozdziału.

Kryptografia to zespół metod matematycznych i technik tkwiący częściowo w szeroko pojętej elektronice a częściowo w informatyce teoretycznej.

Kryptografia oraz nieco obszerniejsza dziedzina jaką jest ochrona informacji w systemach i sieciach komputerowych odgrywają bardzo ważną rolę nie tylko we współczesnej informatyce, ale również w organizacji życia gospodarczego i administracji państwowej. Handel elektroniczny, bankowość elektroniczna, elektroniczna wymiana dokumentów (słynne EDI – Electronic Documents Interchange) czy ogólnie rzecz biorąc bezpieczne magazynowanie i przesyłanie informacji nie byłyby możliwe bez współczesnych metod kryptograficznych. Takie skróty oznaczające bezpieczne protokoły przesyłania danych jak SSL (Secure Socket Layer), SSH (Secure Shell) czy HTTPS (Secure HTTP czyli Secure HyperText Protocol) znane są każdemu użytkownikowi przeglądarki internetowej.

Główne cele kryptografii to

  • zapewnienie poufności wiadomości nazywane też utajnianiem wiadomości (ang. privacy lub confidentiality)
  • uwierzytelnianie strony czyli identyfikacja strony pragnącej uzyskać dostęp do zasobów systemu komputerowego lub sieci komputerowej (ang. entity authentication lub identification)
  • uwierzytelnianie wiadomości lub uwierzytelnianie dokumentu (ang. data origin authentication)
  • zapewnienie integralności danych (ang. data integrity)
  • niezaprzeczalność (ang. non repudiation)

5.2. System kryptograficzny

Teraz powiemy co to jest wiadomość jawna, kryptogram (albo szyfrogram) oraz szyfr.

System kryptograficzny lub szyfr to piątka uporządkowana (V1,V2 , K , E, D) , gdzie:

  • V1 jest alfabetem, w którym zapisujemy wiadomości jawne, V1  \overset{df}{=} M, czyli zbiór wszystkich słów nad alfabetem V1 jest tzw. przestrzenią wiadomości jawnych (M od ang. message) a każdy element M nazywamy wiadomością jawną lub tekstem jawnym.
  • V2  jest alfabetem, w którym zapisujemy kryptogramy (szyfrogramy) V2 \overset{df}{=} C , czyli zbiór wszystkich słów nad alfabetem V2 jest tzw. przestrzenią wiadomości zaszyfrowanych, czyli, jak mówimy, szyfrogramów (szyfrogram nazywamy też kryptogramem, wiadomością zaszyfrowaną, lub tekstem zaszyfrowanym). Często alfabety V1 i V2 są tym samym afabetem.
  • K jest dowolnym zbiorem jest to tzw. przestrzeń kluczy (ang. key space), każdy element k K nazywamy kluczem (lub kluczem szyfrującym). Na ogół K jest zbiorem skończonym ale w definicji ogólnej systemu kryptograficznego nie robimy tego założenia.
  • E jest funkcją E : M K C , obliczanie wartości funkcji E nazywamy szyfrowaniem, a samą funkcję przekształceniem szyfrującym lub co jest nieco mylące również szyfrem. Wartość E(k, m) nazywamy wiadomością zaszyfrowaną, szyfrogramem, kryptogramem lub tekstem zaszyfrowanym. Sposób obliczania E(k,m) dla danych k,m nazywamy algorytmem szyfrowania.
  • D jest funkcją D : C K M obliczanie wartości funkcji D nazywamy deszyfrowaniem a samą funkcję D przekształceniem deszyfrującym. Sposób obliczania D(k,m) dla danych k, m nazywamy algorytmem deszyfrowania.

Od systemu kryptograficznego wymagamy by dla każdego klucza szyfrującego k1 K , istniał klucz deszyfrujący k2 K taki, że dla dowolnej wiadomości jawnej m zachodzi

 D(k2 , E(k1, m)) m .

Istota rzeczy: Każdą wiadomość zaszyfrowaną musimy umieć rozszyfrować, ale być może służy do tego już inny klucz.

Z powyższej definicji wynika, że dla każdego k1 K , odwzorowanie E( , k1 ) : M C jest funkcją różnowartościową. Oczywiście podobnie odwzorowanie D( , k2 ) : E( M , k1 ) C , (gdzie k2 K jest kluczem deszyfrującym odpowiadającym kluczowi k1 K ) jest funkcją różnowarościową.

Z powyższej definicji wynika również, że mówiąc niezbyt precyzyjnie, szyfr to rodzina kodów sparametryzowana kluczem k K .

Istota rzeczy: Szyfr służy do zastąpienia wiadomości jawnej m kryptogramem c. Celem jest takie przekształcenie postaci wiadomości jawnej, by uzyskać postać nieczytelną dla osób nie dysponujących kluczem deszyfrującym. Realizujemy w ten sposób jeden z zasadniczych celów kryptografii tzw. poufność (ang. confidentiality)

Pewnego komentarza wymaga pojęcie klucza. W definicji systemu kryptograficznego wyróżniliśmy pewien zbiór K zwany przestrzenią kluczy (ogólnie rzecz biorąc zbiór K jest dowolny), którego elementy są nazywane kluczami i parametryzują odwzorowanie szyfrujące.

Na ogół im więcej elementów ma zbiór K tym szyfr jest bezpieczniejszy trudniej bowiem dopasować wówczas klucz k do przechwyconego kryptogramu c tak by było metodą przeglądania wszystkich kluczy k czyli za pomocą tzw. ataku brutalnego. D(c, k ) m

Klucz w praktyce to słowo nad jakimś ustalonym alfabetem, na ogół alfabetem, w którym zapisujemy wiadomość jawną, w szczególności może to być alfabet binarny {0,1} i wówczas z reguły K {0,1}n dla pewnego n N lub K {0,1}* . Jeśli klucz jest słowem nad ustalonym alfabetem to możemy mówić o długości klucza.

Na ogół im dłuższy jest klucz tym bezpieczniejszy jest system kryptograficzny. Oczywiście technicznie rzecz biorąc klucz może mieć dowolną długość. Istnieją jednak w wielu krajach (np. w USA, Francji czy Iraku) pewne ograniczenia o charakterze prawnym np. 128 bitowe klucze to standard w USA i w Polsce do zakupów sieciowych z kartą kredytową i w transakcjach bankowych.

W powszechnym mniemaniu klucze 128 bitowe uważa się za bezpieczne, ale rzecz jasna taka zasada jest niebezpieczną półprawdą, bowiem klucza nie można rozpatrywać w oderwaniu od systemu kryptograficznego np. 128 bitowy czy nawet 512 bitowy klucz w przypadku szyfru RSA nie jest kluczem bezpiecznym. Przyjmuje się, że dla RSA bezpieczna długość klucza to 1024 bity lub więcej.

Ważna uwaga praktyczna o tajności metod i algorytmów kryptograficznych:

Nigdy nie budujemy bezpieczeństwa systemu na ukrywaniu metody czy algorytmu kryptograficznego. Wprost przeciwnie, algorytm publicznie znany, o którym wiadomo, że był atakowany bez powodzenia, można uznać za pewny i bezpieczny.

 

5.3. Podział szyfrów

Systemy kryptograficzne, szyfry, algorytmy kryptograficzne dzielimy na

  • symetryczne (inaczej z kluczem symetrycznym )
  • asymetryczne (inaczej z kluczem asymetrycznym).

Jeśli do szyfrowania i rozszyfrowania używamy tego samego klucza (lub nieco ogólniej pary kluczy k1 , k2 K , takich że z klucza szyfrującego k1 K daje się łatwo obliczyć klucz deszyfrujący k2 K i odwrotnie z klucza deszyfrującego k2 K daje się łatwo obliczyć klucz szyfrujący k1 K ) to system kryptograficzny, szyfr czy algorytm kryptograficzny nazywamy systemem kryptograficznym, szyfrem czy algorytmem kryptograficznym z kluczem symetrycznym.

Jeśli szyfr jest taki, że do szyfrowania i rozszyfrowania używamy różnych kluczy ( kluczy k1 K takich że z k1 K nie daje się łatwo obliczyć klucz deszyfrujący k2 K i odwrotnie z klucza deszyfrującego k2 K nie daje się łatwo obliczyć klucz szyfrujący k1 K ) to system kryptograficzny, szyfr czy algorytm kryptograficzny nazywamy systemem, szyfrem czy algorytmem kryptograficznym z kluczem asymetrycznym.

Systemy kryptograficzne z kluczem symetrycznym nazywamy też systemami z kluczem prywatnym. Przykładami takich systemów są DES (Digital Encryption Standard), 3DES (tzw. potrójny DES), DESX (DES z wybielaniem), IDEA, LOKI, Twofish, Blowfish, RC5, AES (Advanced Encryption Standard) czyli Rijndael .

Systemy kryptograficzne z kluczem asymetrycznym nazywamy też systemami z kluczem publicznym. Przykładami takich systemów są szyfr RSA, szyfr Rabina, szyfr ElGamala, szyfr plecakowy Merklego-Hellmanna, szyfr plecakowy Chora-Rivesta, szyfr McEliece’a i szyfr probabilistyczny Bluma-Goldwasser’a.

Określenie: "silna kryptografia" oznacza szyfry trudne do złamania, np. stosuje się w tym celu długie klucze. Pod pojęciem silnej kryptografii rozumie się na ogół algorytm kryptograficzny z kluczem dłuższym od 128 bitów (w przypadku .algorytmów z kluczem prywatnym) i z kluczem dłuższym od 2048 bitów (w przypadku algorytmów z kluczem publicznym).

5.4. Bloki, blokowa funkcja szyfrująca i szyfry blokowe

Funkcję szyfrującą E : M K C wygodnie jest zdefiniować za pomocą różnowartościowej tzw. blokowej funkcji szyfrującej f : V1 n K V2 m . Blokowa funkcja szyfrująca f : V1 n K V2 m musi spełniać warunek: dla każdego k K funkcja f ( , k ) : V1 n V2 m jest różnowartościowa.

Z kolei funkcję deszyfrującą blokowej funkcji deszyfrującej D : C K wygodnie jest zdefiniować za pomocą blokowej funkcji szyfrującej : V2  K V1 .

Jeśli V1 V2 , to musimy mieć oczywiście m n by zachować różnowartościowość funkcji f ( k) : V n V m . Najczęściej jednak w praktyce V1  V2 i m n

Blokową funkcję szyfrującą f : V1 n K V1 m nazywamy też przekształceniem szyfrującym dla tekstów jawnych o stałej długości.

Standardowym postępowaniem przy szyfrowaniu długich tekstów jawnych jest podział takiego tekstu na tzw. jednostki tekstu lub bloki czyli słowa o stałej długości n, które skonkatenowane dają wiadomość jawną m. Każdą jednostkę tekstu możemy szyfrować wówczas niezależnie za pomocą blokowej funkcji szyfrującej f : V1 n K V2 m . Jednostkom tekstu (lub jak mówimy czasem blokom) o długości n blokowa funkcja szyfrująca przyporządkowuje fragment szyfrogramu o długości m.

Dzielenie długiego tekstu wiadomości jawnej na krótsze jednostki tekstu jest bardzo wygodne, ale wymaga niekiedy przedłużenia wiadomości jawnej m tak by ta długość była równa r n dla pewnego r N .

Istota rzeczy: Szyfrujemy długie teksty jawne "po kawałku". Ten "kawałek" nazywamy blokiem lub jednostką tekstu. Powstaje rzecz jasna od razu problem a jeśli to się nie da podzielić na jednakowe „kawałki”. Rozwiązanie jest oczywiste. Dopełniamy tekst szyfrowany do wielokrotności długości bloku. Najczęściej jest to zgodne z pewnymi standardami.

5.5. Funkcja jednokierunkowa

Funkcja f : X Y , gdzie X, Y są dowolnymi niepustymi zbiorami, jest nazywana funkcją jednokierunkową (ang. one way function) jeśli:

  • Dla każdego x X łatwo potrafimy obliczyć wartość f(x).
  • Dla każdego y f ( X ) , znalezienie takiego x X , że y = f(x) jest praktycznie nierealizowalne (z uwagi na złożoność obliczeniową problemu).

Nie znamy żadnego dowodu na to, że jakaś funkcja jednokierunkowa w ogóle istnieje. Jesteśmy jednak przekonani, że funkcje jednokierunkowe w przyrodzie istnieją.

A oto trzy kandydatki na funkcje jednokierunkowe. Są to permutacje podzbioru liczb całkowitych Zp . Ogólnie rzecz biorąc jednak funkcja jednokierunkowa nie musi być różnowartościowa.

  • Funkcja wykładnicza modulo p (ang. exponentation modulo p), gdzie p jest liczbą pierwszą. Niech p będzie liczbą pierwszą i niech  będzie generatorem grupy multiplikatywnej Zp * . Funkcja wykładnicza modulo p f : Zp * Zp * jest zdefiniowana  dla każdego x Zp * wzorem

f (x) x (mod p) ,

  • lub wzorem f (x) x , jeśli mnożenie traktujemy jako mnożenie w Z p . Odwrócenie p Z funkcji f jest dokładnie problemem znalezienia wartości logarytmu dyskretnego w grupie multiplikatywnej Zp * (jeśli y f (x) , czyli y x , to x log y ). Nie są znane efektywne obliczeniowo algorytmy obliczania wartości logarytmu dyskretnego w grupie Zp * .
  • Funkcja RSA (ang. RSA function). Niech p, q będą różnymi nieparzystymi liczbami pierwszymi i niech n p q . Niech ponadto NWD(e,( p 1)(q 1)) 1. Funkcja RSA zdefiniowana jest tak f : Zn Zn , f (x) xe (mod n) dla każdego x Zn .
  • Funkcja Rabina. Niech n p q gdzie p i q są różnymi liczbami pierwszymi takimi, że p 3(mod 4) i q 3(mod 4) (n jest tzw. liczbą Bluma). Funkcja Rabina f zdefiniowana jest następująco

f : Qn x n f (x) x 2 (mod n) Q

gdzie Qn Zn jest tzw. zbiorem reszt kwadratowych modulo n. Można pokazać, że f jest permutacją zbioru Qn . Odwracanie funkcji f, czyli obliczanie pierwiastka kwadratowego w pierścieniu Zn jest dla dużych n praktycznie nierealizowalne, tzn. nie są znane żadne efektywne obliczeniowo algorytmy rozwiązujące ten problem, chyba, ze potrafimy rozłożyć liczbę n na czynniki pierwsze.

5.6. Szyfry przestawieniowe czyli szyfry permutacyjne

Szyfry permutacyjne (ang. transposition ciphers) należą do klasy szyfrów symetrycznych. Załóżmy, że alfabety V1 i V2 są takie, że V1 V2 \overset{ozn}{=} V , a zbiór kluczy

= {zbiór wszystkich permutacji zbioru <1,<i>r</i>>}

gdzie r jest ustaloną liczbą naturalną.

Niech m m1m2 ...mr M będzie dowolną wiadomością jawną. Dla i 1,2,..., r, mi V . Oznaczmy przez f blokową funkcję szyfrującą bloki (czyli jednostki tekstu) o długości r

f : V r K V r

a dokładniej

f : V r K (m, ) f (m, ) m (1) m (2) ...m (r ) c V r

Zatem wiadomości jawnej m o długości r przyporządkowujemy kryptogram o długości r ustawiając w pewnej kolejności (wyznaczonej przez permutację ) wyrazy ciągu stanowiącego wiadomość jawną.

Do zapamiętania: Kluczem jest permutacja zbioru <1,r>, gdzie r jest długością jednostki tekstu (bloku).

5.7. Szyfry podstawieniowe

Prosty szyfr podstawieniowy (ang. simple substitution cipher) to szyfr blokowy o długości bloku 1. Niech V1 będzie ustalonym alfabetem a f : V1 V2 funkcją różnowartościową (z reguły V1 V2 ). Jeśli wiadomość jawna m m1m2 ...mr , to szyfrogram c f (m1) f (m2 )... f (mr) . Funkcja f jest tu kluczem szyfrującym a f 1 kluczem deszyfrującym.

Klasycznym przykładem takiego szyfru jest szyfr Cezara,. który będzie omówiony dokładniej w dalszym ciągu podrozdziału.

Polialfabetyczny szyfr podstawieniowy (ang. polyalphabetic substitution cipher) to szyfr blokowy o długości bloku t. Niech V1 będzie ustalonym alfabetem a fi : V1 V2 , funkcją różnowartościową dla i 1,2,..., t (z reguły V1 V2 ). Jeśli wiadomość jawna m m1m2 ...mt to szyfrogram c f1 (m1 ) f2 (m2 )... ft (mt ) . Funkcja f1 f 2 ... ft jest tu kluczem szyfrującym a f1 1 f2 1 ... ft 1 kluczem deszyfrującym.

Przykład: Rozważmy dla ustalenia uwagi 26 literowy alfabet języka angielskiego V {<i>a</i>, <i>b</i>, <i>c</i>,..., <i>z</i>} . Klasycznym szyfrem Cezara nazywamy szyfr podstawieniowy, w którym V1 V2 V i funkcją definiującą podstawienia jest permutacja : V V , gdzie

\pi = \binom{ \text {a b c d e ... y z}}{ \text {d e f g h ... b c} }

Jeśli utożsamimy litery z liczbami z pierścienia Z26 na zasadzie a ~ 0, b~1, c~2, d~3, ..., y~24, z~25 , to widać, że:

(x) x 3(mod 26)

co odpowiada braniu jako wartości permutacji  liczby – litery znajdującej się o 3 pozycje w prawo (modulo 26) w stosunku do liczby – litery argumentu.

Ogólnie rzecz biorąc, jeśli alfabet V jest n literowy, to możemy V utożsamić z Zn i zdefiniować permutację : V V wzorem:

(x) (x r)(mod n)

Kryptogram wiadomości jawnej m m1m2 m3 ...ms otrzymujemy więc jako

(m1 ) (m2 ) (m3 )... (ms )

Tak zdefiniowany szyfr, nazywamy szyfrem Cezara z przesunięciem r. Oczywiście r jest kluczem szyfrującym i deszyfrującym jednocześnie. Kluczem w szyfrze Cezara jest więc liczba naturalna ze zbioru Zn

Przykład: Załóżmy, że m jest licznością alfabetu jawnego V tzn. m cardV , np. = 26 w przypadku języka angielskiego.

Identyfikujemy:

a 1 czyli identyfikujemy kolejne litery z kolejnymi liczbami

b 2 naturalnymi ze zbioru {1,2,...,26}

...

z 26

W przypadku alfabetu polskiego liter jest trochę więcej, bo mamy ą, ć, ę, ł, ń, ś , ó, ź. Klucz k k1 k2 ...kt V t , t N powtarzamy, jeśli trzeba, okresowo.

Szyfr Vigenere`a zadajemy tak: definiujemy odwzorowane fi : V V wzorem:

fi (a) \overset{df}{=} a m ki

dla tekstu jawnego m m1m2 ...mk

c = f_{[1]_t} (m_1)f_{[2]_t}(m_2)...f_{[k]_t} (m_k)

Przykład: Okres klucza 5, klucz k = OKRES

m = ENCYKLOPEDIA TECHNIKI
k = OKRESOKRESOKRESOKRESO
                                                      
c = TYUCC ZGJWXLRYXRSENCX

Oczywiście szyfr Cezara jest szczególnym przypadkiem szyfru Vigenere`a.

5.8. Szyfr produktowy lub złożeniowy

5.8 Szyfr produktowy lub złożeniowy

Produktem lub złożeniem 2 systemów kryptograficznych o blokowych przekształceniach szyfrujących f1 : V1 n K1 V2 m i f2 : V2 m K2 V3 r nazywamy system kryptograficzny zdefiniowany przez blokowe przekształcenie szyfrujące g : V1 n (K1  K2 ) V3 r zadane wzorem g(m,(k1 , k2 )) f 2 ( f1 (m, k1 ), k2 ) , gdzie m  V1 n oraz k1 K1 , k2 K 2 . Szyfr zdefiniowany przez blokowe przekształcenie szyfrujące g : V n (K K2 ) V3 r nazywa się szyfrem produktowym (ang. product cipher) lub szyfrem złożeniowym.

Typowy szyfr złożeniowy tworzymy składając szyfr permutacyjny z szyfrem podstawieniowym, co tworzy tzw. rundę. Składanie rund z kolei jest ogólnym często stosowanym pomysłem na tworzenie szyfrów blokowych z kluczem symetrycznym. Składając rundy tworzymy nowy szyfr zwany siecią permutacyjno - podstawieniową (ang. substitution-permutation network).

Składając odpowiednio dobrane rundy tworzymy bardzo mocne szyfry. Tak skonstruowane są m.in. szyfry DES, IDEA i AES (Rijndael).

Uwaga: Nie zawsze (jakby się wydawało na pierwszy rzut oka) złożenie dwu systemów kryptograficznych prowadzi do istotnie nowego czy lepszego systemu kryptograficznego

Istota rzeczy: Szyfr produktowy tworzymy "stosując jeden po drugim" dwa lub większą ilość szyfrów. Na ogół prowadzi to do znacznie mocniejszych szyfrów ale nie jest to regułą.

5.9. Szyfry strumieniowe

Szyfry strumieniowe (ang. stream ciphers) są ważną klasą szyfrów z kluczem symetrycznym. Należą do klasy szyfrów blokowych, (z długością bloku =1, a więc oddzielnie szyfrujemy każdą literę tekstu jawnego). Istotą szyfru strumieniowego jest to, że przekształcenie szyfrujące może zmieniać się dla każdego szyfrowanego symbolu.

Szyfry strumieniowe są użyteczne w sytuacji, gdy

  • wysokie jest prawdopodobieństwo błędów transmisji, ponieważ w szyfrach strumieniowych nie ma propagacji błędu;
  • dane muszą być przesyłane symbol po symbolu (np. szyfrator lub deszyfrator nie ma pamięci).

Zdefiniujemy teraz szyfr strumieniowy formalnie. Niech V1 i V2 będą alfabetami. Punktem wyjścia jest blokowa funkcja szyfrująca (szyfrująca bloki o długości 1) E : K V1 V2 i blokowa funkcja deszyfrująca (deszyfrująca bloki o długości 1) spełniające warunek:

\underset{m \in V_1}{\forall} \quad \underset{k_1 \in V_1}{\forall}\quad \underset{k_2 \in K}{\exists} D(k2 , E(k1 , m) m .

Tworzymy ciąg (k1)\underset {_{i=1}}{_{ \infty }} , gdzie ki K dla każdego i N . Nazywamy ten ciąg strumieniem kluczy. Definiujemy nowy system kryptograficzny (V1 ,V2 , \tilde K, \tilde E, \tilde D) następująco. V1 i Vsą wprowadzonymi już alfabetami, \tilde K {(<i>k</i><sub><i>i</i> </sub>)<sup><span class="equation" style="width:100%;;;;">\underset {_{i=1}}{_{ \infty }}  ; ki K} zbiorem kluczy,  \tilde E: \tilde K V1*  V2* funkcją szyfrującą, dokładniej \tilde E zadane jest wzorem:

\tilde E ((ki )\underset {_{i=1}}{_{ \infty }}, * m1m2 ...mr ) E(k1, m1)E(k2 , m2 )...E(kr , mr ) c1c2 ...cr

\tilde D: \tilde K V2* V1* funkcją deszyfrującą, dokładniej \tilde D zadane jest wzorem:

\tilde D((ki )\underset {_{i=1}}{_{ \infty }} c1c2 ...cr ) D(k1, c1)D(k2 , c2 )...D(kr , cr ) m1m2 ...mr

Taki system kryptograficzny nazywamy szyfrem strumieniowym.

5.10. Szyfry idealne

Szyfr Vernama to inaczej szyfr idealny lub szyfr z kluczem jednokrotnym (ang. one pad cipher).

Idealnym z kryptograficznego punktu widzenia jest szyfr z jednokrotnym kluczem losowym generowanym w ciągu prób Bernoulliego. Szyfr z kluczem jednokrotnym charakteryzuje się bezpieczeństwem idealnym, ponieważ przy przechwyceniu szyfrogramu c o określonej długości n prawdopodobieństwo warunkowe wystąpienia danego tekstu jawnego m (długości n) pod warunkiem odebrania c nie zależy od m.

Szczególnym przypadkiem szyfru jednokrotnego jest szyfr Vernama, przekształcający ciągi zerojedynkowe na ciągi zerojedynkowe. Szyfr ten został wynaleziony w 1917 roku przez dwóch Amerykanów: Gilberta S. Vernama (pracującego dla American Telephone and Telegraph Company, w skrócie AT&T) i Josepha O. Mauborgne ( z US Army Signal Corps.)

Rys. 1. Szyfr Vernama. Szyfrowanie: ci mi ki . Deszyfrowanie: mi ci ki

Algorytm deszyfrowania jest następujący: mi ci ki . Jego poprawność wynika z łączności sumy modulo 2 i faktu, że ki ki 0.

Wadą szyfru Vernama jest to, że klucz jest tak długi, jak wiadomość jawna. Jednak w pewnych zastosowaniach użycie klucza jednokrotnego jest uzasadnione (dyplomacja, wojsko).

W praktyce często zastępuje się ciąg losowy k k1 k2 ...kt  {0,1}* ciągiem pseudolosowym. Np. mamy do zaszyfrowania tekst jawny (mi)^{N_0}_{i=1} o długości N0 , generujemy pseudolosowy ciąg bitów (ki )^{N_0}_{i=1} i szyfrujemy tekst jawny jako (ci)^{N_0}_{i=1}, gdzie ci ki ai dla każdego i i1 i=1,2,..., N0 .

Szyfr Vernama jest szyfrem strumieniowym. Można go uogólnić z przypadku binarnego na dowolny alfabet skończony zastępując sumę modulo 2 sumą modulo n.

5.11. Algorytm szyfrowania szyfrem RSA

Szyfr RSA jest typowym, najczęściej stosowanym szyfrem z kluczem publicznym. Nazwa RSA pochodzi od nazwisk 3 profesorów z MIT (Massachussets Institute of Technology) R.L.Rivesta, A.Shamira i L.M.Adlemana, którzy w roku 1978 opracowali koncepcję tego szyfru. Szyfr RSA uważany jest za jeden z najbezpieczniejszych, najpewniejszych szyfrów. Używany jest do szyfrowania wiadomości, do tworzenia podpisów cyfrowych oraz dystrybucji kluczy kryptograficznych.

Opiszemy teraz, jak tworzymy system kryptograficzny RSA. Potrzebne nam będzie przy omawianiu szyfru RSA pojęcie funkcji Eulera. Dla danej liczby naturalnej n symbolem (n) oznaczamy liczbę liczb naturalnych względnie pierwszych z n i mniejszych od n, tzn. (n) card{<i>m</i> <font face="Symbol, serif"></font> <i>N</i>; <i>m</i> <font face="Symbol, serif"></font> <i>n</i>, <i>NWD</i>(<i>m</i>, <i>n</i>) <font face="Symbol, serif"></font> 1}. W ten sposób definiujemy funkcję : N N , która nazywa się funkcją Eulera. Łatwo można wykazać, że jeśli p jest liczbą pierwszą to ( p) p 1 i dla każdego k N , mamy

( pk ) pk 1( p 1) \qquad(1)

Jeśli m i n są względnie pierwszymi liczbami naturalnymi to mamy

(mn) (m) (n) \qquad(2)

Z (1) i (2) dostajemy, że jeśli n p1k1 p2k2 ... prkr , gdzie p1, p2 ,..., pr są parami różnymi liczbami pierwszymi to

(n) p1k1 1 ( p1 1) p2k2 1 ( p2 1) ... prkr 1 ( pr  1) \qquad(3)

Wybieramy teraz dwie duże liczby pierwsze p i q obliczamy iloczyn n=pq oraz wartość funkcji Eulera (n) ( p 1)(q 1) . Wybieramy następnie losowo dowolną taką liczbę (n) d Z(n) , dla której istnieje element odwrotny e d 1 w pierścieniu Z (n) czyli takie e Z (n) by d (n) e 1 w pierścieniu Z (n) (oczywiście jest to równoważne temu by znaleźć takie e Z (n) , żeby d e 1 (mod (n)) ). Warunkiem koniecznym i dostatecznym na to by istniał taki element odwrotny jest by NWD( (n), d ) 1 . Obliczamy e d 1 i ujawniamy parę uporządkowaną (e,n) jako klucz publiczny. Kluczem prywatnym będzie para uporządkowana (d,n).

Tekst szyfrowany, czyli tekst jawny m V1   np. "ala ma kota" zamieniamy na liczbę całkowitą x np. 10982398.

Podnosimy tę liczbę do ustalonej potęgi e w pierścieniu Zn ((e,n) jest znane i stanowi klucz publiczny) lub co na jedno wychodzi podnosimy tę liczbę do potęgi e w pierścieniu Z i bierzemy resztę z dzielenia tej liczby przez liczbę n. Tę resztę wysyłamy do adresata.

c xe

Adresat podnosi (w pierścieniu Zn ) uzyskaną liczbę do pewnej potęgi d będącej kluczem tajnym czyli kluczem prywatnym uzyskując wiadomość jawną x.

x cd

Algorytmy RSA mające klucz o długości do 512 bitów, są stosunkowo proste do złamania. Adi Shamir opracował w 1999 r. specjalizowany równoległy komputer łamiący 512 bitowe RSA w ciągu 2 dni. Obecnie stosuje się już klucze 1024 bitowe a nawet 2048 i 4096 bitowe praktycznie całkowicie bezpieczne (w 2003 r.) tzn. nie do złamania przy współczesnym stanie wiedzy o algorytmach komputerowych i przy współczesnych możliwościach obliczeniowych systemów cyfrowych.
Rivest, Shamir i Adleman w słynnej słynnej zagadce zamieszczonej w 1977 roku w czasopiśmie "Scientific American" zamieścili szyfrogram pewnego tekstu jawnego, proponując czytelnikom odtworzenie tego tekstu z szyfrogramu. Użyli przy tym szyfru nazwanego później RSA-129. Liczba 129 w oznaczeniu bierze się od 129 cyfrowej liczby (cyfry dziesiętne), którą trzeba rozłożyć na czynniki pierwsze by uzyskać klucz i funkcję odwrotną deszyfrującą szyfrogram. Zadanie przed którym stanęli czytelnicy było więc typowym problemem przed którym staje kryptoanalityk. Szyfr RSA-129 został w końcu złamany w 1994 roku dzięki równoległej pracy wielu komputerów w sieci komputerowej i superkomputerów równoległych.
Fakt praktyczny: żeby złamać szyfr RSA musimy umieć rozkładać duże liczby na czynniki pierwsze. Do określenia funkcji odwrotnej do funkcji szyfrującej czyli funkcji deszyfrującej potrzebna jest znajomość 2 liczb pierwszych, p i q, których iloczyn daje n.

Wykażemy teraz poprawność RSA. Skorzystamy z następującego lematu będącego konsekwencją chińskiego twierdzenia o resztach.

Lemat: Niech m1 , m2 ,..., mn N będą parami względnie pierwsze (tzn. NWD(mi , m j ) 1 dla każdego i, j  1, n ,i j ) i mi 2 dla każdego i=1,2,...,n oraz niech a, b będą dowolnymi liczbami całkowitymi czyli a, b Z .

a b(mod mi ) dla każdego i=1,2,...,n wtedy i tylko wtedy gdy a b(mod m1 m2 ... mn )

Dowód poprawności algorytmu RSA: Z założenia ed 1 (mod (n)), zatem istnieje takie k Z , że

ed 1 k (n)\qquad(*)

Załóżmy teraz, że wiadomość jawna m Zn jest względnie pierwsza z p, czyli NWD(m, p) 1 . Z małego twierdzenia Fermata mamy wówczas:

m p1 1 (mod p) \qquad(**)

Podnosząc obie strony kongruencji (**) do potęgi k (q 1) i mnożąc przez m dostajemy:

m1k ( p1)(q1) m(mod p)

a więc korzystając z (*) mamy:

med m(mod p) .

Jeżeli NWD(m, p) 1 , to NWD(m, p) p . Zatem również mamy wówczas:

med m(mod p)\qquad (***)

Istotnie, ponieważ m jest podzielna przez p, więc med 0(mod p) i m 0(mod p), i kongruencja (***) zachodzi.

Zatem niezależnie od tego czy NWD(m, p) 1 , czy NWD(m, p) 1 , zawsze mamy

med m(mod p) .

Podobnie możemy rozumować dla liczby pierwszej q otrzymując

med m(mod q)

Z lematu dostajemy ostatecznie, że med m(mod pq) .

Probabilistyczne uzasadnienie algorytmu RSA: Warto zwrócić uwagę na to, że nie wymagamy w założeniach powyższego twierdzenia by tak jak w założeniach twierdzenia Eulera. Próba uzasadniania kongruencji NWD(a, n) 1, med m(mod pq) twierdzeniem Eulera byłaby więc zręcznym korzystaniem z nieuwagi Czytelnika.

Jednak przy n p1 p2 (gdzie czynniki są liczbami pierwszymi) i założeniu równomiernego rozkładu prawdopodobieństwa na zbiorze Zn prawdopodobieństwo tego, że NWD(a, n) 1 maleje do 0 wraz ze wzrostem p1 i p2 . Istotnie z definicji funkcji Eulera mamy, że:

P({<i>a </i><font face="Symbol, serif"></font> <i>Z</i> ; <i>NWD</i>(<i>a</i>, <i>n</i>) <font face="Symbol, serif"></font> 1}) \frac{n - \phi (n) }{n} 
= \frac{n - (p_1-1)\cdot(p_2-1) }{n}
= \frac{(p_1 + p_2 -1) }{p_1\cdot p_2} 
= \frac{1}{p_1} + \frac{1}{p_1} - \frac{1}{n}

a więc P({<i>a</i> <font face="Symbol, serif"></font> <i>Z</i><sub><i>n</i> </sub>; <i>NWD</i>(<i>a</i>, <i>n</i>) <font face="Symbol, serif"></font> 1}) 0 jeśli p1  i p2 

zatem sytuacja, że NWD(a, n) 1 nie zachodzi staje się bardzo mało prawdopodobna.

Uwagi końcowe o algorytmie RSA: Szyfr RSA jako algorytm z kluczem publicznym może być wykorzystywany zarówno do zwykłego szyfrowania jak również do dystrybucji tzw. kluczy sesyjnych oraz realizacji algorytmów podpisów cyfrowych.

Bezpieczeństwo szyfru RSA jest zależne od tego czy istnieje efektywny algorytm rozkładu dużej liczby n na czynniki pierwsze (dotychczas nie jest znany taki efektywny algorytm). Dokładniej, gdyby taki algorytm istniał to moglibyśmy łatwo obliczyć wartość funkcji Eulera (n) , n jest ogólnie znanym parametrem sytemu kryptograficznego. Odtworzenie klucza prywatnego z klucza publicznego sprowadzałoby się w tej sytuacji do znalezienia elementu odwrotnego do klucza publicznego w pierścieniu Z (n) co sprowadza się z kolei do realizacji rozszerzonego algorytmu Euklidesa (istnieją szybkie efektywne wersje tego algorytmu np. algorytm Stein'a).

Główną wadą algorytmu RSA jest jego znacznie większa złożoność obliczeniowa w porównaniu z innymi algorytmami szyfrowania jak np. algorytmem DES. Okazuje się to specjalnie kłopotliwe, gdy zależy nam na szyfrowaniu w czasie rzeczywistym, np. gdy szyfrujemy próbki sygnału mowy w systemie telekomunikacyjnym.

5.12. Funkcje skrótu

Funkcje skrótu (ang. hash function) nazywa się również jednokierunkowymi funkcjami skrótu, funkcja skrótu f : M {0,1}n (gdzie M jest zbiorem wiadomości a n jest ustalone) powinna być bowiem jednokierunkowa tzn. jeśli powinno być niemożliwe obliczenie x. f (x) y i znamy y, to praktycznie

Funkcja skrótu SHA-1 (Secure Hash Algorithm 1).
Funkcja skrótu MD4 (Message Digest 4).
Funkcja skrótu MD5 (Message Digest 5).
Funkcja skrótu RIPE MD -128
Funkcja skrótu RIPE MD

Plik, tekst, wiadomość m o dowolnej długości „obłożona” funkcja skrótu daje ciąg bitów o ustalonej długości n (np.: 20 bajtów, czyli 160 bitów).

Jeśli znamy wartość funkcji skrótu f (m) i samą funkcję skrótu i dysponujemy parą (m', f (m)) , to możemy sprawdzić integralność danych, czyli sprawdzić czy m m' obliczając dla m' wartość f (m') . Jeśli f (m) f (m') , to przyjmujemy, że m m' (choć tak być nie musi), czyli stwierdzamy, że „integralność danych jest zachowana”.

Podpis cyfrowy wiadomości jawnej m jest tworzony z reguły dla funkcji skrótu f (m) .
Intuicja funkcji skrótu jest prosta. Dla danej wiadomości jawnej m, wartość f (m) to „streszczenie” tej wiadomości zawarte w słowie o stałej liczbie bitów. Typowe n to 20 bajtów, tzn. 160 bitów.

5.13. Uwagi końcowe

Realizacja systemów kryptograficznych: Systemy kryptograficzne można realizować programowo, sprzętowo lub w sposób mieszany czyli częściowo sprzętowo częściowo programowo. Na ogół uważa się, że im więcej specjalizowanego sprzętu , tym system jest bezpieczniejszy. Współczesne profesjonalne systemy kryptograficzne są z reguły realizowane sprzętowo, wewnątrz specjalizowanych układów szyfrujących. Są to na ogół układy scalone typu ASIC (Application Specific Integrated Circuit) lub układy typu PLD (Programmable Logic Design) . Zaszycie algorytmu kryptograficznego w układzie scalonym typu ASIC lub PLD nie stanowi współcześnie większego problemu.

Realizując systemy kryptograficzne trzeba zwracać uwagę na zastrzeżenia eksportowe i patenty np. szyfr z kluczem publicznym RSA jest opatentowany w obu wersjach podstawowej i uogólnionej.

Probabilistyczne systemy kryptograficzne: Jak uwzględnić losowość w szyfrowaniu. W pewnych algorytmach kryptograficznych takich jak np. algorytm z kluczem publicznym ElGamela (z uwagi na losowość „wmontowaną” w algorytm), ustalonej jednostce tekstu jawnego mogą odpowiadać różne szyfrogramy. Tak więc jeśli G jest zbiorem skończonym lub przeliczalnym a (G,2G , P) przestrzenią probabilistyczną, to blokowe przekształcenie szyfrujące definiujemy jako f :V1l1 G V2 l2 dodając warunek, że

\underset{g \in G}{\forall} f (, g) :V1l1 V2 l2 jest funkcją różnowartościową

Informacja o wybranym w losowy sposób g G albo nie jest istotna przy deszyfrowaniu (alternatywnie np. możemy ustalonej jednostce tekstu przyporządkowywać różne szyfrogramy) albo jest zawarta w samym szyfrogramie, wbudowana w szyfrogram (tak jest np. w systemie kryptograficznym ElGamala).

Ogólnie rzecz biorąc funkcja E z definicji systemu kryptograficznego może być funkcją E : M K H C , gdzie H jest dowolnym skończonym zbiorem a (H ,2H , P) jest pewną przestrzenią probabilistyczną.

Istota rzeczy: Fakt losowej zmienności kryptogramu dla tej samej wiadomości jawnej bez wątpienia utrudnia kryptoanalizę.

6. Kompresja informacji

Kompresja informacji

6.1. Wstęp

Kompresja informacji to inaczej kompresja danych. Dokładniej, konkretna metoda kompresji danych to zawsze 2 algorytmy:

  • algorytm kompresji danych
  • algorytm rekonstrukcji danych

Kompresję dzielimy na 2 kategorie: kompresję stratną i kompresję bezstratną. Jeśli dane poddawane kompresji potrafimy odtworzyć, zrekonstruować w dokładnie takiej samej postaci jak dane poddawane kompresji to kompresję nazywamy bezstratną. W przeciwnym razie kompresja jest kompresją stratną.

Rodzaj kompresji zależy również od danych poddawanych kompresji. Tak więc mamy:

  • kompresję plików tekstowych
  • kompresję plików dźwiękowych (kompresja audio)
  • kompresję obrazów statycznych
  • kompresję sekwencji video czyli obrazów dynamicznych (obrazów ruchomych)

Model probabilistyczny generacji danych przez źródło danych: W zagadnieniach związanych z kompresją danych bardzo ważny jest model matematyczny generacji danych. Załóżmy, że obiektami kodowanymi są litery (symbole) z pewnego alfabetu V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n </i></sub>} i znamy prawdopodobieństwo pojawienia się każdej litery tzn. podane są prawdopodobieństwa P(ai ) pi dla i 1, 2,..., n . Jeśli przyjmiemy, że słowo nad alfabetem V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n </i></sub>} generowane jest przez źródło danych jako ciąg niezależnych k losowań liter (podobnie jak w ciągu k doświadczeń Bernoulliego) to otrzymujemy w naturalny rozkład prawdopodobieństwa na zbiorze V1 k wszystkich słów o długości k .

Ważnym parametrem charakteryzującym źródło danych jest entropia źródła. Jest to liczba definiowana wzorem:

H  \displaystyle\sum^{n}_{i=1} pi log2 pi i1

Shannon udowodnił, że najlepszym rezultatem (przy kodowaniu binarnym) jaki można uzyskać za pomocą algorytmu kompresji bezstratnej jest taki algorytm kodowania (taki kod binarny k : V1 {0,1} ) by średnia bitów wykorzystanych do kodowania liter z alfabetu była równa entropii źródła.

Podstawowy pomysł na kompresję jest następujący. Obiekty kodowane występujące częściej (czyli o większym prawdopodobieństwie wystąpienia) powinny być kodowane krótszym słowem a obiekty występujące rzadziej (o mniejszym prawdopodobieństwie) dłuższym słowem. Takie rozwiązanie wykorzystywane jest w znanym każdemu harcerzowi kodzie zwanym „alfabetem Morse’a”. Na przykład litera „e” często pojawiająca się w tekstach angielskich jest kodowana za pomocą jednej kropki (słowo o długości 1 nad alfabetem V2 {<font face="Symbol, serif">□</font>, <font face="Symbol, serif"></font>} ).

Jednoznaczna dekodowalność: Dosyć ważną użyteczną własnością kodu jest jego jednoznaczna dekodowalność. Oto definicja tej własności. Niech V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n</i> </sub>} . Kod k : V1 V nazywamy jednoznacznie dekodowalnym jeżeli każdy ciąg słów kodowych daje 2 się odkodować w dokładnie jeden sposób tzn. nie ma dwóch różnych słów 12 ...m , 1 2 ...m nad alfabetem V1 dających (po zakodowaniu każdej litery z V1 i konkatenacji uzyskanych słów kodowych) to samo słowo tzn. nie może być

k(1 )k(2 )...k (m ) k(1 )k(2 )...k(m )

Prefiks i sufiks. Niech V będzie dowolnym ustalonym alfabetem oraz a1a2 ...asłowem nad alfabetem V, wówczas każde słowo a1a2 ...ak , gdzie 1 k n nazywamy prefiksem słowa a1a2 ...an a każde słowo ak a2 ...an , gdzie 1 k n nazywamy sufiksem słowa a1a2 ...an . Jednocześnie słowo puste jest sufiksem i prefiksem każdego słowa.

Kod prefiksowy. Kod prefiksowy to kod w którym żadne słowo kodowe nie jest prefiksem innego słowa kodowego.

Istota rzeczy jest tu taka. Załóżmy, ze odbieramy słowo kodowe litera po literze. W kodzie prefiksowym patrząc na słowo kodowe nie mamy wątpliwości czy to już koniec słowa kodowego czy być może tylko początkowy fragment innego słowa kodowego.

Kody prefiksowe są w oczywisty sposób jednoznacznie dekodowalne.

Jeśli stosujemy kody o zmiennej długości słowa kodowego to ważnym pojęciem jest średnia długość słowa kodowego. W przypadku słów binarnych średnią długość słowa kodowego nazywamy średnią bitową kodu. Dokładniej, niech źródło danych będzie takie, 2 że V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n</i> </sub>} oraz prawdopodobieństwa P(ai ) pi dla i 1, 2,..., n . Średnią długością kodu k : V1 V2 nazywamy liczbę

l \displaystyle\sum^{n}_{i=1} P(ai )l(k(ai ))

gdzie l( ) oznacza długość słowa

Twierdzenie (warunek konieczny dekodowalności kodu, nierówność Krafta-McMillana):

Jeśli K jest zbiorem słów kodowych (binarnych) o długościach l1 , l2 ,..., ln N i kod k : V1 K jest jednoznacznie dekodowany to spełniony jest następujący warunek n tzw. nierówność Krafta-McMillana

\displaystyle\sum^{n}_{i=1} 2li 1

Twierdzenie (warunek wystarczający istnienia kodu prefiksowego):

Jeśli l1 , l2 ,..., ln N spełniają nierówność

\displaystyle\sum^{n}_{i=1} 2li 1

to można znaleźć dla n elementowego alfabetu V1 {<i>a</i><sub>1 </sub>, <i>a</i><sub>2 </sub>,..., <i>a</i><sub><i>n </i></sub>} kod prefiksowy k : V1 {0,1} o długościach słów kodowych wynoszących l , l ,..., ln .

6.2. Kod Huffmana

Kod Huffmana to kod ale jednocześnie najbardziej znana, powszechnie stosowana, efektywna metoda kompresji danych. W zależności od typu kompresowanego pliku, osiąga się oszczędności w objętości danych od 20% do nawet 90%. Kod Huffmana to kod prefiksowy spełniający następujące warunki:

  • obiektom kodowanym występującym częściej (mającym większe prawdopodobieństwo wystąpienia) odpowiadają krótsze słowa niż obiektom występującym rzadziej (mającym mniejsze prawdopodobieństwo wystąpienia);
  • dwa najrzadziej występujące (najmniej prawdopodobne) mają słowa tej samej długości.

Będziemy dalej zakładać, że obiektami kodowanymi są litery (symbole) z pewnego alfabetu V1 {a1 , a2 ,..., an } i znamy prawdopodobieństwo pojawienia się każdej litery tzn. podane są prawdopodobieństwa binarnego {0,1}. P(ai ) pi dla i 1, 2,..., n . Ponadto używamy jako alfabetu V2 zbioru

Algorytm kodowania Huffmana czyli algorytm Huffmana jest następujący:

  • dla każdej litery tworzymy drzewo złożone tylko z korzenia i ustawiamy te drzewa w malejącym porządku prawdopodobieństwa użycia danej litery
  • while (istnieją przynajmniej 2 drzewa)
    Z drzew t1 i t2 o najmniejszych prawdopodobieństwach p1 i p2 tworzymy nowe drzewo zawierające w korzeniu prawdopodobieństwo p1 p2 i mające t1 i t2 jako lewe i prawe poddrzewo. Przypisujemy 0 każdej lewej krawędzi i 1 każdej prawej krawędzi;
  • Tworzymy słowo kodowe dla każdej litery przechodząc drzewo od korzenia do liścia zawierającego prawdopodobieństwo stowarzyszone z tą literą i łącząc napotkane 0 i 1;

Przykład: Załóżmy, że chcemy zbudować kod Huffmana dla 4 literowego alfabetu V1 {a1 , a2 , a3 , a4 } przy czym P(a1 ) 0,1 , P(a2 ) 0, 2 , P(a3 ) 0, 3 , P(a4 ) 0, 4 .

Postępując zgodnie z podanym algorytmem Huffmana tworzymy drzewo pokazane na Rys. 1. Odczytany z tego drzewa kod Huffmana jest następujący:

a1 000 , a2 001

a3 01, a4 1. 

Rys. 1. Tworzenie kodu Huffmana.

Metoda kompresji plików tekstowych oparta na wykorzystaniu kodu Huffmana jest bardzo prosta. Kodujemy kolejne litery wchodzące w skład tekstu za pomocą kodu Huffmana i tak uzyskane ciągi konkatenujemy. Warto przy tym przypomnieć, że kod Huffmana jest kodem prefiksowym, a więc jednoznacznie dekodowalnym.

6.3. Algorytmy słownikowe

Jak już wspomnieliśmy w zagadnieniach związanych z kompresją danych bardzo ważny jest model matematyczny generacji danych. Algorytmy słownikowe kompresji należą do metod kompresji wykorzystujących cechy strukturalne występujące w danych. Algorytmy słownikowe dzielimy na dwie grupy:

  • algorytmy ze statycznym słownikiem
  • algorytmy z dynamicznym słownikiem

Istotę algorytmów słownikowych wyjaśnia następujący przykład. Załóżmy, ze dany jest tekst złożony ze słów 4 literowych i każde słowo składa się z czterech znaków. Alfabet, w którym zapisujemy tekst, składa się z 26 małych liter i znaków przestankowych, co daje w sumie 32 znaki (5 bitów na znak). Jeśli wybierzemy ze słów 4 znakowych najczęściej spotykane słowa (np. 256 słów) i umieścimy je w słowniku, to możemy postępować tak:

Analizujemy słowa 4 znakowe (dzielimy cały tekst kompresowany na 4 znakowe bloki) jeśli dane słowo znajduje się w słowniku to przesyłamy tylko jeden bit stwierdzający fakt znalezienia słowa w słowniku i pozycję słowa w słowniku (8 bitów). W sumie więc 9 bitów. Jeśli nie znaleźliśmy słowa w słowniku to 4 znakowe słowo kodujemy tak: jeden bit stwierdzający że słowo jest spoza słownika oraz 4 słowa 5 bitowe kodujące znaki z naszego alfabetu (w sumie 21 bitów).

Efektywność takiego kodowania będzie zależeć od tego, jaki odsetek słów analizowanych znajduje się słowniku.

Jeśli prawdopodobieństwo wystąpienia wzorca ze słownika jest równe p, to średnia liczba bitów poświęcanych na zapis 4 znakowego fragmentu tekstu wyraża się następująco:

L 9 p 21(1 p) 21 12 p

Gdybyśmy nie stosowali powyższego algorytmu to każdy 4 znakowy fragment tekstu zabierałby 20 bitów.

Oczywiście w powyższym schemacie kodowania efekt będzie tym lepszy, im większe jest prawdopodobieństwo p znalezienia wzorca w słowniku.

Popularne pakiety kompresji danych takie jak PKZip, Zip, gzip i ARJ używają algorytmów słownikowych opartych na LZ77. Inne znane słownikowe algorytmy kompresji to m.in. algorytmy LZ 77, LZ 78 (litery LZ są tu skrótem od nazwisk Lempel, Ziv), EZW itd. Powszechnie stosowanym algorytmem kompresji jest również algorytm LZW (Lempel-Ziv- Welch) należący do klasy algorytmów typu LZ 78.

Znane z systemu operacyjnego Unix polecenie compress, specyfikacja V.42.bis wykorzystywana w modemach oraz standard GIF (Graphics Interchange Format) są kolejnymi przykładami zastosowania metod słownikowych kompresji.