Jason. Cała informatyka w jednym miejscu!

Małe uzupełnienie wiedzy ogólnej z programowania! Zauważyłem po artykułach z algorytmiki sporo odniesień do podstawowych terminów i pojęć, o których nie zdołałem jeszcze napisać czegokolwiek, więc chcę to nadrobić po części. Dzisiejszy temat to obcinanie jako proces pozbawiania części ułamkowej z liczby zmiennoprzecinkowej, a także kiedy się to pojawia.

WYJAŚNIJMY SOBIE CZYM JEST TO OBCINANIE!

Termin o jaki mi chodzi w anglojęzycznym odpowiedniku brzmi "truncation". W programowaniu jest to pozbawienie liczby zmiennoprzecinkowej części ułamkowej (wiecie, ten ciąg cyfr po przecinku ;)). Uważam, że trzeba zrobić z tego temat na artykuł, gdyż może się ono pojawić nieoczekiwanie! Matematycznie rzecz ujmując, mówimy o podłodze jakiejś liczby:

⌊59.6⌋ = 59

czyli operacji zaokrąglania do największej liczby całkowitej nie większej od liczby poddanej temu zaokrągleniu. Dlaczego w takim razie mowa o "obcinaniu", a nie zaokrąglaniu? Bo to nie jest de facto zaokrąglanie zależne od "wysokości" części ułamkowej, tylko kasowanie jej bez względu na wszystko. To się odbywa ZAWSZE przy użyciu tak zwanego "rzutowania typu" do liczby całkowitej. To jest instrukcja nakazująca traktować typ zmiennej jak ten wskazany przez nas. W dalszych przykładach zostanie pokazane jak należy to robić. Rzutowanie to proces świadomy, czyli wymagający zawarcia takiego życzenia w kodzie, także tu nie ma żadnych zagrożeń ze strony kompilatora. Niemniej jednak ta operacja może być też wykonana bez naszej świadomości!

Języki programowania o słabej typizacji dopuszczają możliwość wykonania operacji obcięcia części ułamkowej bez zmiany typu zmiennej ani nawet rzutowania. Celowo napisałem w tytule o pułapce, gdyż obcinanie nie zawsze wykonywane jest jawnie. Może też powstać za naszymi plecami, a my tylko się denerwujemy kiedy wyjdzie coś po czasie. Dobrze, jeśli ktoś się spyta kiedy się ono odbywa.

A KIEDY?

Jest prosta do skumania zależność, która prostymi słowy sprowadza się do następującego twierdzenia:

Wynik będzie "ścięty" do liczby całkowitej "integer", jeżeli wszystkie liczby wchodzące w skład wyrażenia są również liczbami całkowitymi albo typ zmiennej jest całkowitoliczbowy, bądź występuje jedno i drugie.

KPW? Czyli wystarczy, że zamienicie typ oraz którąkolwiek liczbę podczas dodawania, odejmowania, mnożenia lub dzielenia na "floating-point". Wtedy wynik także będzie zmiennoprzecinkowy i żadne obcinanie za naszymi plecami nie będzie miało miejsca. Nigdy!

DOWÓD TWIERDZENIA

Rozpatrzmy taką rzecz (kod będzie w języku C). Mamy iloraz samych liczb całkowitych i typ całkowitoliczbowy, a zgodnie z zasadami działania kompilatora, to spowoduje przypisanie niczego innego jak liczby całkowitej w miejsce zmiennej (jest 0/2 spełnionych warunków):

int i = 5 / 2;

Jest różnie z dopuszczeniem do kompilacji w zależności od języka, jednak w C to przypisze wartość 2. Jak wstawicie typ zmiennoprzecinkowy:

float f = 5 / 2;	// 2.0

to to nie zmieni sytuacji, bo zostanie spełniony tylko jeden z dwóch warunków. A to dlatego, że obie liczby są całkowite! Żeby poszło po naszej myśli, jedna z liczb musi być zmiennoprzecinkowa:

float f = 5.0f / 2;

Teraz będzie dobrze! Niby banał, a jednak ludzie się na to niejednokrotnie nabierają. Ja też, kiedy miałem parę lat mniej na karku. To też pójdzie:

float f = (float)5 / 2;	// = (float)(5) / 2 = 5.0f / 2

ale nie próbujcie obejmować wszystkiego w nawias, bo kompilator Wam rzutuje wynik PO podzieleniu, a ułamek już się straci:

float f = (float)(5 / 2);	// = (float)(2)

Musicie też bacznie obserwować wyrażenia objęte w nawias będące częścią większego wyrażenia! Tu na przykład wystąpi obcinanie:

float f = (5 / 2)*0.5;

z powodu obu liczb całkowitych podwyrażenia objętego w nawias. Czy już wiesz jak możesz ocalić część ułamkową?

PARĘ SŁÓW NA TEMAT FUNKCJI

Wszystko co zostało napisane, dotyczy również funkcji zwracających wynik zmiennoprzecinkowy (jeden język będzie "krzyczeć" o braku rzutowania, a drugi nie)! W języku C istnieje funkcja "floor" zwracająca wynik typu "double", a nic nie będzie stało na przeszkodzie, żeby przypisać go "integer'owi":

int i = floor(52.0 / 16.0);

To wywoła obcinanie (nawet jeśli po przecinku są same zera). Java już nie da sobą tak manipulować. "Math.floor" także zwraca "double'a", ale tutaj nakaże zrobić rzutowanie na odpowiedni typ:

int i = (int)Math.floor(52.0 / 16.0);

co w mojej ocenie tylko sprzyja programiście.

Wniosek jest taki: obcinanie może "zaatakować" z dwóch stron: od typu całkowitoliczbowego i od operandów (argumentów operatora). Silna typizacja przyciśnie kompilator do zgłoszenia błędu np. Java, aczkolwiek języki o słabszej typizacji mogą "machnąć ręką" i przepuścić taką instrukcję. Wówczas mamy do czynienia z obcinaniem niejawnym. Jeżeli nie macie pewności co do stabilności wyniku, polecam funkcje wypisujące wyniki na konsolę ;).


Tyle! Wiecie już czym jest obcinanie w programowaniu oraz w jaki sposób na nie uważać.

PODOBNE ARTYKUŁY