[C++] Classe Numeri Razionali

Super Squirrel
Guardando un vecchio programma di calcolo matriciale in cui gli elementi delle matrici sono frazioni, visto che sto ancora facendo pratica con le classi, ho deciso di implementare una classe per i numeri razionali.

#include <cstdint>
#include <string>

class frazione
{
    private:
        int64_t num, den;
        bool controlla_inserimento(std::string);
        void trasforma_inserimento(std::string);
        void normalizza_frazione();
    public:
        std::string inserimento;
        frazione(int64_t = 0, int64_t = 1);
        friend int64_t mcd(int64_t, int64_t);
        friend int64_t mcm(int64_t, int64_t);
        friend std::ostream& operator <<(std::ostream&, const frazione&);
        friend std::istream& operator >>(std::istream&, frazione&);
        friend frazione operator -(const frazione&);
        friend frazione operator +(const frazione&, const frazione&);
        friend frazione operator -(const frazione&, const frazione&);
        friend frazione operator *(const frazione&, const frazione&);
        friend frazione operator /(const frazione&, const frazione&);
        friend bool operator ==(const frazione&, const frazione&);
        friend bool operator !=(const frazione&, const frazione&);
        friend bool operator > (const frazione&, const frazione&);
        friend bool operator >=(const frazione&, const frazione&);
        friend bool operator < (const frazione&, const frazione&);
        friend bool operator <=(const frazione&, const frazione&);
};


#include "frazione.h"

int64_t mcd(int64_t a, int64_t b)
{
    if(a < 0)
    {
        a = -a;
    }
    if(b < 0)
    {
        b = -b;
    }
    int64_t resto = a % b;
    while(resto != 0)
    {
        a = b;
        b = resto;
        resto = a % b;
    }
    return b;
}


int64_t mcm(int64_t a, int64_t b)
{
    if(a < 0)
    {
        a = -a;
    }
    if(b < 0)
    {
        b = -b;
    }
    return a * b / mcd(a, b);
}

frazione::frazione(int64_t _num, int64_t _den)
{
    num = _num;
    den = _den;
    normalizza_frazione();
}

void frazione::normalizza_frazione()
{
    if(num == 0)
    {
        den = 1;
    }
    else
    {
        if(den < 0)
        {
            num = -num;
            den = -den;
        }
        int64_t _mcd = mcd(num, den);
        num = num / _mcd;
        den = den / _mcd;
    }
}

frazione operator -(const frazione& q1)
{
    frazione q2;
    q2.num = -q1.num;
    q2.den = q1.den;
    return q2;
}

frazione operator +(const frazione& q1, const frazione& q2)
{
    frazione q3;
    q3.den = mcm(q1.den, q2.den);
    q3.num = q3.den / q1.den * q1.num + q3.den / q2.den * q2.num;
    q3.normalizza_frazione();
    return q3;
}

frazione operator -(const frazione& q1, const frazione& q2)
{
    return q1 + (-q2);
}

frazione operator *(const frazione& q1, const frazione& q2)
{
    frazione q3;
    q3.num = q1.num * q2.num;
    q3.den = q1.den * q2.den;
    q3.normalizza_frazione();
    return q3;
}

frazione operator /(const frazione& q1, const frazione& q2)
{
    frazione q3;
    q3.num = q1.num * q2.den;
    q3.den = q1.den * q2.num;
    q3.normalizza_frazione();
    return q3;
}

bool operator ==(const frazione& q1, const frazione& q2)
{
    if(q1.num == q2.num && q1.den == q2.den)
    {
        return 1;
    }
    return 0;
}

bool operator !=(const frazione& q1, const frazione& q2)
{
    if(q1 == q2)
    {
        return 0;
    }
    return 1;
}

bool operator >(const frazione& q1, const frazione& q2)
{
    int64_t _mcm = mcm(q1.den, q2.den);
    if(_mcm / q1.den * q1.num > _mcm / q2.den * q2.num)
    {
        return 1;
    }
    return 0;
}

bool operator >=(const frazione& q1, const frazione& q2)
{
    if(q1 > q2 || q1 == q2)
    {
        return 1;
    }
    return 0;
}

bool operator <(const frazione& q1, const frazione& q2)
{
    if(q1 >= q2)
    {
        return 0;
    }
    return 1;
}

bool operator <=(const frazione& q1, const frazione& q2)
{
    if(q1 > q2)
    {
        return 0;
    }
    return 1;
}

