Strona główna » Algorytmy » Artykuły » Ułamki

Ułamki

· część 1 · część 2 ·

Cel

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

C++
C#
  1. class ulamek {

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.

C++
C#
  1. private:
  2.   int licznik, 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.

C++
C#
  1.   ulamek dodaj(const ulamek &a) {
  2.     return ulamek(licznik * a.mianownik + a.licznik * mianownik, mianownik * a.mianownik);
  3.   }

Na podobnej zasadzie działają funkcje odejmij(), mnozenie() oraz dzielenie().

C++
C#
  1.   ulamek odejmij(const ulamek &a) {
  2.     return ulamek(licznik * a.mianownik - a.licznik * mianownik, mianownik * a.mianownik);
  3.   }
C++
C#
  1.   ulamek mnozenie(const ulamek &a) {
  2.     return ulamek(licznik * a.licznik, mianownik * a.mianownik);
  3.   }
C++
C#
  1.   ulamek dzielenie(const ulamek &a) {
  2.     return ulamek(licznik * a.mianownik, mianownik * a.licznik);
  3.   }

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.

C++
C#
  1.   int nwd(int a, int b) {
  2.     return (a == 0) ? b : nwd(b % a, a);
  3.   }

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.

C++
C#
  1. public:
  2.   ulamek(int _licznik, int _mianownik = 1) {
  3.     licznik = _licznik;
  4.     mianownik = _mianownik;
  5.     redukuj();
  6.   }

(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.

Użyta funkcja redukuj() wygląda następująco:

C++
C#
  1.   void redukuj() {
  2.     int t = nwd(licznik, mianownik);
  3.     licznik /= t;
  4.     mianownik /= t;
  5.   }

(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ą.

C++
C#
  1.   double pobierzWartosc() {
  2.     return (double)licznik / (double)mianownik;
  3.   }

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.

C++
C#
  1.   void odwroc() {
  2.     *this = ulamek(mianownik, licznik);
  3.   }

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.

C++
C#
  1.   friend ostream& operator << (ostream& out, const ulamek& a) {
  2.     out << a.licznik;
  3.     if (a.mianownik != 1)
  4.       out << "/" << a.mianownik;
  5.     return out;
  6.   }

Dalej zostaje zdefiniowany operacje dodawania:

C++
C#
  1.   ulamek operator + (const ulamek &a) {
  2.     return dodaj(a);
  3.   }
C++
C#
  1.   ulamek &operator += (const ulamek &a) {
  2.     return (*this = dodaj(a));
  3.   }

Analogicznie do zadeklarowanych operatorów dodawania zostają zadeklarowane pozostałe operatory do wykonywania operacji arytmetycznych:

C++
C#
  1.   ulamek operator - (const ulamek &a) {
  2.     return odejmij(a);
  3.   }
C++
C#
  1.   ulamek &operator -= (const ulamek &a) {
  2.     return (*this = odejmij(a));
  3.   }
C++
C#
  1.   ulamek operator * (const ulamek &a) {
  2.     return mnozenie(a);
  3.   }
C++
C#
  1.   ulamek &operator *= (const ulamek &a) {
  2.     return (*this = mnozenie(a));
  3.   }
C++
C#
  1.   ulamek operator / (const ulamek &a) {
  2.     return dzielenie(a);
  3.   }
C++
C#
  1.   ulamek &operator /= (const ulamek &a) {
  2.     return (*this = dzielenie(a));
  3.   }

Na sam koniec deklaracji klasy nie należy zapomnieć o poprawnym zakończeniu klasy:

C++
C#
  1. };

Korzystanie z klasy

Deklaracja

C++
C#
  1.   ulamek c1 = 120;
  2.   ulamek c2 = ulamek(13);
  3.   ulamek c3 = ulamek(13, 3);

(1.) Tworzy ułamek. (2.) Podobnie jak w pierwszym przypadku . (3.) Utworzenie ułamka niewłaściwego

Operacje arytmetyczne

Operacje arytmetyczne można wykonać używając operatorów dwuargumentowych:

C++
C#
  1.   c1 = c1 + 3;
  2.   c1 = c1 - 6;
  3.   c2 = c2 * ulamek(7, 2);
  4.   c3 = c1 - c2;

Można też oczywiście użyć operatorów skróconych co znacznie ułatwia zapis:

C++
C#
  1.   c1 += 3;
  2.   c1 -= 6;
  3.   c2 *= ulamek(7, 2);
  4.   c3.odwroc();

Po wykonaniu wszystkich operacji wyżej można wypisać dane w następujący sposób:

C++
C#
  1.   cout << c1 << endl;
  2.   cout << c2.pobierzWartosc() << endl;
  3.   cout << (int)(c3.pobierzWartosc()) << endl;

(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.