Liczyby, ciągi znaków, o co tu chodzi?

Posted on 2019-02-04 Updated on 2019-02-24

Jak na kartce napiszę 2 + 2 - to każdy wie o co chodzi. A komputer nie wie? Co jest w tym takiego trudnego? Z Wordem sobie radzi, a 2 + 2 to problem? Jak więc komputer postrzega to, co jemu piszemy?

Znaki, czy liczby, a może bity?

Najmniejszą jednostą informacji w informatyce jest bit. Zapisujemy albo zero, albo jedynkę. Nie ma "pół", nie ma "może". Następnie, z bitów zbudowane są bajty - każdy bajt ma osiem bitów. W zasadzie to wszystko. Pozostaje pytanie: co tam jest, w tych bajtach? Właśnie... odpowiedź jest typowa - to zależy. A od czego zależy? Jako konsultant powiem, że od kontektstu. Spokojnie już tłumaczę.

Dla przykładu, załóżny, że rozważamy zmienną, która zawiera ciąg znaków: a = "Ala ma kota" Komputer ma zapisane, że dany obszar pamięci (ten, na który wskazuje zmienna a) zawiera literki. Od tego miejsca, w pamięci przez określoną ilość bajtów. Kiedyś było tak, że jeden bajt to był jeden znak, ale potem się skomplikowało... bo ludzie stwierdzili, że alfabet języka angielskiego to stanowczo za mało.

Co ciekawe, jeszcze wcześniej było tak, że używać można było tylko pierwszych siedmiu bitów, a ósmy był bitem parzystości. Sprzęt był zawodny, woleli się zabezpieczyć poświęcając jeden bit z każdego bajta na upewnienie się, że dane są poprawne. Po pewnym czasie okazało się, że już zawodny nie jest i można zagospodarować dodatkowe 128 wartości. Aż 128? Tak, dwa bity pozwalają zakodować 2 do potęgi czwartej bitów, na 7 bitach można zapisać 128 wartości, na 8 bitach 256. Każda, kolejna potęga, podwaja, tak samo dodanie kolejnego bitu... Większość rzeczy jest dość prosta. Dopiero złożenie wielu takich drobinych rzeczy w wielką całość staje się problematyczne. Łatwo coś przeoczyć.

To może zacznę jeszcze od innej strony. Jak wpisujemy tekst, używamy klawiatury. Każdy znak jest przesyłany do komputera, ale nie każdy jest pokazywany - są znaki sterujące - strzałki, albo enter. Klawiatura koduje wszystkie znaki na bitach i wysyła do komputera. Komputer wie, że dany zestaw bitów to jest np. Enter, czy też jak to było na maszynie powrót karetki. Znaki sterujące to początek - do 31. Potem są inne znaki, które nadają się do pokazywania - np. kod 32 to jest spacja. Ciekawa sprawa jest z cyframi, bo zaczynają się od kodu 48 i idą w górę. Czyli jak stwierdzimy, że podany znak ma kod z przedziału do 48 do 57 - to jest to cyfra, jak odejmiemy 48 - to mamy konkretną wartość. I to w wyjaśnia dlaczego komputer ma problem z 2 + 2.

No dobrze, ale skąd wie, że np. kod 48 to jest znak, który my rozumiemy jako zero? W środowisku okienkowym - np. pod Windowsami, mamy fonty, w trybie tekstowym jest obszar pamięci, gdzie było powiedziane co, konketnie pokazać. Każdy znak to obszar 8 na 16, czyli 16 bajtów, gdzie każdy bit z wartością 1 oznacza, że obrazek znaku ma zawierać zapalony pixel - jak jest zero to wtedy pixel ma być zgaszony. Można było zrobić program, który modyfikuje te obrazki, zamieniając je na coś innego - ktoś wciskał a, a na ekranie pokazywało się b. Na szczęście restart pomagał.

A co z tym obszarem do zagospodarowania? Jak doszło dodatkowe 127 nowych kodów, to ludzie wrzucili tam ramki i parę innych rzeczy. Np. można było zmienić obrazek dla określonego kodu, żeby pokazywał literki z naszymi znakam diakrytycznymi - dobrze - z ogonkami. Mało to wygodne, ale działało. Pierwszą firmą, która wprowadziła kodowanie polskich liter z ALTem był IBM - ź było pod ALT-Z, czyli dowrotnie jak w Microsofcie, który musi mieć wszystko inaczej.