bool frazione::controlla_inserimento(std::string v)
{
    int virgola = 0, meno = 0, fratto = 0, v_1, v_2, m_1, m_2, f;
    for(std::size_t i = 0; i < v.size(); i++)
    {
        if(v[i] < '-' || v[i] > '9')
        {
            return 0;
        }
        if(v[i] == '.')
        {
            virgola++;
            if(virgola == 1)
            {
                v_1 = i;
            }
            else if(virgola == 2)
            {
                v_2 = i;
            }
            else
            {
                return 0;
            }
        }
        if(v[i] == '-')
        {
            meno++;
            if(meno == 1)
            {
                m_1 = i;
            }
            else if(meno == 2)
            {
                m_2 = i;
            }
            else
            {
                return 0;
            }
        }
        if(v[i] == '/')
        {
            fratto++;
            if(fratto == 1)
            {
                f = i;
            }
            else
            {
                return 0;
            }
        }
    }
    if(v[0] == '.' || v[0] == '/' || v[v.size() - 1] < '0')
    {
        return 0;
    }
    if(virgola > 0)
    {
        if(v[v_1 - 1] < '0' || v[v_1 + 1] < '0')
        {
            return 0;
        }
        if(virgola == 2)
        {
            if(v[v_2 - 1] < '0' || v[v_2 + 1] < '0' || fratto == 0 || v_1 > f || v_2 < f)
            {
                return 0;
            }
        }
    }
    if(meno > 0)
    {
        if(v[m_1 + 1] < '0' || (fratto == 0 && m_1 != 0) || (fratto == 1 && m_1 != 0 && m_1 != f + 1))// warning???
        {
            return 0;
        }
        if(meno == 2)
        {
            if(v[m_2 + 1] < '0' || fratto == 0 || m_1 != 0 || m_2 != f + 1)//warning???
            {
                return 0;
            }
        }
    }
    return 1;
}

void frazione::trasforma_inserimento(std::string v)
{
    frazione q1, q2(1);
    int64_t ordine_di_grandezza = 1;
    for(int i = v.size() - 1; i >= 0; i--)
    {
        if(v[i] == '.')
        {
            q1.den = ordine_di_grandezza;
        }
        else if(v[i] == '-')
        {
            q1.num = -q1.num;
        }
        else if(v[i] == '/')
        {
            q2.num = q1.num;
            q2.den = q1.den;
            q1.num = 0;
            q1.den = 1;
            ordine_di_grandezza = 1;
        }
        else
        {
            q1.num = q1.num + (v[i] - '0') * ordine_di_grandezza;
            ordine_di_grandezza = ordine_di_grandezza * 10;
        }
    }
    q1.normalizza_frazione();
    q2.normalizza_frazione();
    *this = q1 / q2;
}

std::istream& operator >>(std::istream& in, frazione& q)
{
    do
    {
        std::cout << "Caratteri validi: . - / 0 1 2 3 4 5 6 7 8 9" << std::endl;
        in >> q.inserimento;
    }
    while(q.controlla_inserimento(q.inserimento) == 0);
    q.trasforma_inserimento(q.inserimento);
    return in;
}

std::ostream& operator <<(std::ostream& out, const frazione& q)
{
    out << q.num;
    if(q.den != 1)
    {
        out << "/" << q.den;
    }
    return out;
}


- Vorrei sapere se ci sono errori logici o di cattiva programmazione e cosa dovrei togliere o aggiungere per un buon funzionamento della classe.
- Dovrei gestire la divisione per 0? nel caso come?
- Sembrano funzionare anche operazioni tra oggetti della classe ed interi. per esempio (fissato g = 3/4) cout << g + 1 mi mostra 7/4. non capisco però come il programma riesca ad interpretare correttamente l'espressione. io ho effettuato l'overloading dell'operatore + come somma fra due oggetti frazione. l'unica spiegazione che riesco a trovare è che considera 1=1/1 grazie ad una sorta di casting automatico...

Risposte
vict85
Cominciamo con le cose semplici. Il tuo codice funziona perché nel costruttore hai messo i due argomenti come opzionali e il primo è il numeratore. Se avessi diviso tra i due costruttori, senza mettere alcun valore di default alle variabili, o se avessi segnato il costruttore come explicit non ti avrebbe funzionato. Insomma la conversione usa il costruttore assegnando l'int al primo valore e usando il valore di default per il secondo.

Hai segnato come friend oggetti che neanche usano i membri privati della classe. In ogni caso è generalmente sconsigliato avere delle funzioni o delle classi friend.

Dal punto di vista del design non vedo grossi vantaggi a non usare direttamente una struct completamente pubblica del tipo
struct funzione {
   int64_t num, den;
}
con la sola aggiunta di un costruttore. Trovo che il permettere di accedere direttamente a numeratore e denominatore sia una cosa non troppo problematica.

Il mcd e il mcm devono essere esterni alla struttura/classe (puoi anche metterli privati nella classe se non vuoi esporli pubblicamente). In ogni caso ti suggerisco di dare un occhiata al concetto dei namespace e usarli. Per le operazioni esistono modi per evitare il più possibile overflow e avere direttamente una frazione normalizzata alla fine del calcolo.

