May 3, 2020

Написание класса Дроби на C++

Это довольно классическая задача, которая рано или поздно встречается каждому программисту. Я думаю, вы знаете, что компьютер не хранит вещественные числа точно и что погрешности, как правило вызванные округлением, имеют свойство накапливаться, а потому даже небольшая погрешность потенциально может сильно исказить результат арифметических операций.

Именно из-за этого класс Дроби часто необходим при работе с большими вычислениями, поскольку помогает хранить дробные числа точно (в виде двух целых чисел) и производить промежуточные расчёты без накопления погрешности.

Новый тип данных Fraction будет иметь два поля — числитель (numerator) и знаменатель (denominator). Знак будет храниться в числителе.

class Fraction {
public:
    int numer, denom;
    Fraction(int n = 0, int d = 1) { //конструктор, на вход подаются два числа n и d
        numer = n;
        denom = d;
    }
}

Если на входе одно число, оно записывается в числитель, а знаменатель по умолчанию равен 1. В этом случае мы получаем обычное целое число. Если объект создаётся без входных параметров, по умолчанию создаётся число 0.

Арифметические операции для дробей будем реализовывать с помощью перегрузки соответствующих операторов:

Fraction operator + (Fraction& other) {
        return
            Fraction(
                numer * other.denom + denom * other.numer,
                denom * other.denom
                );
}

Вычитание выполняется как сложение с отрицательным числом:)

Fraction operator - (Fraction& other) {
        return Fraction(numer, denom) + Fraction(-other.numer, other.denom);
}

Умножение и деление (умножение на "перевёрнутую дробь")

Fraction operator * (Fraction& other) {
        return
            Fraction(
                numer * other.numer, denom * other.denom
                ).reduce();
 }
 Fraction operator / (Fraction& other) {
        return Fraction(numer, denom) * Fraction(other.denom, other.numer);
 }

Почти готово. Осталось прописать ввод/вывод дроби, и, самое интересное — сокращение дроби до несократимой. Для этого напишем функции input, show и reduce.

Начнём с сокращения: дробь станет несократимой, если числитель и знаменатель поделить на их НОД, поэтому его сначала нужно найти. Есть разные способы это сделать, я применю рекурсию (алгоритм Евклида):

int fract_nod(int a, int b) {
    if (b == 0) return a;
    return fract_nod(b, a % b);
}

Здесь важно, чтобы a было больше или равно b, а также чтобы числа были положительны, но это мы учтём в самой функции reduce.

Fraction reduce() {
        int a = max(abs(numer), abs(denom)), b = min(abs(numer), abs(denom));
        int sgn; //знак нашей дроби
        if (numer * denom >= 0) sgn = 1;
        else sgn = -1;
        int nod = fract_nod(a, b);
        return Fraction(sgn * (abs(numer) / nod), abs(denom) / nod);
}

Теперь нужно встроить эту функцию в операции сложения и умножения так, чтобы конечный результат был несократимой дробью:

Fraction operator + (Fraction& other) {
        return
            Fraction(
                numer * other.denom + denom * other.numer,
                denom * other.denom
                ).reduce(); //новое
}
Fraction operator * (Fraction& other) {
        return
            Fraction(
                numer * other.numer, denom * other.denom
                ).reduce(); //новое
}

Напишем функцию вывода:

void show() {
        cout << "\t" << numer;
        if (denom != 1) cout << "/" << denom;
}

Логично вводить дроби в том же формате, что и выводить, т.е. в виде m/n, где m — целое число, а n — натуральное. Поскольку мы пишем класс на плюсах, придётся повозиться со считыванием строки и преобразованием её в два числа (возможно, с отрицательным знаком).

Fraction input(string& s) {
    int n = 0, d = 0, deg = 0;
    int slash_pos = s.find('/'); //ищем разделитель
    if (slash_pos != s.npos) { //если он есть (число введено как дробь)
        int i = slash_pos - 1;
        //записываем в n число перед "/"
        while (i > -1) {
            if (s[i] != '-')
                n += (s[i] - 48) * pow(10, deg++); //сдвиг на 48, т.к. код "0" в ASCII = 48
            else
                n = -n;
            i -= 1;
        }
        deg = 0;
        i = s.size() - 1;
        //записываем в d число после "/"
        while (i > slash_pos) {
            if (s[i] != '-')
                d += (s[i] - 48) * pow(10, deg++);
            else
                d = -d;
            i -= 1;
        }
    } else { //если число введено как целое
        d = 1;
        int i = s.size() - 1;
        while (i > -1) {
            if (s[i] != '-')
                n += (s[i] - 48) * pow(10, deg++);
            else
                n = -n;
            i -= 1;
        }
    }
    //создаём дробь с введёнными числами и сокращаем
    Fraction f;
    if (d) f = Fraction(n, d).reduce();
    else f = Fraction(n);
    return f;
}

Готово!

Код целиком

#include <algorithm>
#include <string>
using namespace std;
int fract_nod(int a, int b) {
    if (b == 0) return a;
    return fract_nod(b, a % b);
}
class Fraction {
public:
    int numer, denom;
    Fraction(int n = 0, int d = 1) {
        numer = n;
        denom = d;
    }
    Fraction reduce() {
        int a = max(abs(numer), abs(denom)), b = min(abs(numer), abs(denom));
        int sgn;
        if (numer * denom >= 0) sgn = 1;
        else sgn = -1;
        int nod = fract_nod(a, b);
        return Fraction(sgn * (abs(numer) / nod), abs(denom) / nod);
    }
    void show() {
        cout << "\t" << numer;
        if (denom != 1) cout << "/" << denom;
    }
    Fraction operator + (Fraction& other) {
        return
            Fraction(
                numer * other.denom + denom * other.numer,
                denom * other.denom
                ).reduce();
    }
    Fraction operator - (Fraction& other) {
        return Fraction(numer, denom) + Fraction(-other.numer, other.denom);
    }
    Fraction operator * (Fraction& other) {
        return
            Fraction(
                numer * other.numer, denom * other.denom
                ).reduce();
    }
    Fraction operator / (Fraction& other) {
        return Fraction(numer, denom) * Fraction(other.denom, other.numer);
    }
};
Fraction input(string& s) {
    int n = 0, d = 0, deg = 0;
    int slash_pos = s.find('/');
    if (slash_pos != s.npos) {
        int i = slash_pos - 1;
        while (i > -1) {
            if (s[i] != '-')
                n += (s[i] - 48) * pow(10, deg++);
            else
                n = -n;
            i--;
        }
        deg = 0;
        i = s.size() - 1;
        while (i > slash_pos) {
            if (s[i] != '-')
                d += (s[i] - 48) * pow(10, deg++);
            else
                d = -d;
            i--;
        }
    } else {
        d = 1;
        int i = s.size() - 1;
        while (i > -1) {
            if (s[i] != '-')
                n += (s[i] - 48) * pow(10, deg++);
            else
                n = -n;
            i--;
        }
    }
    Fraction f;
    if (d) f = Fraction(n, d).reduce();
    else f = Fraction(n);
    return f;
}

https://tlgg.ru/Selfinstallation