fbpx

Przyszedł czas na wpis numer 30 i jest to artykuł przygotowujący do czegoś większego, więc dziś tylko teoria. Liczby zmiennoprzecinkowe to temat, który miałem zamiar poruszyć w pierwszych dziesięciu wpisach, jednak plany zmieniały się często i wpis ten przeniosłem na późniejszy etap.

Jest to też artykuł wyjątkowy, bo ostatni przed wylotem do Tajlandii, dlatego odliczam dni i cieszę się, że będę mógł popracować na drugim końcu świata. Zdam na pewno wiele relacji i opiszę całą podróż w marcu lub kwietniu.

Teraz wróćmy jednak do tematu liczb zmiennoprzecinkowych. W tym artykule dowiesz się między innymi o tym:

  • Czym jest liczba wymierna i niewymierna?
  • Co oznacza notacja naukowa i wykładnicza?
  • Jaka jest definicja liczb zmiennoprzecinkowych?
  • Czym jest standard IEEE 754?
  • Jakie są przypadki szczególne podczas zapisu  i operacji na liczbach rzeczywistych?

Jak widzisz, pytania zapowiadają ciekawy wpis, więc zaczynajmy!

Zatrzymaj się!


Książki to obowiązkowa pozycja dla każdego zainteresowanego programowaniem!

Jest to zdecydowanie jedno z najlepszych źródeł do nauki programowania! Zyskasz przewagę w branży IT i osiągniesz dużo jako deweloper.


Wprowadzenie

Każdy z nas — prędzej czy później — spotyka się z ułamkami, bo o ile w życiu codziennym, z użyciem długopisu i kartki papieru jest je łatwo przedstawić, tak już za sprawą urządzeń elektronicznych jest inaczej. 

Aby w ogóle rozpocząć dywagacje na temat liczb zmiennoprzecinkowych, musimy sięgnąć do matematyki. Liczby zmiennoprzecinkowe to w matematyce liczba przedstawiona w formie ułamka złożonego z licznika i mianownika.

Ułamek jest inaczej nazywany liczbą wymierna.

Liczby wymierne to liczby, które można zapisać w postaci ilorazu dwóch liczb całkowitych

Dodatkowo nie jest tajemną wiedzą to, że nie wszystkie ułamki są skończone, lecz co to oznacza? Ni mniej, ni więcej tyle, że ułamki takie nie mają reprezentacji w formie ułamka dziesiętnego.

Weźmy na przykład ułamek 1/6, gdzie jego forma dziesiętna to 0.1(6), co oznacza tyle, że liczba 6 powtarza się w nieskończoność (ułamek ma rozwinięcie okresowe). I tu właśnie pojawia się jeden z problemów, który napotykamy podczas zapisu liczby wymiernej w pamięci komputera.

Z liczbami niewymiernymi mamy jeszcze gorzej. Mają one postać, której nie da się przedstawić za pomocą ułamka. Do liczb takich należą:

  • π – w przybliżeniu 3,14159265359
  • e – w przybliżeniu 2,71828182845904523536028
  • pierwiastek z 2 – w przybliżeniu 1,4142135623

Co z takimi „nieskończonymi” liczbami możemy zrobić w kontekście zapisu ich w pamięci komputera?

Notacja naukowa

Nie możemy przejść dalej bez omówienia notacji naukowej, ponieważ na pewno się z nią spotkaliście, nie będąc tego świadomym.

Jest to pewien sposób przedstawiania liczb wymiernych i niewymiernych, który pomaga głównie w prezentacji liczb bardzo małych lub bardzo dużych. Pomyśl sobie, że musisz zapisać liczbę z 35 zerami po przecinku albo w drugą stronę, 35 zer przed przecinkiem. Już sam zapis, a co dopiero liczenie tych zer później podczas obliczeń jest bardzo kłopotliwe i spowoduje szereg błędów natury ludzkiej.

Aby zapisać liczbę w takiej notacji, musimy zastosować się do poniższego wzoru.

M oznacza mantysę, 10 to oznaczenie zapisu dziesiętnego, a E to wykładnik (często też nazywany cechą). Wzór wydaje się prosty, jednak bez odpowiedniego przykładu nie da zrozumieć prostoty i znaczenia.

