Ten artykuł rozpoczyna serię artykułów dotyczących implementacji ułamków. Konkretniej chodzi o stworzenie biblioteki, którą będzie można zaimplementować w dowolnym projekcie, aby można było swobodnie pracować na ułamkach (używając nowego typu danych). Przed przystąpieniem do pisania warto przypomnieć sobie definicję ułamka:
Definicja
Ułamek to wyrażenie , gdzie a i b to dowolne wyrażenia algebraiczne.
Implementacja
Część prywatna
Biblioteka będzie zawierać nową klasę ulamek. Pozwoli to na zadeklarowanie zmiennej tego typu w dowolnym miejscu w kodzie. Utworzenie nowej klasy rozpoczyna słowo kluczowe class i nazwa klasy
1
classulamek {
1
classulamek {
Następnie rozpoczyna się sekcja prywatna klasy. Jest to część hermetyzacji klasy. Ma to za zadanie ograniczyć co może wywołać programista na tej klasie. Pozostawienie niektórych z tych funkcji publicznych mogłoby doprowadzić do nieoczekiwanego przerwania działania programu (poprzez nieprawidłową operację). W prywatnej części są zadeklarowane dwie zmienne typu int , które odpowiadają licznikowi i mianownikowi ułamka.
1
2
private:int licznik, mianownik;
1
2
privateint licznik;
privateint mianownik;
Następnie do programu zostają wprowadzone prywatne funkcje pomocnicze. Ich zadanie polega na wykonaniu określonych operacji arytmetycznych. Przykładowo pierwsza funkcja dodaj() dodaje nowy ułamek do aktualnie przechowywanego w zmiennej.
privatestatic ulamek dzielenie(ulamek a, ulamek b) {
returnnewulamek(a.licznik * b.mianownik, a.mianownik * b.licznik);
}
Ostatnią prywatną funkcją jest nwd(), która jest implementacją szukania największego wspólnego dzielnika przy pomocy Algorytmu Euklidesa . Funkcja ta zostanie użyta podczas redukowania ułamka.
1
2
3
intnwd(int a, int b) {
return (a ==0) ?b : nwd(b % a, a);
}
1
2
3
privateintnwd(int a, int b) {
return (a == 0) ? b : nwd(b % a, a);
}
Część publiczna
Teraz w klasie zadeklarowana część publiczna. Świadczy o tym słowo kluczowe public . Część ta udostępni określone funkcje dla programisty. Pierwsza z nich to będzie konstruktor, który pozwoli utworzyć zmienną typu ulamek.
(2.) Konstruktor przyjmuje dwa argumenty _licznik oraz _mianownik. Argument _mianownik domyślnie ma wartość 1. Pozwoli to na utworzenie ułamka przy pomocy tylko wartości licznika. Są to wartości, które (3., 4.) zostaną przypisane do wcześniej zadeklarowanych zmiennych. Po przypisaniu program (5.) zredukuje ułamek.
Dodatkowo, aby można było tworzyć ułamek podając jedynie licznik zdefiniowany został konstruktor jednoargumentowy.
1
publiculamek(int licznik) : this(licznik, 1) { }
Użyta funkcja redukuj() wygląda następująco:
1
2
3
4
5
voidredukuj() {
int t = nwd(licznik, mianownik);
licznik /= t;
mianownik /= t;
}
1
2
3
4
5
publicvoidredukuj() {
int t = nwd(licznik, mianownik);
licznik /= t;
mianownik /= t;
}
(1.) Funkcja nic nie zwraca. (2.) Oblicza największy wspólny dzielnik, a potem (3., 4.) dzieli zarówno licznik i mianownik przez wyliczoną wartość.
Pomimo zwiększenia precyzji przechowywania danych programista lub użytkownik mogą chcieć, aby program wypisał wartość dziesiętną przechowywanego ułamka. W tym celu można posłużyć się funkcją pobierzWartosc(), która podzieli licznik przez mianownik i zwróci liczbę rzeczywistą.
O ile do tej pory ciężko było znaleźć przewagę nowej klasy nad typowym przechowywaniem ułamka w zmiennej typu rzeczywistego to funkcja odwroc() z pewnością to zmieni. Potrafi ona odwrócić ułamek nie tracąc żadnych danych przez błąd przybliżenia.
publicvoidodwroc() {
int t = licznik;
licznik = mianownik;
mianownik = t;
}
Operatory
Klasa ulamek jest już prawie gotowa, ale wciąż brakuje możliwości wykonywania operacji na ułamkach. Są oczywiście zadeklarowane, ale programista nie ma do nich dostępu. W celu uproszczenia wykonywanych operacji zadeklarowane zostaną operatory. Pozwoli to wykonywać operacje arytmetyczne na ułamku tak samo jak w przypadku prostych typów danych.
Pierwszy operator << ma za zadanie ułatwić wypisywanie ułamka na dowolny strumień danych. Ułamek zostanie wypisany w postaci licznik/mianownik.
1
2
3
4
5
6
friend ostream&operator<< (ostream& out, const ulamek& a) {
out << a.licznik;
if (a.mianownik !=1)
out <<"/"<< a.mianownik;
return out;
}
1
2
3
4
5
6
publicoverridestringToString() {
string s = "" + licznik;
if (mianownik != 1)
s += "/" + mianownik;
return s;
}
publicstatic ulamek operator -(ulamek a, ulamek b) {
returnodejmij(a, b);
}
1
2
3
publicstatic ulamek operator *(ulamek a, ulamek b) {
returnmnozenie(a, b);
}
1
2
3
publicstatic ulamek operator /(ulamek a, ulamek b) {
returndzielenie(a, b);
}
Pozostało teraz jeszcze dodać przykładowo konwersję liczb całkowitych na ułamki. Jest to niezwykle przydatna konwersja, która umożliwia mieszanie liczb całkowitych i ułamków nie definiując dodatkowych operatorów.
(1.) Wypisanie ułamka w postaci licznik/mianownik. (2.) Wypisanie wartości dziesiętnej ułamka oraz (3.) obcięcie części dziesiętnej wypisywanego ułamka.