10 września 2019

C# - samouczek, część III - proste typy zmiennych i po co komu te cholerne zmienne.

Zacznijmy od tego, czym jest zmienna. Otóż zmienna jest odnośnikiem do pamięci, który akurat przechowuje dane mogące nam się z różnych przyczyn przydać. Mówiąc bardziej po ludzku - to takie nasze myślowe pudełko i jak to z pudełkami bywa, można coś do niego włożyć, przechować i wyjąć gdy będzie potrzebne. Można też gdzieś wysłać gdy jest taka potrzeba. 


Czym są więc typy zmiennych? Typ zmiennych mówi nam jak program ma interpretować dane, które w tej pamięci siedzą. Nie jest wiedzą tajemną, że każda informacja jaką nasz komputer w sobie przechowuje ma postać cyfrową - najczęściej zer i jedynek, natomiast zmienne pozwalają nam zinterpretować te liczby bądź jako ciąg znaków, bądź liczbę, bądź odpowiedź na pytanie o życie wszechświat i całą resztę (nawiasem mówiąc - to 42). 

W przypadku C# powinniśmy sami określać jaki rodzaj danych mają nasze zmienne (i chociaż da się to trochę obejść, to strasznie utrudnia to poprawianie kodu, który zawiera błędy logiczne), co (wracając do pudełkowej analogii) znowuż możemy sobie wyobrazić jako te nieszczęsne styropianowe wkładki, które powodują, że do naszego pudełka włazi tylko jeden konkretny produkt. Niestety tutaj przydatność pudełka jako analogii kompletnie się kończy, ponieważ ze zmiennymi możemy zrobić kilka sztuczek, które nie udadzą się z żadnym kartonikiem, dlatego zacznijmy już nasz krótki przegląd zmiennych. 

Typy całkowite. 

Typy całkowite pozwalają nam przechowywać liczby całkowite. Ponieważ informację o tym, jak wygląda liczba całkowita większość z nas zdążyła zapomnieć gdzieś w 4 klasie szkoły podstawowej przypomnę, że to po prostu liczba, która nie posiada części ułamkowej. 

