Desafio C++

Classe Fração com operadores aritméticos, comparação e saída

Enunciado

Implemente uma classe Fracao que representa números racionais com numerador e denominador inteiros. Sobrecarregue operator+, operator-, operator*, operator/ para realizar as operações e simplificar o resultado. Implemente operator<=> para comparação e operator<< para imprimir no formato numerador/denominador. No main, demonstre as operações e comparações.

Requisitos

  • A classe Fracao deve ter membros num e den (int) e garantir que o denominador nunca seja zero.
  • Os operadores aritméticos devem retornar uma nova Fracao simplificada (usar std::gcd).
  • O operator<=> deve comparar frações pelo valor real (multiplicação cruzada).
  • O operator<< deve imprimir no formato num/den.
  • O programa deve exibir o resultado das quatro operações e duas comparações.

Código inicial

#include <iostream>
#include <numeric>
#include <compare>
#include <stdexcept>

class Fracao {
public:
    int num, den;
    Fracao(int n = 0, int d = 1) : num(n), den(d) {
        if (den == 0) throw std::invalid_argument("denominador zero");
        simplificar();
    }
    // TODO: operadores aritméticos, <=> e <<
private:
    void simplificar() {
        int mdc = std::gcd(num, den);
        num /= mdc;
        den /= mdc;
        if (den < 0) { num = -num; den = -den; }
    }
};

// TODO: implementar operadores

int main() {
    Fracao a(1, 2);
    Fracao b(1, 3);
    std::cout << "a + b = " << a + b << '\n';
    std::cout << "a - b = " << a - b << '\n';
    std::cout << "a * b = " << a * b << '\n';
    std::cout << "a / b = " << a / b << '\n';
    std::cout << "a < b? " << (a < b) << '\n';
    std::cout << "a == b? " << (a == b) << '\n';
    return 0;
}

Saída esperada

a + b = 5/6
a - b = 1/6
a * b = 1/6
a / b = 3/2
a < b? 0
a == b? 0
Ver dica

Para somar: (num*b.den + b.num*den) / (den*b.den). Para comparar, use num * b.den <=> b.num * den. Lembre-se de simplificar no construtor.

Mostrar solução
#include <iostream>
#include <numeric>
#include <compare>
#include <stdexcept>

class Fracao {
public:
    int num, den;

    Fracao(int n = 0, int d = 1) : num(n), den(d) {
        if (den == 0) throw std::invalid_argument("denominador zero");
        simplificar();
    }

    // Operador de soma
    Fracao operator+(const Fracao& o) const {
        return Fracao(num * o.den + o.num * den, den * o.den);
    }

    // Operador de subtração
    Fracao operator-(const Fracao& o) const {
        return Fracao(num * o.den - o.num * den, den * o.den);
    }

    // Operador de multiplicação
    Fracao operator*(const Fracao& o) const {
        return Fracao(num * o.num, den * o.den);
    }

    // Operador de divisão
    Fracao operator/(const Fracao& o) const {
        return Fracao(num * o.den, den * o.num);
    }

    // Operador three-way para comparação
    auto operator<=>(const Fracao& o) const {
        return num * o.den <=> o.num * den;
    }

    // Igualdade explícita (gerada por <=>? Não, precisamos definir)
    bool operator==(const Fracao& o) const {
        return num == o.num && den == o.den;
    }

    // Operador de saída
    friend std::ostream& operator<<(std::ostream& os, const Fracao& f);

private:
    void simplificar() {
        int mdc = std::gcd(num, den);
        num /= mdc;
        den /= mdc;
        if (den < 0) { num = -num; den = -den; }
    }
};

std::ostream& operator<<(std::ostream& os, const Fracao& f) {
    os << f.num << '/' << f.den;
    return os;
}

int main() {
    Fracao a(1, 2);
    Fracao b(1, 3);
    std::cout << "a + b = " << a + b << '\n';
    std::cout << "a - b = " << a - b << '\n';
    std::cout << "a * b = " << a * b << '\n';
    std::cout << "a / b = " << a / b << '\n';
    std::cout << "a < b? " << (a < b) << '\n';
    std::cout << "a == b? " << (a == b) << '\n';
    return 0;
}

Passo a passo

  1. A classe Fracao armazena num e den (int). O construtor verifica denominador zero, lança exceção e chama simplificar().
  2. simplificar() usa std::gcd para dividir numerador e denominador pelo máximo divisor comum e ajusta o sinal para o denominador ser positivo.
  3. Os operadores +, -, *, / são funções membro constantes que criam uma nova Fracao com o resultado e deixam o construtor simplificar.
  4. O operador <=> compara os valores reais via multiplicação cruzada: num * o.den <=> o.num * den. Isso evita divisão e mantém precisão inteira.
  5. Definimos operator== explicitamente porque <=> não gera == automaticamente (é necessário definir ambos ou usar = default no <=>).
  6. operator<< é uma função amiga que imprime num/den.
  7. No main, testamos as quatro operações e duas comparações.

Por que funciona

A sobrecarga de operadores permite que Fracao se comporte como um número. As operações aritméticas seguem as regras matemáticas e simplificam o resultado. A comparação via <=> usa multiplicação cruzada, que é correta para frações. O operador << integra a classe com std::cout.

Erros comuns

  • Esquecer de simplificar no construtor: resultados como 2/4 em vez de 1/2.
  • Não tratar denominador zero: lançar exceção ou evitar divisão por zero.
  • Comparar diretamente num < o.num sem multiplicação cruzada: isso compararia apenas numeradores, o que está errado.
  • Não definir operator== quando se usa <=>: o compilador pode não gerar == automaticamente se <=> for definido pelo usuário; é preciso defini-lo ou usar = default no <=>.

Outra forma de resolver

Usar = default no <=> e == se a comparação membro a membro for suficiente (não é o caso para frações, pois 1/2 e 2/4 são iguais em valor mas diferentes membro a membro). Para frações, a multiplicação cruzada é necessária.

Saída esperada

a + b = 5/6
a - b = 1/6
a * b = 1/6
a / b = 3/2
a < b? 0
a == b? 0