Weźmy na przykład stałą Plancka. Jest to wartość 0,000000000000000000000000000000000662607015. Jak widać, liczba ta jest totalnie dla nas nieczytelna. W notacji naukowej wartość 10 nie zostaje pokazana, jest jedynie mantysa i wartość wykładnika poprzedzona literą E.

Aby przekształcić taką liczbę w notację naukową, musimy przecinek przesunąć w prawo o tyle miejsc, by znaleźć pierwszą cyfrę (od 1 do 9). Liczba przesunięć przecinka będzie oznaczać wykładnik. Stałą Plancka zapiszemy w ten sposób. 

Teraz czas podstawić wartości do wzoru powyżej. Taki zapis liczby nazywa się zapisem w postaci wykładniczej.

Widzimy postać Mantysy, która równa jest 6,62607015, a cecha jest równa -34.

Drugim przykładem niech będzie liczba ogromna. Prędkość światła w przybliżeniu to jakieś 300000000 metrów na sekundę i zapiszmy go w postaci naukowej i wykładniczej.

Tym razem przecinek przesuwamy w lewo aż do momentu, gdy zostanie tylko jedna cyfra przed przecinkiem. Skoro przesuwamy w lewo, to możemy stwierdzić, że wykładnik jest dodatni, dlatego w tym przypadku równy 8, bo o tyle miejsc nastąpiło przesunięcie.

Liczby zmiennoprzecinkowe – definicje

Skoro już mamy rozjaśniony temat matematyczny i naukowy zapis liczb, to czas przejść do tego, czym są liczby zmiennoprzecinkowej. 

Liczby wymierne i niewymierne są objęte zbiorem liczb rzeczywistych i to ich reprezentacja zapisana za pomocą notacji naukowej (postaci wykładniczej) jest oficjalną definicją liczb zmiennoprzecinkowych. Skoro już wiemy, czym są wspomniane notacje oraz to, że nie da się wielu liczb przedstawić w skończony sposób, to mamy jasną informację o tym, że liczby zmiennoprzecinkowe w wielu przypadkach są tylko przybliżoną wartością faktycznej liczby.

Ułamki w systemie binarnym

Kolejnym krokiem do zrozumienia liczb zmiennoprzecinkowych jest zastanowienie się, w jaki sposób są one przechowywane w pamięci komputera. Oczywiste jest to, że operujemy na wartościach binarnych – 0 oraz 1. Wszystkie zapisane informacje są ostatecznie konwertowane do ciągu zer i jedynek.  

W jaki sposób binarnie zapisać liczbę 12,375? Liczba przed przecinkiem to 12. Jako wprawni programiści nie powinniśmy mieć problemu z zapisem liczby w systemie dwójkowym, dlatego ten fragment dywagacji pominę i po prostu stwierdzamy, że jest to wartość 1100.

Co zrobić z 0,375? Tu do tematu musimy podejść z innej strony. Skoro system binarny opiera się na dwóch wartościach, to aby liczbę po przecinku przedstawić w takim systemie musimy mnożyć przez dwa i brać wartość na lewo od przecinka — będzie to nasze 0 albo 1. Zobacz na przykładzie.

Zamiana liczby 0.375 na liczbę binarną.
Rysunek 1. Zamiana liczby 0.375 na liczbę binarną.

Teraz wystarczy od góry przepisać zera i jedynki i mamy wartość binarną liczby ułamkowej 0,375, która wynosi 0,011.

Możemy już teraz zapisać pełną liczbę 12,375 jako 1100,011.

Proste prawda?

Co jednak z liczbami, które nie są skończone? Spróbujemy zapisać binarnie liczbę 0,35, gdzie na pierwszy rzut oka wydaje się to równie łatwe, jak w poprzednim przykładzie. Sprawdźmy jednak działanie zgodnie z powyższym przykładem.

Zamiana liczby 0.35 na liczbę binarną
Rysunek 2. Zamiana liczby 0.35 na liczbę binarną.

