Denna uppgift utgör en övning på skapande av klasser som representerar en numerisk typ och som med hjälp av operatoröverlagring kan fås att för en tillämpningsprogrammerare se ut som en inbyggd typ..

 

Uppgiften går ut på att skapa och testa ut en klass representerande rationella tal. Rationella tal är bråktal av typen ½, ¾ osv. Rationella tal brukar approximeras med flyttal (double), men det är inte alltid tillfredsställande, eftersom man med flyttal inte kan representera rationella värden exakt.

 

Du skall alltså skapa klassen Rational för exakt representation av rationella tal. Det är lätt att inse att ett rationellt tal kan representeras med två heltal: täljaren och nämnaren. Dessa två värden borde inte ha några gemensamma divisorer, dvs. bråket borde vara förkortat så långt som möjligt – se längst ner för hur detta kan åstadkommas.

 

Ett Rational-objekt skall kunna konstrueras utan initialvärde (dess värde borde då initieras till 0, vilket innebär att täljaren skall vara 0 och nämnaren skall vara 1), med ett heltal som initialvärde och med två heltal (täljaren och nämnaren) som initialvärde. Exempel:
Rational r1;      // r1:s värde initieras till 0 (dvs 0/1)

Rational r2(3);           // r2:s värde initieras till 3 (dvs 3/1)

Rational r3(1, 2);   // r3:s värde initieras till ½

 

Man skall inte kunna initiera ett Rational-objekt från ett double-värde – dels är det ganska svårt att implementera på ett seriöst sätt, dels är det inte säkert att det är önskvärt eftersom vitsen med Rational-klassen är att representera rationella tal exakt, medan ett double-värde är per definition en approximation, skall t.ex. 0.3333333333 representeras som 1/3 eller som 3333333333/10000000000 ?

 

Rational-objekt skall givetvis ha värdesemantik, man skall kunna tilldela ett Rational-objekt från ett annat, skicka ett Rational-objekt som värdeargument till funktioner och returnera ett Rational-objekt från funktioner, i samtliga fall skall objekten kopieras. Du får själv avgöra om det behövs en egendefinierad copy-konstruktor, en destruktor och en egendefinierad tilldelningsoperator – meningen är att du inte skall implementera sådant som inte behövs.

 

Rational bör ha en fullständig uppsättning aritmetiska operatorer, dvs. unärt + och -, de binära operatorerna +, -, * och / samt operatorerna ++ och -- i både prefix och postfix variant (++ resp. -- skall öka resp. minska värdet med 1, om t. ex. värdet innan ++ var 1/3 så skall det efter operationen vara 4/3). Även uppdateringsoperatorer motsvarande de binära operatorerna borde finns (alltså +=, -=, *= och /=).

 

Dessutom borde en fullständig uppsättning jämförelseoperatorer finnas, alltså ==, !=, <, <=, > och >=.

 

Om ett Rational-objekt ingår i ett uttryck med double-värden borde det automatiskt konverteras till double. Det skall inte konverteras automatiskt till int för att undvika tvetydighet (eftersom det finns en konstruktor som kan konvertera ett int-värde till ett Rational-objekt), däremot borde det finnas en namngiven medlemsfunktion int_value som kan anropas för att få fram heltalsvärdet (genom att utföra heltalsdivision av täljaren med nämnaren). Vidare borde det finnas medlemsfunktioner för att avläsa nämnaren resp. täljaren.

 

Ett Rational-värde borde kunna skrivas ut på t.ex. cout med operatorn <<, det skall då skrivas ut som t.ex. 3/4 dvs täljaren, ett snedstreck, nämnaren. Man borde även kunna läsa in ett Rational-värde från cin med operatorn >>, hur detta görs kommer att visas på en föreläsning (du behöver alltså inte implementera denna operation).

 

Vid konstruktion av ett Rational-objekt från två heltal och vid utförande av aritmetiska operationer borde de resulterande värdena för täljaren och nämnaren divideras med största gemensamma nämnaren, så att ett visst värde alltid representeras på samma sätt (annars kan man få situationen att ett Rational-objekt har värdet 1/2, ett annat 2/4 och det är svårt att inse att de är lika). Dessutom borde man se till att det endast är täljaren som kan vara negativ i fall bråket är negativt.

 

En sådan "förkortning" kan göras med nedanstående funktion. Funktionen borde alltså anropas sist i varje operator-implementering samt i konstruktorn med två heltalsargument. Funktionen kan med fördel göras till en privat medlemsfunktion i klassen Rational – den behöver i så fall inte ta täljaren och nämnaren som argument utan kan operera på objektets data.

void reduce(int& numerator, int& denominator){

  // Spara förtecken och se till att värden är icke-negativa

  int sign = 1;

  if (numerator < 0){

    sign = -1;

    numerator = -numerator;

  } // if

  if (denominator < 0){

    sign = -sign;

    denominator = -denominator;

  } // if

  // Beräkna största gemensamma divisorn med Euklides algoritm

  int gcd = numerator;

  int d   = denominator;

  while (d != 0) {

    int temp = gcd % d;

    gcd = d;

    d = temp;

  } // while


  // Reducera värdena

  numerator /= gcd;

  numerator *= sign;

  denominator /= gcd;

 

} // reduce