Step 9 of 9
Challenge: a Fraction class
A value type represents a value, like a number or a date, and a good one keeps itself in a clean, canonical form so that equal values always look the same. A fraction is a great example: 2/4, 1/2 and -3/-6 are all the same number, so the class should always store it as 1/2.
The invariant
Always fully reduced, and the denominator is always positive. With that rule, comparing two fractions is just comparing numerators and denominators, and printing is straightforward.
Establishing it
The constructor normalizes whatever it's given:
- If the denominator is negative, flip the sign of both.
- Divide both by their greatest common divisor.
std::gcd(a, b) from <numeric> computes the greatest common divisor, and handles negative inputs.
#include <iostream>
#include <numeric>
#include <string>
class Ratio {
public:
Ratio(int a, int b) : a_(a), b_(b) {
if (b_ < 0) {
a_ = -a_;
b_ = -b_;
}
int g = std::gcd(a_, b_);
if (g != 0) {
a_ /= g;
b_ /= g;
}
}
Ratio inverse() const { return Ratio(b_, a_); }
std::string str() const { return std::to_string(a_) + ":" + std::to_string(b_); }
private:
int a_, b_;
};
int main() {
std::cout << Ratio(6, 8).str() << " " << Ratio(3, -9).str() << " " << Ratio(-2, 10).inverse().str() << "\n";
}
3:4 -1:3 -5:1
Preserving it for free
Notice inverse builds its result with the constructor. Since the constructor always normalizes, any new value created that way is automatically valid. Do the same for plus and times: compute the raw numerator and denominator with the usual math formulas (a/b + c/d = (ad + cb) / bd, and a/b x c/d = ac / bd), and let the constructor clean up.
Your turn: write class Fraction whose invariant is: always fully reduced, denominator always positive.
Fraction(int num, int den = 1):Fraction(2, -4)becomes-1/2. You can assumeden != 0.int num() const,int den() constFraction plus(const Fraction& o) constandFraction times(const Fraction& o) conststd::string str() const:"3/4", or just"2"when the denominator is 1