Penso sinceramente che manchi l'operazione di approssimazione di un float o double e il viceversa e l'operazione di inversione della matrice. Se proprio vuoi renderla robusta dovresti anche considerare alcune cose come la gestione delle frazioni non rappresentabili in maniera esatta (ovvero gestire il caso in cui il denominatore richiede più di 64 bit e il numeratore è sufficientemente grande da permettere una rappresentazione approssimata diversa da 0/1).

Inoltre, se stai compilando in C++11 o superiore ti suggerisco di mettere tutte le operazioni come constexpr. Ti permette di fare le operazioni a tempo di compilazione e usare la struttura in funzioni constexpr. Se usi un compilatore non aggiornato potrebbe essere inutile. Nota comunque che il C++11 già supporta una sorta di tipo frazionario (ma i suoi valori devono essere conosciuti a tempo di compilazione).

Super Squirrel
#include <iostream>
#include <cstdint>
#include <string>

class frazione
{
    private:
        int64_t num, den;
        std::string inserimento;
        bool controlla_inserimento(std::string);
        void trasforma_inserimento(std::string);
        void normalizza_frazione();
        static int64_t mcd(int64_t, int64_t);
        static int64_t mcm(int64_t, int64_t);
    public:
        frazione(int64_t = 0, int64_t = 1);
        friend std::ostream& operator <<(std::ostream&, const frazione&);
        friend std::istream& operator >>(std::istream&, frazione&);
        friend frazione operator - (const frazione&);
        friend frazione operator + (const frazione&, const frazione&);
        friend frazione operator - (const frazione&, const frazione&);
        friend frazione operator * (const frazione&, const frazione&);
        friend frazione operator / (const frazione&, const frazione&);
        friend bool     operator ==(const frazione&, const frazione&);
        friend bool     operator !=(const frazione&, const frazione&);
        friend bool     operator > (const frazione&, const frazione&);
        friend bool     operator >=(const frazione&, const frazione&);
        friend bool     operator < (const frazione&, const frazione&);
        friend bool     operator <=(const frazione&, const frazione&);
};


#include "frazione.h"

int64_t frazione::mcd(int64_t a, int64_t b)
{
    if(a < 0)
    {
        a = -a;
    }
    if(b < 0)
    {
        b = -b;
    }
    int64_t resto = a % b;
    while(resto != 0)
    {
        a = b;
        b = resto;
        resto = a % b;
    }
    return b;
}


int64_t frazione::mcm(int64_t a, int64_t b)
{
    if(a < 0)
    {
        a = -a;
    }
    if(b < 0)
    {
        b = -b;
    }
    return a * (b / mcd(a, b));
}

frazione::frazione(int64_t _num, int64_t _den)
{
    num = _num;
    den = _den;
    normalizza_frazione();
}

void frazione::normalizza_frazione()
{
    if(num == 0)
    {
        den = 1;
    }
    else
    {
        if(den < 0)
        {
            num = -num;
            den = -den;
        }
        int64_t _mcd = mcd(num, den);
        num = num / _mcd;
        den = den / _mcd;
    }
}

frazione operator -(const frazione& q1)
{
    frazione q2;
    q2.num = -q1.num;
    q2.den = q1.den;
    return q2;
}

frazione operator +(const frazione& q1, const frazione& q2)
{
    frazione q3;
    q3.den = frazione::mcm(q1.den, q2.den);
    q3.num = q3.den / q1.den * q1.num + q3.den / q2.den * q2.num;
    q3.normalizza_frazione();
    return q3;
}

frazione operator -(const frazione& q1, const frazione& q2)
{
    return q1 + (-q2);
}

frazione operator *(const frazione& q1, const frazione& q2)
{
    frazione q3(q1.num, q2.den), q4(q2.num, q1.den);
    q3.num = q3.num * q4.num;
    q3.den = q3.den * q4.den;
    return q3;
}

frazione operator /(const frazione& q1, const frazione& q2)
{
    frazione q3;
    q3.num = q2.den;
    q3.den = q2.num;
    return q1 * q3;
}

bool operator ==(const frazione& q1, const frazione& q2)
{
    if(q1.num == q2.num && q1.den == q2.den)
    {
        return 1;
    }
    return 0;
}

bool operator !=(const frazione& q1, const frazione& q2)
{
    if(q1 == q2)
    {
        return 0;
    }
    return 1;
}

bool operator >(const frazione& q1, const frazione& q2)
{
    int64_t _mcm = frazione::mcm(q1.den, q2.den);
    if(_mcm / q1.den * q1.num > _mcm / q2.den * q2.num)
    {
        return 1;
    }
    return 0;
}

bool operator >=(const frazione& q1, const frazione& q2)
{
    if(q1 > q2 || q1 == q2)
    {
        return 1;
    }
    return 0;
}

bool operator <(const frazione& q1, const frazione& q2)
{
    if(q1 >= q2)
    {
        return 0;
    }
    return 1;
}

bool operator <=(const frazione& q1, const frazione& q2)
{
    if(q1 > q2)
    {
        return 0;
    }
    return 1;
}

