Ułamki możemy zapisać w dowolnym systemie. Zasada zapisu jest identyczna z dotychczasowymi zasadami systemu pozycyjnego. Spójrzmy na przykład w systemie dziesiętnym:
10.25 = 1·10 + 0·100 + 2·10-1 + 5·10-2 = 10 + 0 + 0.2 + 0.05
Przy rozpisywaniu warto pamiętać, że jedność zawsze ma bazę podniesioną do potęgi 0 (czyli mnożnik 1). "Idąc" w lewo zwiększamy potęgę podstawy, a "idąc" w prawo zmniejszamy. Każdy jeden krok to odpowiednia zmiana o 1.
Podczas przeliczania części dodatniej nie powinno być problemu, więc zajmijmy się częścią ułamkową. Należy pamiętać, że niektórych liczba nie da się łatwo przedstawić w innym systemie, dlatego czasem będzie trzeba przyjąć swojego rodzaju precyzje, która pozwoli na uzyskanie przybliżenia. Algorytm przeliczania części ułamkowej wygląda tak:
Weźmy przykładowo ułamek 0.375, który przeliczymy na system binarny:
Krok | Aktualny ułamek | Wynik | Aktualna liczba |
---|---|---|---|
1 | 0.375 | 0.75 = 0 + 0.75 | 0.0 |
2 | 0.75 | 1.5 = 1 + 0.5 | 0.01 |
3 | 0.5 | 1.0 = 1 + 0 | 0.011 |
Kończymy obliczenia, ponieważ część ułamkowa wynosi już 0. Odczytujemy wynik, że 0.37510 = 0.0112.
Odwołując się do uwagi do precyzji - sprawdźmy teraz przeliczanie ułamka 0.1:
Krok | Aktualny ułamek | Wynik | Aktualna liczba |
---|---|---|---|
1 | 0.1 | 0.2 = 0 + 0.2 | 0.0 |
2 | 0.2 | 0.4 = 0 + 0.4 | 0.00 |
3 | 0.4 | 0.8 = 0 + 0.8 | 0.000 |
4 | 0.8 | 1.6 = 1 + 0.6 | 0.0001 |
5 | 0.6 | 1.2 = 1 + 0.2 | 0.00011 |
6 | 0.2 | 0.4 = 0 + 0.4 | 0.000110 |
... | ... | ... | ... |
Przerywamy obliczenia i analizujemy tabelkę. Łatwo zauważyć, że podążając ślepo za algorytmem nigdy nie skończymy wypisywać cyfr po przecinku, ponieważ będziemy w kółko wypisywać wyniki z kroków (2.) - (5.). Z matematycznego punktu widzenia możemy zapisać to jako: 0.0(0011). Sposobem na rozwiązanie tego problemu jest ustalenie precyzji. Precyzja określa ile cyfr po przecinku chcemy wypisać. Załóżmy, że chcemy wypisać przeliczenie 0.110 na system binarny z precyzją 7. Wynikiem będzie 0.000110010.
W celu przeliczania ułamka w bazie n na dziesiętny należy dla każdej kolejnej cyfry części ułamkowej wyliczyć jej wartość i wszystko razem zsumować. Rozpatrzmy przykładowo jeszcze raz 0.0112:
Cyfra | Wartość dziesiętna |
---|---|
0 | 0·2-1 = 0 |
1 | 1·2-2 = 1/4 |
1 | 1·2-3 = 1/8 |
Na sam koniec sumujemy wyliczone wartości i otrzymujemy, że 0.0112 = 0.37510.
Wróćmy teraz do drugiego przykładu 0.00010012.
Cyfra | Wartość dziesiętna |
---|---|
0 | 0·2-1 = 0 |
0 | 0·2-2 = 0 |
0 | 0·2-3 = 0 |
1 | 1·2-4 = 1/16 |
1 | 1·2-5 = 1/32 |
0 | 0·2-6 = 0 |
0 | 0·2-7 = 0 |
Na koniec sumujemy wartości i otrzymujemy wynik 0.00011002 = 0.0937510 co nie wynosi 0.1.
Weźmy jednak 0.1 w systemie binarnym z precyzją np. 13: 0.00011001100112 ~ 0.09997558594. Łatwo zauważyć, że im precyzja większa tym wynik jest dokładniejszy. Jest to problem znany od początku istnienia komputerów. Precyzja przechowywania danych przysparza wiele problemów każdemu kto chce przechować dane nie całkowitoliczbowe. Jednym ze sposobów jest przechowywanie liczb w postaci tekstu co nie prowadzi do utraty precyzji. Takie rozwiązanie zajmuje zdecydowanie więcej miejsca w pamięci.
W zadaniu będę korzystać z kodu poprzedniego artykułu. W tej implementacji będziemy potrzebowali funkcji, która pozwoli nam znaleźć dowolny znak w tekście:
(1.) Funkcja wyszuka nam znak c w podanej tablicy s. Jeśli nie znajdzie to (4.) zwróci -1.
(1.) Tym razem nasza funkcja będzie zwracała liczbę rzeczywistą, a nie całkowitą. Jako argumenty przyjmujemy liczbę zapisaną w tekście a oraz podstawę systemu n. (2.) Wynik będziemy też przechowywać w typie double. (3.) W tekście musimy wiedzieć, gdzie jest część całkowita i ułamkowa, dlatego wyznaczamy gdzie jest kropka. (4.) Wartości cyfr będą mnożone przez potęgi podstawy mniejsze od 1, więc będziemy potrzebowali double po raz kolejny. (5. - 8.) Przeliczanie części całkowitej wymaga tylko poprawy zakresu, gdzie znajduje się ta część. Do tej pory była to długość tekstu teraz jest to pozycja kropki. Poza tym kod bez zmian.
(9.) Upewniamy się, że we wczytanej liczbie jest część ułamkowa. (10.) Resetujemy potęgę na 1 i (11.) dla każdej cyfry z części ułamkowej (12.) wyliczamy potęgę (kolejno 1, , , ..), a wyliczoną wartość cyfry (13.) dodajemy do wyniku liczba. Na koniec (16.) zwracamy wyliczoną liczbę.
Przeliczanie liczby do kropki pozostaje bez zmian.
(1.) Zmieniamy argumenty przyjmowane przez funkcje: a - liczba do skonwertowania, n - baza systemu oraz prec - precyzja z jaka będziemy wypisywać liczbę. (2. - 7.). Wyliczamy długość części całkowitej.
Rozdzielamy podaną liczbę na (8.) część całkowitą oraz (9.) część ułamkową. (10.) Alokujemy pamięć pod tekst wynikowy. (11. - 13.) Kod konwertujący część całkowitą pozostaje bez zmian.
(15.) Tam gdzie dopisalibyśmy \0 wstawiamy teraz kropkę "." i (16.) obliczamy kolejne cyfry części ułamkowej. Dla każdego znaku precyzji zgodnie z przeliczaniem: (17.) mnożymy przez podstawę, (18.) dopisujemy wyliczoną cyfrę i (19.) zmniejszamy część ułamkową o pobraną cyfrę. (20.) Dopisujemy znak \0 i (21.) zwracamy wynik.
Poniższa funkcje main() prezentuje zastosowanie napisanych funkcji.
W celu usunięcia zer z końca w pętli wyliczającej kolejne miejsca w której po stwierdzeniu, że część ułamkowa a wyniesie 0:
Pozwoli to uniknąć wypisywania zbędnych zer na końcu część ułamkowej, ale nie jeśli istnieje taka potrzeba.
Zmodyfikuj funkcję, aby część ułamkową wprowadzało się przy pomocy przecinka ",", a nie kropki "."
Zmodyfikuj funkcję decimalToAny(), aby "ucinanie" zer było opcjonalne - ma być podawane jako dodatkowy argumenty, który jest domyślnie prawdziwy.