Jak widzimy, sprawa się skomplikowała. Od wartości 0.40 cztery kolejne linie są różne, jednak dalej następuje powtórzenie. I dzieje się tak w nieskończoność. Mówimy wtedy, że wartość jest w okresie. W naszym przypadku liczbę 0,35 możemy zapisać binarnie jako 0,01(0110). Oznaczać to będzie, że wartość w nawiasie powtarza się cyklicznie.

Standard IEEE 754

Po takich podstawach doszliśmy do właściwej części artykułu. Omówmy standard IEEE 754.

Jest to jeden z najważniejszych standardów w informatyce, który ujednolica sposób zapisu liczb rzeczywistych na różnych platformach sprzętowych.

Standard opracował kanadyjski matematyk i informatyk William Kahan. Odnosi się do binarnej reprezentacji liczb zmiennoprzecinkowych. Dodatkowo reprezentuje też obliczenia na tych liczbach. Głownie implementowany jest w oprogramowaniu i procesorach.

Reprezentację taką należy podzielić na dwie grupy:

  1. pojedynczej precyzji — 32 bity, które dzielimy na trzy składowe
    • znak – 1 bit
    • cecha – 8 bitów
    • mantysa  – 23 bity
  2. podwójnej precyzji — 64 bity, które dzielimy na trzy składowe
    • znak – 1 bit
    • cecha  – 11 bitów
    • mantysa- 52 bity

Pierwszym i zarazem najstarszym w nomenklaturze binarnej bitem jest znak. Jedynka oznacza liczbę ujemną, zero natomiast dodatnią.

Cechę znamy ze wzoru powyżej. Pozwala ona na zakodowanie wykładnika potęgi dwójki. Jest to tak zwany zapis z nadmiarem. W przypadki zapisu z pojedynczą precyzją można zakodować wartości od -127 (osiem 0) do 128 (osiem 1), z podwójną precyzją natomiast od -1023 (jedenaście 0) do 1024 ( jedenaście 1). Nadmiar to najmniejsza możliwa do zakodowania w wykładniku liczba — odpowiednio 127 i 1023. Wartość tą będziemy dalej odejmować od kodowanej liczby.

Mantysa to właściwa liczba, która zostanie pomnożona przez wykładnik. W przypadku liczb z pojedynczą precyzją możemy zapisać liczby z zakresu ±1.18·10−38 do ±3.4·1038. Natomiast liczby zapisane z podwójną precyzją będą z zakresu ±2.2·10−308 do ±1.8·10308

Przykład

Mantysa ma zazwyczaj postać znormalizowaną, czyli wiodący, pierwszy bit jest pomijany. Najlepiej zobaczyć to można na przykładzie, gdzie mamy liczbę binarną, która przedstawiona jest w przykładzie opisanym wyżej — 1100,011. Robimy następnie przesunięcie przecinka w lewo i otrzymujemy zapis 

Pozostaje teraz wartości po prawej stronie uzupełnić zerami i mantysę liczby z pojedynczą precyzją mamy gotową. Będzie to 1000 1100 0000 0000 0000 000. 

Cechę wyliczamy w dość skomplikowany sposób, ponieważ mamy ją kodowaną z nadmiarem (tzn. BIAS, który równy jest 127) i przesuwamy przecinek o trzy miejsca w lewo, to musimy znaleźć taką cyfrę, która po odjęciu od 127 da -3. Oczywiście jest to wartość 130, więc taką więc będziemy mieć cechę, gdzie binarnie jest to 10000010. Nasza liczba 1100,011 jest dodatnia, więc pierwszy bit będzie zerowy. Cała liczba zapisana zgodnie ze standardem będzie następująca — 0 10000010 10001100000000000000000.

Polecam pobawić się tym kalkulatorem online!

Przypadki szczególne

Jest kilka przypadków, które pojawiają się, chociażby podczas programowania i wymagają odnotowania. Kojarzysz NaN, błąd podczas dzielenia przez zero czy błędy przepełnienia?