bool frazione::controlla_inserimento(std::string v)
{
    int virgola = 0, meno = 0, fratto = 0, v_1, v_2, m_1, m_2, f;
    for(std::size_t i = 0; i < v.size(); i++)
    {
        if(v[i] < '-' || v[i] > '9')
        {
            return 0;
        }
        if(v[i] == '.')
        {
            virgola++;
            if(virgola == 1)
            {
                v_1 = i;
            }
            else if(virgola == 2)
            {
                v_2 = i;
            }
            else
            {
                return 0;
            }
        }
        if(v[i] == '-')
        {
            meno++;
            if(meno == 1)
            {
                m_1 = i;
            }
            else if(meno == 2)
            {
                m_2 = i;
            }
            else
            {
                return 0;
            }
        }
        if(v[i] == '/')
        {
            fratto++;
            if(fratto == 1)
            {
                f = i;
            }
            else
            {
                return 0;
            }
        }
    }
    if(v[0] == '.' || v[0] == '/' || v[v.size() - 1] < '0')
    {
        return 0;
    }
    if(virgola > 0)
    {
        if(v[v_1 - 1] < '0' || v[v_1 + 1] < '0')
        {
            return 0;
        }
        if(virgola == 2)
        {
            if(v[v_2 - 1] < '0' || v[v_2 + 1] < '0' || fratto == 0 || v_1 > f || v_2 < f)
            {
                return 0;
            }
        }
    }
    if(meno > 0)
    {
        if(v[m_1 + 1] < '0' || (fratto == 0 && m_1 != 0) || (fratto == 1 && m_1 != 0 && m_1 != f + 1))// warning???
        {
            return 0;
        }
        if(meno == 2)
        {
            if(v[m_2 + 1] < '0' || fratto == 0 || m_1 != 0 || m_2 != f + 1)//warning???
            {
                return 0;
            }
        }
    }
    return 1;
}

void frazione::trasforma_inserimento(std::string v)
{
    frazione q1, q2(1);
    int64_t ordine_di_grandezza = 1;
    for(int i = v.size() - 1; i >= 0; i--)
    {
        if(v[i] == '.')
        {
            q1.den = ordine_di_grandezza;
        }
        else if(v[i] == '-')
        {
            q1.num = -q1.num;
        }
        else if(v[i] == '/')
        {
            q2.num = q1.num;
            q2.den = q1.den;
            q1.num = 0;
            q1.den = 1;
            ordine_di_grandezza = 1;
        }
        else
        {
            q1.num = q1.num + (v[i] - '0') * ordine_di_grandezza;
            ordine_di_grandezza = ordine_di_grandezza * 10;
        }
    }
    q1.normalizza_frazione();
    q2.normalizza_frazione();
    *this = q1 / q2;
}

std::istream& operator >>(std::istream& in, frazione& q)
{
    do
    {
        std::cout << "Caratteri validi: . - / 0 1 2 3 4 5 6 7 8 9" << std::endl;
        in >> q.inserimento;
    }
    while(q.controlla_inserimento(q.inserimento) == 0);
    q.trasforma_inserimento(q.inserimento);
    return in;
}

std::ostream& operator <<(std::ostream& out, const frazione& q)
{
    out << q.num;
    if(q.den != 1)
    {
        out << "/" << q.den;
    }
    return out;
}


Per le operazioni esistono modi per evitare il più possibile overflow e avere direttamente una frazione normalizzata alla fine del calcolo.


ho modificato mcm e * per ridurre la probabilità di overflow. C'è ancora modo di migliorare la classe da questo punto di vista? se si come?

Cominciamo con le cose semplici. Il tuo codice funziona perché nel costruttore hai messo i due argomenti come opzionali e il primo è il numeratore. Se avessi diviso tra i due costruttori, senza mettere alcun valore di default alle variabili, o se avessi segnato il costruttore come explicit non ti avrebbe funzionato. Insomma la conversione usa il costruttore assegnando l'int al primo valore e usando il valore di default per il secondo.


Vediamo se ho capito... in g + 1 il programma sceglie come + l'overload da me creato, quindi dovendo essere i due operandi delle frazioni vede 1 come frazione(1) e quindi il costruttore della classe viene utilizzato per un cast automatico. invece lo specificatore explicit messo davanti ad un costruttore impedisce che quest'ultimo sia utilizzato per cast automatici.
Giusto?
Quindi penso che la cosa migliore sia segnare il costruttore come explicit e creare gli overload di casting per ogni tipo in modo da avere il completo controllo sulle conversioni.

Penso sinceramente che manchi l'operazione di approssimazione di un float o double e il viceversa


Gli overload dei casting dovrebbero risolvere anche questo problema, o sbaglio?

e l'operazione di inversione della matrice


Matrice?

