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 (ten ciąg cyfr po przecinku 😉). Niemniej ważną informacją jest fakt, iż 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 zapisu 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żeli się pytasz 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, 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 zamienisz 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 👍!

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 wstawisz 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óbuj obejmować wszystkiego w nawias, bo wynik będzie rzutowany PO podzieleniu, a ułamek już się straci 🛑:

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

Musisz 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;

bo obie liczby podwyrażenia objętego w nawias są całkowite. 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 zmiennej typu "int":

int i = floor(52.0 / 16.0);

To wywoła obcinanie (nawet, jeżeli po przecinku są same zera). Java na ten przykład, już nie da sobą tak manipulować. "Math.floor" (odpowiednik w Javie tej samej metody matematycznej) także zwraca "double", lecz tutaj już nakaże zrobić rzutowanie na odpowiedni typ:

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

co w mojej ocenie tylko sprzyja programiście, bo będzie widać Twój "zamiar".

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 masz pewności co do stabilności wyniku, polecam funkcje wypisujące wyniki w terminalu 😉.


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

PODOBNE ARTYKUŁY