Kilka przypadków szczególnych:

  • NaN — Not a Number lub nie-liczba, czyli sytuacja, gdy wykonamy operację, która nie jest dozwolona. Na przykład pierwiastek z liczby ujemnej
  • +0 oraz -0 — możemy mieć zarówno zero dodatnie, jak i ujemne
  • Dodatnia lub ujemna nieskończoność
  • zdenormalizowane liczby — to takie liczby, które są zbyt małe, by je zapisać

Aby zweryfikować, który z przypadków szczególnych nam się trafił, możemy posłużyć się tabelą poniżej.

Kilka przypadków szczególnych.
Rysunek 3. Kilka przypadków szczególnych.

Czy wiesz, że…

Każdy z nas (no może prawie każdy) zna takie przedrostki jak centy, mili, kilo, mega, giga, decy, mikro czy tera, jednak czy znasz jednak większe lub mniejsze przedrostki liczb?

Przedrostek to prefiks, który służy do opisania dużych i małych liczb. Został stworzony e ramach Międzynarodowego Układu Jednostek Miar, czyli szeroko znanego układu SI. Został on zatwierdzony w roku 1960 i co ciekawe, układu nie przyjęły między innymi Stany Zjednoczone.

W tabelach poniżej przedrostki oraz jednostki układu SI.

Przedrostki naukowe
Rysunek 4. Przedrostki naukowe.
Siedem jednostek znajdujących się w podstawowym układzie SI.
Rysunek 5. Siedem jednostek znajdujących się w podstawowym układzie SI.

Warto zauważyć, że na przykład czas nie jest oficjalnie podawany w minutach czy godzinach. Tak samo temperatura, która jest w mniej znanych nam Kelwinach, a nie w stopniach Celsjusza.

Podsumowanie

Jak widać, temat wcale nie jest taki łatwy i oczywisty, ponieważ wiele jest tu niejasności, matematycznych aspektów i zabawy z podstawami informatyki. Jest to mój ostatni wpis przed wylotem do Tajlandii, kolejne będą już pisane w pełnym słońcu, pod palmami 🙂

Bardzo wiele tematów rodzi się po drodze i lada moment będę potrzebował wsparcia, więc Jeżeli masz pomysł na współpracę, to zostaw wiadomość!

Temat liczb w programowaniu nie jest u mnie skończony i z czasem wrócimy z aktualizacją.

Źródła

https://eduinf.waw.pl/inf/alg/006_bin/0011.php
http://lpf.wppt.pwr.edu.pl/informacje/pj.pdf 
https://www.cia.gov/library/publications/the-world-factbook/appendix/appendix-g.html 
https://eduinf.waw.pl/inf/alg/006_bin/0022.php 

Newsletter

Nie przegap i dołącz już dziś do 838 osób będących w tym Newsletter! Otrzymuj co niedzielę o godzinie 20 listę kilku ciekawych tematów, które miałem okazję obserwować w mijającym tygodniu.

Tematy będą głównie techniczne, ale czasami pojawi się coś, co może wprowadzi Cię w stan zadumy i zmusi do dyskusji w szerszym gronie. Zero spamu!

Autor

Programista .NET i Python. Autor książki "Programistą być".

8 komentarzy

  1. Świetny artykuł o typie float. Czytam teraz książke Łukasza Lamża „Przekrój przez wszechświat”. Są tam ogromie wielkie miary, jak miara obserwowalengo wszechświata, oraz bardzo małe miary.

    Polecam tą książke. Chociaż dopiero zacząłem ją czytać to myślę że jest bardzo dobra.

    Życzę ci Mateusz udanego wypoczynku w Tajlandii ??.

    Cześć.

  2. Masz błąd w mantysie przykładowej liczby 12.375 – pewnie literówka. Skoro liczba była zapisana jako 1100.011 to po przedstawieniu jej w postaci wykładniczej mamy 1.100011 x 2^3 (bo wiodący bit jest pomijany, więc przesuwamy o 3 miejsca w lewo) czyli mantysą jest 100011 uzupelnione zerami więc ostatecznie 1000110 00000000 00000000.
    Cała liczba w IEEE-754 to 01000001 01000110 00000000 00000000

    BTW Fajny artykuł

Napisz komentarz

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

SPRAWDŹ POLECANĄ KSIĄŻKĘ. Najlepsze materiały do nauki programowania!

X