Hai segnato come friend oggetti che neanche usano i membri privati della classe. In ogni caso è generalmente sconsigliato avere delle funzioni o delle classi friend...
...Il mcd e il mcm devono essere esterni alla struttura/classe (puoi anche metterli privati nella classe se non vuoi esporli pubblicamente). In ogni caso ti suggerisco di dare un occhiata al concetto dei namespace e usarli.


tra quelle che avevo segnato friend e non usano membri privati ci sono solo mcd e mcm, giusto?
mcd e mcm li ho segnati static e, insieme alla stringa inserimento, li ho resi privati.
In ogni caso ho parecchi dubbi su come e dove dichiarare le varie funzioni. Tu come avresti fatto con le funzioni presenti nell header?

Inoltre, se stai compilando in C++11 o superiore ti suggerisco di mettere tutte le operazioni come constexpr. Ti permette di fare le operazioni a tempo di compilazione e usare la struttura in funzioni constexpr. Se usi un compilatore non aggiornato potrebbe essere inutile. Nota comunque che il C++11 già supporta una sorta di tipo frazionario (ma i suoi valori devono essere conosciuti a tempo di compilazione).


Preferisco per il momento non mettere altra carne a cuocere, in ogni caso in futuro approfondirò questo specificatore.

apatriarca
Che cos'è una frazione se non una coppia di numeri numeratore/denominatore? Un aspetto strano che vedo spesso nei tuoi codici è quello di non dare accesso a variabili (anche solo in lettura) che sono ovvie: in questo caso non c'è modo di accedere al numeratore e denominatore, qui non hai dato accesso alle coordinate del punto..

La conseguenza di questa mancanza è la necessità di mettere come funzioni membro o funzioni friend un sacco di funzioni che potrebbero non esserlo. Stai inoltre rendendo molto più difficile aggiungere funzioni che utilizzino funzionalità non fornite dalla tua classe.

Super Squirrel
Stai inoltre rendendo molto più difficile aggiungere funzioni che utilizzino funzionalità non fornite dalla tua classe.


Certo, su questo sono d'accordo.

La conseguenza di questa mancanza è la necessità di mettere come funzioni membro o funzioni friend un sacco di funzioni che potrebbero non esserlo.


Non ho capito cosa intendi, potresti farmi un esempio pratico con una delle funzioni?

apatriarca
La principale ragione per cui tutte le tue funzioni sono membro o friend è che le due variabili membro sono private. Fossero pubbliche potresti metterle quasi tutte come funzioni normali (non membro e non friend).

vict85
"Super Squirrel":
Per le operazioni esistono modi per evitare il più possibile overflow e avere direttamente una frazione normalizzata alla fine del calcolo.


ho modificato mcm e * per ridurre la probabilità di overflow. C'è ancora modo di migliorare la classe da questo punto di vista? se si come?


Knuth ha dedicato un intero capitolo di art of computer programming (volume 2) sui razionali. Quindi ti rimando a lui o ad altri riferimenti simili.

"Super Squirrel":
Cominciamo con le cose semplici. Il tuo codice funziona perché nel costruttore hai messo i due argomenti come opzionali e il primo è il numeratore. Se avessi diviso tra i due costruttori, senza mettere alcun valore di default alle variabili, o se avessi segnato il costruttore come explicit non ti avrebbe funzionato. Insomma la conversione usa il costruttore assegnando l'int al primo valore e usando il valore di default per il secondo.


Vediamo se ho capito... in g + 1 il programma sceglie come + l'overload da me creato, quindi dovendo essere i due operandi delle frazioni vede 1 come frazione(1) e quindi il costruttore della classe viene utilizzato per un cast automatico. invece lo specificatore explicit messo davanti ad un costruttore impedisce che quest'ultimo sia utilizzato per cast automatici.
Giusto?
Quindi penso che la cosa migliore sia segnare il costruttore come explicit e creare gli overload di casting per ogni tipo in modo da avere il completo controllo sulle conversioni.


Le conversioni sono in un certo senso degli overload dei costruttori. Quindi non ha senso esplicitarlo due volte. D'altra parte è ok metterlo come explicit, ma non è strettamente necessario. Sinceramente penso che sia meglio se fornisci direttamente l'overload delle operazioni, ma verosimilmente il codice in più non vale il beneficio in performance.

"Super Squirrel":
Penso sinceramente che manchi l'operazione di approssimazione di un float o double e il viceversa


Gli overload dei casting dovrebbero risolvere anche questo problema, o sbaglio?


NON scrivere queste operazioni come casting; scrivi una funzione chiamata approssima o qualcosa di similare. Inoltre non è necessariamente una buona idea metterlo come membro interno.

"Super Squirrel":
e l'operazione di inversione della matrice


Matrice?


Intendevo la inversione della frazione. E' equivalente a fare 1/frazione ma calcolare questa operazione è molto più costoso di scambiare i due valori tra di loro.

