Professional Documents
Culture Documents
Monika Zagała
Streszczenie
Poniższa praca przedstawia projekt oraz implementacj˛e nowego typu danych
mzRational dla j˛ezyka C++, służacego
˛ do prostych operacji arytmetycznych na licz-
bach wymiernych. Artykuł wykazuje jego słabe i mocne strony, na podstawie po-
równania z liczbami zmiennoprzecinkowymi (dost˛epnymi w standardzie j˛ezyka),
precyzji wyników otrzymanych dla prostych działań matematycznych. Ponadto, zo-
stała zaproponowana arytmetyka mieszana mi˛edzy liczbami wymiernymi, a liczbami
zmiennopozycyjnymi oraz omówione zostały problemy, jakie si˛e z tym wiaż˛ a.˛
1 Wst˛ep
Wraz, z pojawieniem si˛e pierwszych maszyn liczacych,
˛ czynności zwiazane
˛ z pobieraniem
i przetwarzaniem danych liczbowych, zostały zautomatyzowane. Do wykonywania działań
arytmetycznych stosowany jest powszechnie typ zmiennopozycyjny. Niestety, w wielu
przypadkach, obliczenia wykonywane przy jego pomocy, daja˛ przybliżone rezultaty. Wy-
st˛epujace
˛ bł˛edy, sa˛ spowodowane min. brakiem skończonego rozwini˛ecia dziesi˛etnego,
dla niektórych ułamków zwykłych [1]. Inna˛ istotna˛ sprawa˛ jest kolejność wykonywania
działań. Ma ona duży wpływ na precyzj˛e otrzymywanych wyników [2].
Fakt, że reprezentacja liczb zmiennoprzecinkowych w pami˛eci komputera nie zawsze
jest precyzyjna, nasuwa ide˛e zastosowania zamiennie danych, w postaci wymiernej. Dzi˛eki
temu, że sa˛ one przedstawiane za pomoca˛ pary liczb: licznika i mianownika, uniknać ˛
można np. bł˛edów zaokraglenia
˛ liczb, które towarzysza˛ postaci dziesi˛etnej implementacji.
Ze wzgl˛edu na brak ogólnodost˛epnego typu liczb wymiernych dla j˛ezyka C++ oraz
przez wzglad ˛ na jego duże zapotrzebowanie w wielu dziedzinach nauki i techniki, została
zaprojektowana biblioteka zawierajaca ˛ zestaw algorytmów i funkcji, umożliwiajacych
wykonywanie operacji arytmetycznych, na liczbach reprezentowanych w postaci ułamków
zwykłych.
Praca zorganizowana jest w nastepujacy ˛ sposób: W rozdziale drugim przedstawiona
została reprezentacja liczb zmiennoprzecinkowych, wraz z charakterystyka˛ najcz˛eściej
spotykanych bł˛edów wystepujacych ˛ w obliczeniach. Rozdział trzeci zawiera definicj˛e
typu mzRational oraz jego porównanie z typami zmiennopozycyjnymi, na podstawie pro-
stych przykładów działań arytmetycznych. Rozdział czwarty określa zasady arytmetyki
mieszanej opracowanego typu liczb wymiernych, z istniejacymi ˛ postaciami reprezentacji
liczb rzeczywistych.
1
2 Reprezentacja liczb zmiennoprzecinkowych
Liczba zmiennoprzecinkowa (ang. floating–point number) służy do przedstawienia ogra-
niczonego przedziału liczb rzeczywistych w pami˛eci komputera. Wszystkie założenia,
zwiazane
˛ z reprezentacja˛ tego typu, zdefiniowane zostały przez standard IEEE 754 [3].
W praktyce stosowane sa˛ trzy metody wyświetlania liczb zmiennoprzecinkowych: dzie-
si˛etna, naukowa oraz inżynierska. Najbardziej popularnym zapisem jest notacja nauko-
wa[4]. Stosujac˛ dynamiczne przesuni˛ecie przecinka oraz używajac
˛ pot˛egi podstawy do o-
kreślenia jego rzeczywistego położenia, możemy reprezentować dowolne liczby za po-
moca˛ kilku cyfr [5]. Ogólny wzór wyglada˛ nast˛epujaco:
˛
zm M ∗ βzcC (1)
• Bł˛edy zaokragleń
˛ – pojawiaja˛ si˛e podczas obliczeń, na skutek konieczności zao-
kraglania
˛ wartości, ze wzgledu na ograniczona˛ długość słów binarnych. Bł˛edy te
można czasem zmniejszyć, ustalajac ˛ umiej˛etnie sposób i kolejność wykonywania
działań.
f =d+p (2)
2
float d1 = 123456.78
float d2 = 103.6003
sa˛ reprezentowane jako:
f 1 = d1 + p1 i f 2 = d2 + p2,
przy czym: f1 = 123456.781250, f2 = 103.600304 natomiast bł˛edy zaokraglenia
˛ wy-
nosza˛ odpowiednio: p1 = 0.00125 , p2 = -0.000004.
Podczas dodawania dwóch liczb zmiennoprzecinkowych mamy do czynienia, z sumo-
waniem si˛e bł˛edów:
3
Na stronie internetowej Boost’a [6] można znależć implementacj˛e typu rational dla j˛e-
zyka C++, wraz z podstawowymi algorytmami i funkcjami. Brakuje jednak operandów
dla arytmetyki mieszanej i możliwosci rzutowania typu zmiennopozycyjnego, na typ wy-
mierny. Pakiet ten jest biblioteka˛ "otwarta",˛ wciaż
˛ opracowywana.˛ Na jego podstawie
została zaprojektowana własna biblioteka mzRational, z operandami: dodawania, odejmo-
wania, mnożenia i dzielenia, a także relacji porównania. Dodatkowo zostały przeciażone
˛
operatory typów zmiennoprzecinkowych oraz zdefionowana została ich konwersja
do mzRational, wraz z funkcjami dla całej arytmetyki mieszanej.
Na podstawie przykładowych dwóch liczb: a = 1223334444.5 i b = 2e-8 zostało
dokonane porównanie operacji dodawania i mnożenia pomi˛edzy danymi typu mzRational,
gdzie poszczególne składowe ułamka zwykłego zdefiniowane zostały jako long long int
oraz liczbami zmiennoprzecinkowymi typu double.
Otrzymane wyniki były nast˛epujace:
dla wymiernej reprezentacji rezultat był prawidłowy, zarówno dla operacji dodawania
oraz mnożenia. W przypadku liczb zapisanych w postaci dziesietnej, operacja dodawania
dała wynik równy wi˛ekszemu czynnikowi, czyli wystapił typowy bład ˛ obci˛ecia, charakte-
rystyczny dla tego typu danych. Natomiast mnożenie zostało przeprowadzone precyzyjnie,
z niewielkim bł˛edem reprezentacji. Nasuwa si˛e tutaj wniosek, że typ mzRational wykazuje
zdecydowana˛ przewag˛e nad typem zmiennopozycyjnym, w operacjach dodawania (odej-
mowania) liczb skrajnie różnych.
Porównanie arytmetyki, dla dwóch innych liczb: c = 45e12 oraz d = 5e-8, wykazało,
że mnożenie wykonane zostało prawidłowo na liczbach mzRational, natomiast dodawanie
zakończyło si˛e bł˛edem spowodowanym przekroczeniem najwyższej wartości liczby typu
long long int. Podobnie, dla operacji mnożenia może wystapić
˛ overflow (underflow), czyli
tzw. bład˛ nadmiaru (niedomiaru), szczególnie wtedy, gdy redukcja ułamków zwykłych
jest niewykonalna. Zatem problem zachowania precyzji nie jest do końca rozwiazany. ˛
W tym konkretnym przypadku widoczna jest wyższość typów zmiennopozycyjnych typu
double(long double). Przy zastosowaniu liczb typu float sprawa przedstawia si˛e inaczej.
Porównanie zakresów możliwych prezentowanych wyników wypada na korzyść repre-
zentacji mzRational.
Dodatkowym atutem, reprezentacji liczb w postaci wymiernej, jest łatwość ich po-
równywania. Powszechnie wiadomo, że takie operacje na liczbach prezentowanych w
postaci ułamków dziesi˛etnych nie sa˛ możliwe. Można jedynie sprawdzić, czy dana liczba
zmiennopozycyjna mieści si˛e w pewnym jej zakresie, otoczeniu [3]. Typ mzRational
zapewnia nam operatory (<, >, ==, ! =) dla tego typu relacji, zwracajace ˛ odpowiednio
true, jeżeli została ona spełniona, w przeciwnym razie false. Poniżej znajduje si˛e fragment
implementacji operatora mniejszości:
template<typename Int>
bool mzRational<Int>::operator<( const mzRational<Int>& less){
mzRational<Int> l(*this);
mzRational<Int> r(less);
4
if(l.num < 0 && r.num >= 0) return true;
if(l.num >= 0 && r.num <= 0) return false;
if(l.den == r.den) return l.num < r.num;
l.normalize();
r.normalize();
Int gcd1 = gcd(l.num, r.num);
Int gcd2 = gcd(r.den, l.den);
return (l.num/gcd1) * (r.den/gcd2) < (l.den/gcd2) * (r.num/gcd1);
}
Funkcja normalize() służy do redukcji ułamków zwykłych, natomiast gcd(), jako re-
zultat zwraca najwi˛ekszy wspólny dzielnik. Zastosowanie operatora < wymaga zdefinio-
wania dwóch obiektów typu mzRational i porównaniu ich ze soba.˛ Ilustruje to poniższy
przykład:
int main(){
mzRational<long int> a(12, 78);
mzRational<long int> b(34, 13);
if(a < b){...}
return 0;
}
Inna˛ cecha˛ typu mzRational, jest reprezentacja wyników w postaci zredukowanych
ułamków zwykłych. Notacja dziesi˛etna jest zdecydowanie bardziej przyswajalna, od tego
rodzaju prezentacji danych. Na przykład, liczba a = 1223334444.5 zostanie przedsta-
wiona odpowiednio przez typ zmiennopozycyjny jako: 1223334444.500000000000000000
natomiast mzRational wyświetli si˛e jako: (2446668889 / 2) T˛e mała˛ niedogodność re-
kompensuje możliwość zamiany typu z mzRational na dowolny typ zmiennoprzecinkowy.
Trzeba si˛e liczyć z tym, że w niektórych przypadkach, konwersja może przyczynić si˛e, do
utraty dokładności prezentowanej liczby.
Porównanie typów: mzRational ze wszystkimi typami zmiennopozycyjnymi nie miało
na celu wykazania, który z nich jest lepszy. Zarówno jedna, jak i druga reprezentacja
niesie ze soba˛ wiele zalet i wad. Jednakże, wykazanie słabych i mocnych stron pomaga
w dobraniu odpowiedniego typu, w zależności od wykonywanych operacji.
5
template<typename Real> explicit mzRational(Real x){...}
5 Podsumowanie
Artykuł przedstawia ogólna˛ charakterystyk˛e typu mzRational. Liczby prezentowane, ja-
ko ułamki zwykłe, poszerzaja˛ dotychczasowe możliwości, o wykonywanie precyzyjne-
go dodawania (a co za tym idzie, odejmowania) liczb, szczególnie o dużej rozbieżności
wykładników. Ponadto, łatwość wykonywania porównań, takich jak: która z liczb jest
wi˛eksza, badź:
˛ czy dwie liczby sa˛ równe, czy różne - to dodatkowy atut typu mzRational.
Niestety, każdy reprezentacja danych, w pami˛eci komputera posiada pewne wady. Tak też
jest w przypadku reprezentacji wymiernej implementacji. Świadcza˛ o tym wyżej przed-
stawione przykłady. Dzi˛eki poznaniu i zrozumieniu wszelkich ograniczeń, zastosowanie
w konkretnych aplikacjach arytmetyki liczb wymiernych, staje si˛e o wiele prostsze i bar-
dziej efektywne.
Literatura
[1] W. Hebish, A. Szustalewicz, K. Tabisz, Wst˛ep do informatyki, 2002.
[2] D. Goldberg, What Every Computer Scientist Should Know About Floating-Point
Arithmetic, 1991.