Najprostszym typem całkowitym jest byte i jego koleżka sbyte (w C# wielkość liter ma znaczenie, więc aby uniknąć konfliktu z moim edytorem tekstu będę się starał nazwy pisane małą literą umieszczać gdzieś w środku zdania). Jak już sama nazwa wskazuje zajmują w naszej cennej pamięci dokładnie jeden, ośmiobitowy bajt (bajty nie zawsze muszą być ośmiobitowe, choć takie się najczęściej stosuje). Nasuwa się pytanie, czym więc się różnią? Literka “s” w sbyte bierze się od angielskiego “signed”, co w tym wypadku oznacza “ze znakiem”. Dzięki temu sbyte umożliwia nam zapisanie liczby ze znakiem. Pierwszy bit zużyjemy więc na opisanie czy nasza liczba jest dodatnia, czy ujemna, zostanie nam więc jeszcze 7 bitów do zapisania liczby. Każdy bit może przyjąć dwa stany - zero lub jeden, a więc możemy zapisać na nim 2 do 7 kombinacji czyli 128. Jedna kombinacja odpada nam na zapisanie liczby zero, dlatego maksimum jakie możemy zapisać to 127. Ponieważ zero nie ma znaku (komputery jednakże dla ułatwienia przyjmują zero za dodatnie), to dla zakresu liczb ujemnych nic nie musimy zabierać i zostaje nam 128 kombinacji. Dlatego pełny zakres sbyte to od -128 przez 0 do 127. Dla odmiany byte nie przechowuje żadnej informacji o znaku i każdą liczbę zapisaną w tym typie zmiennych traktuje jako dodatnią. Mamy więc do dyspozycji 8 bitów, a więc 2 do 8 potęgi kombinacji, czyli 256, minus jedna kombinacja na zapisanie liczby zero. Daje nam to zakres od 0 do 255. 

Przebrnęliśmy jakoś przez te najprostsze typy, ale zasada co do zakresu jest zawsze podobna. I tak przechodząc do zmiennych całkowitych szesnastobitowych mamy short i ushort. Zderzamy się tutaj z rzeczą bardzo, bardzo często spotykaną w wielu językach programowania. Jest ona straszna i czasem doprowadza do białej gorączki - niekonsekwencja. Tym razem ktoś wymyślił, żeby lepiej nie dodawać dodatkowej literki “s” przed nazwą typu który może być ujemny. Zdecydowano dodać “u” od “unsigned” (czyli "bez znaku") przed typem, który nie może ujemny (chociaż tak naprawdę to byte jest tutaj czarną owcą). Reszta podobnie - w short 1 bit na znak, 15 na liczbę co daje nam zakres od -32768 przez 0 do 32767. W ushort wykorzystujemy pełne 16 bitów co daje nam do dyspozycji zakres od 0 65535. 

Następne w kolejności zmienne 32 bitowe to ulubiony przez wszystkich int i jego rzadziej stosowany brat uint. Podobnie jak poprzednio dla int dysponujemy 31 bitami na liczbę plus jeden na znak, co daje nam dość konkretny już zakres od -2 147 483 648 do 2 147 483 647 (z zerem włącznie) i nie mniej poważny dla uint od 0 do 4 294 967 295. 

Na koniec zostają nam już tylko 64-bitowe potwory long i ulong. W pierwszej zapiszemy liczby od -9 223 372 036 854 775 808 do 9 223 372 036 854 775 807, a w drugiej nie mniej poważny zakres od 0 do 18 446 744 073 709 551 615. Są to już naprawdę poważne liczby, ale mimo to mogą się okazać za małe, niestety aby wykroczyć poza nie trzeba już złożonych typów danych i raczej nie planuję poruszać tego tematu w tym samouczku. 

Typy zmiennoprzecinkowe 

Nie samymi jednak liczbami całkowitymi człowiek żyje i czasem musi popełnić jakiś ułamek. Aby uporać się z tym zadaniem wymyślono typy zmiennoprzecinkowe i tej nazwy raczej nie pamiętacie z matematyki, a to dlatego, że to nie to samo co liczby rzeczywiste poznane w szkole. Generalnie rozchodzi się o sposób zapisu w tzw. notacji naukowej. Gdzie np 125,68 zapiszemy jako 1,2568*10^2.

Zapis bitowy liczby zmiennoprzecinkowej to generalnie koszmar, ale warto mieć w pamięci, że ponieważ bity przyjmują tylko dwa stany wszystko rozchodzi się o podnoszenie dwójki do odpowiedniej potęgi. Z ułamkami jest podobnie, tylko jako podstawę przyjmujemy nie 2, a ½ (czyli 2 do -1) i sumujemy ułamki, aż wyjdzie mniej więcej to co trzeba. Im więcej mamy tych ułamków do dyspozycji tym dokładniej będzie opisana nasza liczba. Niestety trudności sprawiają już nawet bardzo nieskomplikowane liczby jak np. ⅕. W zapisie dwójkowym to by było mniej więcej tak: 0 * ½ + 0 * ¼ + 1*⅛ + 1*1/16 = 3/16 ~ ⅕. Im więcej wstawię ułamków, tym bliżej będę pożądanej wartości, ale nigdy jej w 100% nie osiągnę i zawsze gdzieś pojawi się to nieszczęsne zaokrąglenie. Dlatego w ich przypadku istotniejsza jest precyzja zapisu (więcej ułamków) niż zakres. To wszystko jednak nie wyjaśnia dlaczego te liczby nazywają się zmiennoprzecinkowymi - otóż samo zapisanie ułamka to nie wszystko. W końcu liczby rzeczywiste nie ograniczają się do zakresu od 0 do 1. Aby problem rozwiązać, a sprawy nieco skomplikować znowu musimy gdzieś dorzucić bit znaku, a i jeszcze trzeba by zapisać część całkowitą no i gdzieś przechować wykładnik potęgi… Krótko mówiąc koszmar i tyle. 

Mając jednak to wszystko na uwadze trzeba pamiętać, że liczby zmiennoprzecinkowe są prawie zawsze obarczone pewnym błędem i wiele wielu operacji na tych samych zmiennych tylko sprawę pogarsza. 

Wracając jednak do meritum w C# znajdziemy trzy podstawowe typy zmiennych zmiennoprzecinkowych:
float o zakresie od ±1.5 x 10^(−45) do ±3.4 x 10^(38) i dokładności ~6-9 cyfr
double dające od ±5.0 x 10^(−324) do ±1.7 x 10^(308) i dokładności 15-17 cyfr
decimal w zakresie od ±1.0 x 10^(-28) do ±7.9228 x 10^28 i dokładności 28-29 cyfr

Ponieważ w pamięci komputera zapis tych liczb jest bardzo różny w kodzie musimy podawać jakim dokładnie typem się posługujemy. Aby więc pomóc kompilatorowi rozróżnić jakiego typu jest liczba 0.1 dodajemy na jej końcu odpowiednie literki: f lub F dla float, d lub D (albo po prostu liczbę z kropeczką) dla double i aby dobrze nam się kojarzyło z mamoną - m lub M dla decimal.

Których więc używać? Najszybsze są zmienne typu float, ale też najmniej dokładne. Najdokładniejsze są zmienne typu decimal i z tego tytułu zalecane jest ich używanie przy obliczeniach finansowych, ale ze względu na rozmiar operacje na nich są najwolniejsze - no i za ich pomocą można przechować najmniejsze liczby. Złotym środkiem jest double, który daje średni czas operacji, niezłą dokładność i największy zakres. Niestety oficjalna dokumentacja milczy na temat ilości zajmowanej pamięci przez poszczególne typy, nie mniej jednak należy założyć, że im większa precyzja, tym większy rozmiar. Najprawdopodobniej jednak mamy 32 bity na float, 64 na double i 128 na decimal

Typ znakowy. 

I na koniec bliski koleżka typu byte czyli char. Podobnie jak on zajmuje w pamięci 1 bajt, ale jest interpretowany nie jako liczba, a jako znak. Dokładniej pojedynczy, niesformatowany znak z rozszerzonego zakresu ASCII. Same w sobie używane dosyć rzadko, ale za to nieocenione, gdy weźmie się stadko takich znaków i ułoży z nich napis np. “Koniec tej części samouczka, bo już mnie palce bolą od pisania”. 

Lekcja już wyszła dosyć długa, choć temat należał raczej do tych prostszych, nie mniej jednak warto byłoby poruszyć jeszcze jedną kwestię. Otóż jak i gdzie mamy powiedzieć naszemu programowi, że chcemy użyć jakiejś zmiennej? O tym będzie w następnej części (no i pojawią się jakieś przykłady). 

W poprzednim odcinku: Witaj świecie!

Brak komentarzy:

Prześlij komentarz

„Obrażać też trzeba umieć!”

W zasadzie ten jeden cytat wystarczyłby za cały wpis. Nie wiem, czy w tym ukopanym kraju uchował się jeszcze ktokolwiek powyżej 25 roku życi...