"Super Squirrel":
Hai segnato come friend oggetti che neanche usano i membri privati della classe. In ogni caso è generalmente sconsigliato avere delle funzioni o delle classi friend...
...Il mcd e il mcm devono essere esterni alla struttura/classe (puoi anche metterli privati nella classe se non vuoi esporli pubblicamente). In ogni caso ti suggerisco di dare un occhiata al concetto dei namespace e usarli.


tra quelle che avevo segnato friend e non usano membri privati ci sono solo mcd e mcm, giusto?
mcd e mcm li ho segnati static e, insieme alla stringa inserimento, li ho resi privati.
In ogni caso ho parecchi dubbi su come e dove dichiarare le varie funzioni. Tu come avresti fatto con le funzioni presenti nell header?


Trovo che sia meglio usare un namespace e/o definirli come funzioni static del file cpp (il namespace lo metterei indipendentemente). Attento che non parlo di funzioni membro statiche ma di questo http://www.geeksforgeeks.org/what-are-s ... ions-in-c/

"Super Squirrel":
Inoltre, se stai compilando in C++11 o superiore ti suggerisco di mettere tutte le operazioni come constexpr. Ti permette di fare le operazioni a tempo di compilazione e usare la struttura in funzioni constexpr. Se usi un compilatore non aggiornato potrebbe essere inutile. Nota comunque che il C++11 già supporta una sorta di tipo frazionario (ma i suoi valori devono essere conosciuti a tempo di compilazione).


Preferisco per il momento non mettere altra carne a cuocere, in ogni caso in futuro approfondirò questo specificatore.


Come vuoi.

Super Squirrel
Innanzitutto grazie mille per la disponibilità.

Knuth ha dedicato un intero capitolo di art of computer programming (volume 2) sui razionali. Quindi ti rimando a lui o ad altri riferimenti simili.


Opera interessante. Comunque da uno sguardo veloce al suddetto capitolo credo che la sicurezza contro l'overflow dovrebbe essere la stessa, mentre posso apportare qualche miglioria dal punto di vista dell'efficienza.

Le conversioni sono in un certo senso degli overload dei costruttori. Quindi non ha senso esplicitarlo due volte. D'altra parte è ok metterlo come explicit, ma non è strettamente necessario. Sinceramente penso che sia meglio se fornisci direttamente l'overload delle operazioni, ma verosimilmente il codice in più non vale il beneficio in performance.
...
NON scrivere queste operazioni come casting; scrivi una funzione chiamata approssima o qualcosa di similare. Inoltre non è necessariamente una buona idea metterlo come membro interno.


Scrivere l'overload delle operazioni per i vari tipi in effetti mi sembra eccessivo.
Alla fine, tenendo conto dei cast impliciti tra tipi nativi, una funzione "approssima" o un overload del costruttore per trasformare float/double in frazioni è più che sufficiente.
Per quanto riguarda invece l'approssimazione di una frazione ad un float/double scrivo un'altra funzione.

Intendevo la inversione della frazione. E' equivalente a fare 1/frazione ma calcolare questa operazione è molto più costoso di scambiare i due valori tra di loro.


Conviene farla tramite una funzione o tramite l'overload di qualche operatore?
Oltre agli overload degli operatori di incremento/decremento e di quelli in notazione compatta dovrei farne anche altri?

Trovo che sia meglio usare un namespace e/o definirli come funzioni static del file cpp (il namespace lo metterei indipendentemente). Attento che non parlo di funzioni membro statiche ma di questo http://www.geeksforgeeks.org/what-are-s ... ions-in-c/


Se definisco mcd e mcm come funzioni static nel file .cpp, non potrei inserire i prototipi delle funzioni nell'header se ho capito bene. Invece vorrei che nel file .h ci sia un elenco di tutte le funzioni definite nel file .cpp.

Ho letto del materiale sui namespace, ma non riesco ad utilizzarli nella classe. Mi spiego meglio, ho racchiuso tutto il codice del file .h nel namespace "tizio", ma sia utilizzando using namespace tizio nel file .cpp sia utilizzando :: nelle definizioni delle funzioni(sempre nel file .cpp) non compila, dando errori diversi per ognuna delle funzioni.

vict85
Devi racchiudere le implementazioni nello stesso modo in cui lo hai fatto nel .h o scrivere tizio::func al posto di func.

Super Squirrel
Ho provato col secondo metodo, sicuramente sbaglio qualcosa, ma ho problemi con gli overload degli operatori.
Alla fine mi accontento di racchiudere tutte le definizioni delle funzioni nello stesso namespace.

Ho fatto alcune aggiunte e cambiamenti alla classe anche in base ai vari suggerimenti che mi sono stati dati.

#include <iostream>
#include <cstdint>
#include <string>

