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