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 int licznik;
  2.   private int 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.   private static ulamek dodaj(ulamek a, ulamek b) {
  2.     return new ulamek(a.licznik * b.mianownik + b.licznik * a.mianownik, a.mianownik * b.mianownik);
  3.   }

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

C++
C#
  1.   private static ulamek odejmij(ulamek a, ulamek b) {
  2.     return new ulamek(a.licznik * b.mianownik - b.licznik * a.mianownik, a.mianownik * b.mianownik);
  3.   }
C++
C#
  1.   private static ulamek mnozenie(ulamek a, ulamek b) {
  2.     return new ulamek(a.licznik * b.licznik, a.mianownik * b.mianownik);
  3.   }
C++
C#
  1.   private static ulamek dzielenie(ulamek a, ulamek b) {
  2.     return new ulamek(a.licznik * b.mianownik, a.mianownik * b.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.   private 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 ulamek(int licznik, int mianownik) {
  2.     this.licznik = licznik;
  3.     this.mianownik = mianownik;
  4.     this.redukuj();
  5.   }

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

C++
C#
  1.   public ulamek(int licznik) : this(licznik, 1) { }

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

C++
C#
  1.   public 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.   public 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.   public void odwroc() {
  2.     int t = licznik;
  3.     licznik = mianownik;
  4.     mianownik = t;
  5.   }

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.   public override string ToString() {
  2.     string s = "" + licznik;
  3.     if (mianownik != 1)
  4.       s += "/" + mianownik;
  5.     return s;
  6.   }

Dalej zostaje zdefiniowany operacje dodawania:

C++
C#
  1.   public static ulamek operator +(ulamek a, ulamek b) {
  2.     return dodaj(a, b);
  3.   }

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

C++
C#
  1.   public static ulamek operator -(ulamek a, ulamek b) {
  2.     return odejmij(a, b);
  3.   }
C++
C#
  1.   public static ulamek operator *(ulamek a, ulamek b) {
  2.     return mnozenie(a, b);
  3.   }
C++
C#
  1.   public static ulamek operator /(ulamek a, ulamek b) {
  2.     return dzielenie(a, b);
  3.   }

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.

C++
C#
  1.   public static implicit operator ulamek(int d) {
  2.     return new ulamek(d, 1);
  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 = new ulamek(13);
  3.     ulamek c3 = new 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 * new 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 *= new 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.     Console.WriteLine(c1);
  2.     Console.WriteLine(c2.pobierzWartosc());
  3.     Console.WriteLine((int)(c3.pobierzWartosc()));
  4.     Console.ReadKey();

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