namespace tizio
{
class frazione
{
private:
    int64_t num, den;
    std::string inserimento;
    bool controlla_inserimento(std::string);
    void trasforma_inserimento(std::string);
    void normalizza_frazione();
public:
    frazione(int64_t = 0, int64_t = 1);
    int64_t get_num();
    int64_t get_den();
    static frazione to_rational(double);
    friend std::ostream& operator <<(std::ostream&, const frazione&);
    friend std::istream& operator >>(std::istream&, frazione&);
    friend frazione operator - (const frazione&);
    friend frazione operator + (const frazione&, const frazione&);
    friend frazione operator - (const frazione&, const frazione&);
    friend frazione operator * (const frazione&, const frazione&);
    friend frazione operator / (const frazione&, const frazione&);
    friend bool     operator ==(const frazione&, const frazione&);
    friend bool     operator !=(const frazione&, const frazione&);
    friend bool     operator > (const frazione&, const frazione&);
    friend bool     operator >=(const frazione&, const frazione&);
    friend bool     operator < (const frazione&, const frazione&);
    friend bool     operator <=(const frazione&, const frazione&);
    frazione& operator ++();
    frazione  operator ++(int);
    frazione& operator --();
    frazione  operator --(int);
    frazione& operator +=(const frazione&);
    frazione& operator -=(const frazione&);
    frazione& operator *=(const frazione&);
    frazione& operator /=(const frazione&);
};
    int64_t mcd(int64_t, int64_t);
    int64_t mcm(int64_t, int64_t);
    double to_double (frazione&);
}


#include "frazione.h"