Około 2000 roku komputery stały się na tyle mocne, że stać nas był na poświęcenie większej ilości miejsca na znak. Przyszedł Unicode, znaki kodowane są na 2, 4 bajtach, albo i więcej. Czy to nie jest martnotrastwo? Poniekąd. Można użyć kodowania UTF-8 - w skrócie koduje się tylko nie-angielskie znaki - cały Internet tak robi.

Python w wersji 3 też domyślnie ma kodowanie UTF-8 - tu jest pełen wywód, z nazwami standardów - wszystko, wyszystko. Czyli nie ma się co martwić? Zawsze są tzw. przypadki brzegowe. O ile nie musisz wyznaczyć dokłądnej długości ciągu znaków w bajtach - działa.

Ogólnie Python powstał, żeby się go prosto używało. Mało jest rzeczy, którymi należy się przejmować, ale dalej - dobrze jest wiedzieć jak to działa.

Liczby

Skoro wszystko jest liczbą to pewnie z liczbami jest prościej. Poniekąd.

Mam liczbę, którą zapisuję na 4 bajtach, gdzieś w pamięci. Muszę to zrobić tak, żeby przy odczytaniu wiedzieć, który bajt był do czego. Na szczęście takie rzeczy ustala twórca procesora. Procesory x86, są little endian, czyli liczby zapisane są odwrotnie niż my ich używamy - najmniej znaczący bajt jest pierwszy. Po naszemu 1123 najbardziej znaczący jest tysiąc, potem sto, potem dwadzieścia, a na końcu trzy. Odwrotny zapis pozwala na wykonywanie operacji matematycznych szybciej. Pascal zrobił kalkulator na krorbę, który wykonywał obliczenia właśnie w taki sposób. Czyli pomysł jest raczej stary. Python operuje na wyższym poziomie, chociaż warto mieć świadomość, że tu też żadnej magii nie ma.

Ważną sprawą, wynikającą z kodowania liczb właśnie w taki sposób jest ograniczone miejsce. Można sprawdzić na wikipedii Jakie są konkretne długości. Znaczenie ma, czy chcemy przechowywać znak, czy tylko liczby całkowite. Zabranie jednego bitu na kodowanie znaku powoduje, że liczba może być dwa razy mniejsza - to już wyjaśniałem.

Kolejny krok to zapisanie liczb innych niż załkowite. Pierwsze rozwiązanie to liczby stało pozycyjne. Czyli umawiamy się, że ileś bitów opisuje to, co przed przecinkiem, a reszta to co po. Jak się nad tym dobrze zastanowić, to jest bardzo mało wygodne. Macie coś policzyć i na starcie trzeba określić gdzie ile bitów ma być przydzielone. Nie da się dodać dużej liczby całkowitej i małej, ale z dużą częścią po przecinku, wynik będzie wymagać dużo bitów przed przecinkiem i tak samo za. Na szczęście ludzie już sobie z tym poradzili. Odpowiedzią są liczby zmienno-pozycyjne. Również mamy dwa zestawy bitów, ale teraz jeden opisuje cechę a drugi mantysę. Sprawa brzmi mocno podejrzanie, ale jest to dość proste. Cechę mnożymy przez 2 do potęgi określonej przez mantysę. Bity na znak cech i mantysy - jest liczba. Jak każde rozwiązanie w informatyce to jest kompromis. Dostajemy coś, ale coś poświęcamy - słowem doskonale nie jest. Tu problemem są małe liczby i dokładność. Taki system ma najmniejszą liczbę dodatnią jaką da się zakodować, czyli jest przerwa między zerem a kolejną wartością. No i dokładność:

a = 0
while a != 10:
    a += 0.1
    print (a)

Co się stanie? Wypisze do 10 i skończy, czy tak jak u mnie - poleci w nieskończoność? Dlaczego tak się dzieje? W komputerze 0.1 to tak jak zapis dziesiętny 1/3... A jak wcześniej napisałem ilość miejsca ograniczona, więc jest jak jest i trzeba na to uważać. Bardzo.

Wszelkie porównania - albo na liczbach całkowitych, albo "mniejsze-równe":

a = 0
while a <= 10:
    a += 0.1
    print (a)

Trochę dużo dziewiątek, ale przynajmniej się zakończyło. Jest jak jest. Uważać trzeba.