Написание класса Дроби на 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