namespace tizio
{

int64_t mcd(int64_t a, int64_t b)
{
    if(a < 0)
    {
        a = -a;
    }
    if(b < 0)
    {
        b = -b;
    }
    int64_t resto = a % b;
    while(resto != 0)
    {
        a = b;
        b = resto;
        resto = a % b;
    }
    return b;
}

int64_t mcm(int64_t a, int64_t b)
{
    if(a < 0)
    {
        a = -a;
    }
    if(b < 0)
    {
        b = -b;
    }
    return a * (b / mcd(a, b));
}

frazione::frazione(int64_t _num, int64_t _den)
{
    num = _num;
    den = _den;
    normalizza_frazione();
}

frazione frazione::to_rational(double a)
{
    frazione q1;
    while(a - (int)a != 0)
    {
        a = a * 10;
        q1.den = q1.den * 10;
    }
    q1.num = a;
    q1.normalizza_frazione();
    return q1;
}

double to_double(frazione& q1)
{
    double a = q1.get_num();
    return a / q1.get_den();
}

int64_t frazione::get_num()
{
    return num;
}

int64_t frazione::get_den()
{
    return den;
}

void frazione::normalizza_frazione()
{
    if(num == 0)
    {
        den = 1;
    }
    else
    {
        if(den < 0)
        {
            num = -num;
            den = -den;
        }
        int64_t _mcd = mcd(num, den);
        num = num / _mcd;
        den = den / _mcd;
    }
}

frazione operator -(const frazione& q1)
{
    frazione q2;
    q2.num = -q1.num;
    q2.den = q1.den;
    return q2;
}

frazione operator +(const frazione& q1, const frazione& q2)
{
    frazione q3;
    q3.den = mcm(q1.den, q2.den);
    q3.num = q3.den / q1.den * q1.num + q3.den / q2.den * q2.num;
    q3.normalizza_frazione();
    return q3;
}

frazione operator -(const frazione& q1, const frazione& q2)
{
    return q1 + (-q2);
}

frazione operator *(const frazione& q1, const frazione& q2)
{
    frazione q3(q1.num, q2.den), q4(q2.num, q1.den);
    q3.num = q3.num * q4.num;
    q3.den = q3.den * q4.den;
    return q3;
}

frazione operator /(const frazione& q1, const frazione& q2)
{
    frazione q3;
    q3.num = q2.den;
    q3.den = q2.num;
    return q1 * q3;
}

bool operator ==(const frazione& q1, const frazione& q2)
{
    if(q1.num == q2.num && q1.den == q2.den)
    {
        return 1;
    }
    return 0;
}

bool operator !=(const frazione& q1, const frazione& q2)
{
    if(q1 == q2)
    {
        return 0;
    }
    return 1;
}

bool operator >(const frazione& q1, const frazione& q2)
{
    int64_t _mcm = mcm(q1.den, q2.den);
    if(_mcm / q1.den * q1.num > _mcm / q2.den * q2.num)
    {
        return 1;
    }
    return 0;
}

bool operator >=(const frazione& q1, const frazione& q2)
{
    if(q1 > q2 || q1 == q2)
    {
        return 1;
    }
    return 0;
}

bool operator <(const frazione& q1, const frazione& q2)
{
    if(q1 >= q2)
    {
        return 0;
    }
    return 1;
}

bool operator <=(const frazione& q1, const frazione& q2)
{
    if(q1 > q2)
    {
        return 0;
    }
    return 1;
}

frazione& frazione::operator ++()
{
    *this = *this + 1;
    return *this;
}

frazione frazione::operator ++(int)
{
    frazione q1 = *this;
    ++*this;
    return q1;
}

frazione& frazione::operator --()
{
    *this = *this - 1;
    return *this;
}

frazione frazione::operator --(int)
{
    frazione q1 = *this;
    --*this;
    return q1;
}

frazione& frazione::operator +=(const frazione& q1)
{
    *this = *this + q1;
    return *this;
}

frazione& frazione::operator -=(const frazione& q1)
{
    *this = *this - q1;
    return *this;
}

frazione& frazione::operator *=(const frazione& q1)
{
    *this = *this * q1;
    return *this;
}

frazione& frazione::operator /=(const frazione& q1)
{
    *this = *this / q1;
    return *this;
}

bool frazione::controlla_inserimento(std::string v)
{
    int virgola = 0, meno = 0, fratto = 0, v_1, v_2, m_1, m_2, f;
    for(std::size_t i = 0; i < v.size(); i++)
    {
        if(v[i] < '-' || v[i] > '9')
        {
            return 0;
        }
        if(v[i] == '.')
        {
            virgola++;
            if(virgola == 1)
            {
                v_1 = i;
            }
            else if(virgola == 2)
            {
                v_2 = i;
            }
            else
            {
                return 0;
            }
        }
        if(v[i] == '-')
        {
            meno++;
            if(meno == 1)
            {
                m_1 = i;
            }
            else if(meno == 2)
            {
                m_2 = i;
            }
            else
            {
                return 0;
            }
        }
        if(v[i] == '/')
        {
            fratto++;
            if(fratto == 1)
            {
                f = i;
            }
            else
            {
                return 0;
            }
        }
    }
    if(v[0] == '.' || v[0] == '/' || v[v.size() - 1] < '0')
    {
        return 0;
    }
    if(virgola > 0)
    {
        if(v[v_1 - 1] < '0' || v[v_1 + 1] < '0')
        {
            return 0;
        }
        if(virgola == 2)
        {
            if(v[v_2 - 1] < '0' || v[v_2 + 1] < '0' || fratto == 0 || v_1 > f || v_2 < f)
            {
                return 0;
            }
        }
    }
    if(meno > 0)
    {
        if(v[m_1 + 1] < '0' || (fratto == 0 && m_1 != 0) || (fratto == 1 && m_1 != 0 && m_1 != f + 1))// warning???
        {
            return 0;
        }
        if(meno == 2)
        {
            if(v[m_2 + 1] < '0' || fratto == 0 || m_1 != 0 || m_2 != f + 1)//warning???
            {
                return 0;
            }
        }
    }
    return 1;
}

void frazione::trasforma_inserimento(std::string v)
{
    frazione q1, q2(1);
    int64_t ordine_di_grandezza = 1;
    for(int i = v.size() - 1; i >= 0; i--)
    {
        if(v[i] == '.')
        {
            q1.den = ordine_di_grandezza;
        }
        else if(v[i] == '-')
        {
            q1.num = -q1.num;
        }
        else if(v[i] == '/')
        {
            q2.num = q1.num;
            q2.den = q1.den;
            q1.num = 0;
            q1.den = 1;
            ordine_di_grandezza = 1;
        }
        else
        {
            q1.num = q1.num + (v[i] - '0') * ordine_di_grandezza;
            ordine_di_grandezza = ordine_di_grandezza * 10;
        }
    }
    q1.normalizza_frazione();
    q2.normalizza_frazione();
    *this = q1 / q2;
}

std::istream& operator >>(std::istream& in, frazione& q)
{
    do
    {
        std::cout << "Caratteri validi: . - / 0 1 2 3 4 5 6 7 8 9" << std::endl;
        in >> q.inserimento;
    }
    while(q.controlla_inserimento(q.inserimento) == 0);
    q.trasforma_inserimento(q.inserimento);
    return in;
}

std::ostream& operator <<(std::ostream& out, const frazione& q)
{
    out << q.num;
    if(q.den != 1)
    {
        out << "/" << q.den;
    }
    return out;
}

}


- C'è qualcosa da modificare o aggiungere?
- E' una domanda stupida lo so, ma con quale criterio dovrei scegliere il nome del namespace?
- Ho provato a mettere la funzione to_rational come friend, ma poi non riesco a richiamarla nel main. Come dovrei richiamarla in questo caso?
- Ho provato al posto della funzione to_rational anche l'overload del costruttore in modo da rendere il tutto un po' più automatico, ma ho avuto vari problemi. Infatti se dichiaro frazione(double) mi dà errori di ambiguità, mentre se dichiaro frazione(double&) funziona, ma solo se l'argomento è una variabile double giustamente, infatti se inserisco un valore numerico con la virgola mi considera solo la parte intera. Vale la pena di insistere sull'overload? e nel caso come dovrei fare?

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.