Classe Fraction

di Antonio Bonifati <antonio.b@infinito.it>


 
Prima bozza
Con questa classe non ci saranno più errori nei calcoli numerici. Il prezzo da pagare sarà però quello di un maggiore overhead, anche se cercheremo di ridurlo al minimo. Una limitazione della nostra classe inoltre è che non può trattare numeri irrazionali esatti (come pigreco, radice di due, il numero di Nepero, ecc.). La classe sarà quindi adatta solo per semplici problemi matematici.

Con questa classe si possono fare i calcoli con le frazioni in modo esatto e poi convertire il risultato finale in un numero floating point.

Ecco una prima semplice definizione della classe fraction:

class fraction
{
public:
   // constructors //
   fraction(): _num(0), _denom(1) {}
   fraction(long n, long d=1): _num(n), _denom(d) { /*...*/ }

   // accessors //
   long num() const { return _num; }
   long denom() const { return _denom; }
   void set_num(long) { /*...*/ }
   void set_denom(long) { /*...*/ }

protected:
   long _num, _denom;
};

La rappresentazione interna avviene semplicemente con due long che rappresentano numeratore e denominatore. Questi sono con segno, in modo da poter rappresentare frazioni negative efficientemente. C'è un costruttore senza argomenti che fornisce il valore iniziale nullo della frazione, per convenzione rappresentato con 0/1. Quindi

fraction f; // pone f=0 (0/1)

Il costruttore con due argomenti riceve nell'ordine numeratore e denominatore; se viene specificato un solo argomento, è assunto essere il numeratore e il valore di default del denominatore è 1. Questo costruttore è quindi in grado di convertire un long in un tipo fraction.

fraction f(1,3); // pone f=1/3
fraction f(-6); // pone f=-6/1, cioè -6

f=3; // crea fraction(3) e lo assegna ad f
f=fraction(2,3); // f diventa 2/3

L'unica difficoltà del costruttore con più argomenti è che occorre ridurre la frazione ai minimi termini. Se l'utente scrive:

fraction f(20,30); // deve essere memorizzato 2/3

Vogliamo memorizzare ogni frazione riducendola ai minimi termini, perchè questa è la forma preferita con cui vogliamo stampare le frazioni, ma soprattutto perchè in questo modo si incorre di meno nel pericolo di overflow quando si svolgono delle operazioni tra le frazioni. Questa è anche la ragione per cui non possiamo consentire l'accesso diretto alle variabili _num e _denom (numerator e denominator): se l'utente accedesse direttamente ad esse, potrebbe modificarle in modo che la frazione non sia più ridotta ai minimi termini. Consentiamo all'utente di ottenere il valore del numeratore e del denominatore di una frazione chiamando i due metodi num e denom:

fraction f(8,7);
cout << "numeratore: " << f.num( ) << endl; // 8
cout << "denominatore: " << f.denom( ) << endl; //7

Ho preferito chiamare queste funzioni num e denom anzichè get_num e get_denom, per risparmiare un po' di digitazione all'utente. Poichè ho scelto questi nomi per le funzioni membro, non ho potuto chiamare le variabili membro num e denom; anche qui per abbreviare, ho posto un _ all'inizio. Alcuni preferirebbero scrivere mNum, mDenom, o m_num, m_denom e chi sa quali altri varianti. E' tutta una questione di gusti, non c'è niente di concettualmente profondo. E' utile comunque avere un prefisso che permetta di distinguere le variabili membro da altri tipi di variabili, o avere dei prefissi che indicano il tipo delle variabili. Questo è utile soprattutto in quei casi in cui si deve lavorare con molte variabili contemporaneamente, poichè si può sapere tipo e tipologia della variabile guardando il nome anzichè riferirsi continuamente alle dichiarazioni, ma ha anche lo svantaggio di rendere i nomi strani e lunghi e difficoltosi da digitare.

Ci sono poi una coppia di funzioni per settare il valore del numeratore e denominatore, alle quali ho preferito non far restituire nulla.

f.set_num(4);
f.set_denom(5);
// f=4/5

Per risparmiare digitazione all'utente, definiamo anche qualche sinonimo breve di fraction:

typedef class fraction fract;
typedef class fraction frac;

il qualificatore class è necessario indicarlo se si pongono queste typedef prima della definizione della classe. Un modo per abbreviare sarebbe quello di scrivere una dichiarazione incompleta della classe prima delle typedef:

class fraction;
typedef fraction fract;
typedef fraction frac;

Inserite tutti i test visti prima in un file, così:

                        // test.cc
#include <iostream.h>
#include "frac.h"

int main()
{
   fraction f;

   { fraction f(1,3); // pone f=1/3
   }

   { fraction f(-6); // pone f=-6/1, cioè -6
   }

   { f=3; // crea fraction(3) e lo assegna ad f
   }

   { f=fraction(2,3); // f diventa 2/3
   }

   { fraction f(20,30); // deve essere memorizzato 2/3
   }

   { fraction f(8,7);
     cout << "numeratore: " << f.num( ) << endl; // 8
     cout << "denominatore: " << f.denom( ) << endl; //7

     f.set_num(4);
     f.set_denom(5);
     // f=4/5
   }

   return 0;
}

questo file si compila, e stampa (per ora) solo questo:

numeratore: 8
denominatore: 7


pag. precedente pag. iniziale pag. seguente