I created a class for fractions (rational numbers). It contains basic operators for arithmetics, comparisons, and input and output.
I often write classes for fractions, 2D or 3D points, and log probabilities for my projects. I even use templates for underlying types sometimes. For example, 2d points could be based on int
, float
, or Fraction
.
It always takes quite some time to program those classes without bugs. I am creating a class containing all the basic operators that are needed. In the future, I will start by copying that into my project. It will contain
- constructors
- comparison operators
- arithmetic operators
- input and output operators
I think arithmetic operators are the most difficult part. They are often the most complex and I implement both assignment operator+=
and usual operator+
operators. I always implement them as member functions and non-member functions respectively. I also often have problems with the input operator >>
. Sometimes I use regex
for that.
Would you implement the same operators for the class? Would you add or remove anything? Can something be simplified? I would also like to know all C++-related issues. Are there any better or easier ways to do things? I have used the same Makefile
for all small C++ scripts I make. I would love to hear opinions about that.
Here is main.cpp
:
#include <cassert>
#include <iostream>
#include <compare>
#include <ios>
#include <numeric>
class Fraction {
public:
constexpr Fraction(): Fraction(0) {}
constexpr explicit Fraction(int numerator, int denominator = 1)
: numerator(numerator), denominator(denominator)
{
assert(denominator != 0);
reduce();
}
constexpr bool operator==(const Fraction& other) const = default;
constexpr std::strong_ordering operator<=>(const Fraction& other) const {
return numerator*other.denominator <=> other.numerator*denominator;
}
constexpr Fraction operator-() const { return Fraction { -numerator, denominator }; }
constexpr Fraction& operator+=(const Fraction& other) {
numerator = numerator*other.denominator + other.numerator*denominator;
denominator *= other.denominator;
reduce();
return *this;
}
constexpr Fraction& operator-=(const Fraction& other) {
operator+=(-other);
return *this;
}
constexpr Fraction& operator*=(const Fraction& other) {
numerator *= other.numerator;
denominator *= other.denominator;
reduce();
return *this;
}
constexpr Fraction& operator/=(const Fraction& other) {
operator*=(Fraction { other.denominator, other.numerator });
return *this;
}
friend std::ostream& operator<<(std::ostream& out, const Fraction& fraction);
friend std::istream& operator>>(std::istream& in, Fraction& fraction);
private:
constexpr void reduce() {
int gcd = std::gcd(numerator, denominator);
if (denominator < 0) gcd *= -1;
numerator /= gcd;
denominator /= gcd;
}
int numerator, denominator;
};
constexpr Fraction operator+(Fraction left, const Fraction& right) { return left += right; }
constexpr Fraction operator-(Fraction left, const Fraction& right) { return left -= right; }
constexpr Fraction operator*(Fraction left, const Fraction& right) { return left *= right; }
constexpr Fraction operator/(Fraction left, const Fraction& right) { return left /= right; }
std::ostream& operator<<(std::ostream& out, const Fraction& fraction) {
return out << fraction.numerator << '/' << fraction.denominator;
}
std::istream& operator>>(std::istream& in, Fraction& fraction) {
int denominator, numerator;
char slash;
in >> numerator >> slash >> denominator;
if (slash != '/') {
in.setstate(std::ios_base::failbit);
}
if (in) {
fraction.numerator = numerator;
fraction.denominator = denominator;
}
return in;
}
void ask_fraction() {
Fraction fraction;
std::cout << "Give fraction (for example 2/3): ";
std::cin >> fraction;
if (!std::cin) {
std::cout << "invalid input\n";
std::cin.clear();
return;
}
std::cout << fraction << '\n';
}
int main() {
Fraction x { 4, 6 };
Fraction y { -5, -10 };
Fraction z { -3 };
std::cout << x << '\n';
std::cout << y << '\n';
std::cout << z << "\n\n";
std::cout << "- " << x << " = " << -x << '\n';
std::cout << x << " + " << y << " = " << x+y << '\n';
std::cout << x << " - " << y << " = " << x-y << '\n';
std::cout << x << " * " << y << " = " << x*y << '\n';
std::cout << x << " / " << y << " = " << x/y << "\n\n";
std::cout << std::boolalpha;
std::cout << x << " == " << y << " = " << (x == y) << '\n';
std::cout << x << " != " << y << " = " << (x != y) << '\n';
std::cout << x << " < " << y << " = " << (x < y) << '\n';
std::cout << x << " <= " << y << " = " << (x <= y) << '\n';
std::cout << x << " > " << y << " = " << (x > y) << '\n';
std::cout << x << " >= " << y << " = " << (x >= y) << "\n\n";
ask_fraction();
ask_fraction();
}
Here is Makefile
:
appname = fraction
CXX = g++
CXXFLAGS = -Wall -g -std=c++2a
SRCS = main.cpp
.PHONY: test
test: $(appname)
./$(appname) < test_input.txt > user_output.txt
diff -y user_output.txt test_output.txt
.PHONY: run
run: $(appname)
./$(appname)
$(appname): $(SRCS)
$(CXX) $(CXXFLAGS) -o $(appname) $(SRCS)
.PHONY: clean
clean:
$(RM) $(appname) user_output.txt
Here is test_input.txt
:
3+2
5/3
Here is test_output.txt
:
2/3
1/2
-3/1
- 2/3 = -2/3
2/3 + 1/2 = 7/6
2/3 - 1/2 = 1/6
2/3 * 1/2 = 1/3
2/3 / 1/2 = 4/3
2/3 == 1/2 = false
2/3 != 1/2 = true
2/3 < 1/2 = false
2/3 <= 1/2 = false
2/3 > 1/2 = true
2/3 >= 1/2 = true
Give fraction (for example 2/3): invalid input
Give fraction (for example 2/3): 5/3
0
denominator ? Youassert()
sure, but what about when the code is not compiled in debug mode ? It might be worth raising a specific exception. \$\endgroup\$rational numbers
andc++
. In particular, one that I thoroughly reviewed is Implementation of a Rational Number class in C++. \$\endgroup\$