Mathematics / Arbitrary Precision Arithmetic
6.4.4 Rational Numbers
Perform operations on rational numbers internally represented as two integers: a numerator and a denominator. The template integer type must support streamed input/output, comparisons, and arithmetic operations.
Rational<Int>(n)constructs a rational number with numeratornand denominator $1$.Rational<Int>(n, d)constructs a rational number with numeratornand denominatord.operator>>inputs a rational number using the next integer from the stream as the numerator and 1 as the denominator.operator<<outputs the rational number as a string consisting of possibly a minus sign followed by the numerator, followed by a slash, followed by the denominator.to_string(),to_llong(),to_double(), andto_ldouble()return the rational converted to anstd::string,int64_t,double, andlong doublerespectively with potential truncation or approximation for the primitive types. Equivalent conversions are also available through explicit casts toint,long long,double, andlong double.to_arithmetic<T>()is the general form behind the three numeric conversions above, returning the rational as any arithmetic typeTreadable from a stream. IntegralTtruncates toward zero.abs(),floor(), andceil()return the absolute value, floor, and ceiling.- Operators
<,>,<=,>=,==,!=,+,-,*,/,%,++,--,+=,-=,*=,/=, and%=are defined analogous to those on numerical primitives. The comparisons and binary arithmetic are hidden friends, so a raw integer operand works on either side.
Overflow warning: Internal operations do not check for overflow. Comparisons and arithmetic cross-multiply numerators and denominators, so instantiate with a wider integer type (such as __int128, or even BigInt) if the values may grow large.
Implementation
#include <cassert>
#include <cstdint>
#include <istream>
#include <ostream>
#include <sstream>
#include <string>
template<typename Int>
class Rational {
Int num, den;
public:
Rational() : num(0), den(1) {}
Rational(const Int &n) : num(n), den(1) {}
Rational(const Int &n, const Int &d) : num(n), den(d) {
assert(den != 0);
if (den < 0) {
num = -num;
den = -den;
}
Int a(num < 0 ? -num : num), b(den), tmp;
while (a != 0 && b != 0) {
tmp = a % b;
a = b;
b = tmp;
}
Int gcd = (b == 0) ? a : b;
num /= gcd;
den /= gcd;
}
friend std::istream &operator>>(std::istream &in, Rational &r) {
in >> r.num;
r.den = 1;
return in;
}
friend std::ostream &operator<<(std::ostream &out, const Rational &r) {
out << r.num << "/" << r.den;
return out;
}
std::string to_string() const {
std::stringstream ss;
ss << num << "/" << den;
return ss.str();
}
// Round-trips through a stringstream so that any Int supporting streamed I/O (e.g. a big-integer
// type) converts, even without a direct cast to the target type. Integral T truncates.
template<typename T>
T to_arithmetic() const {
std::stringstream ss;
ss << num << " " << den;
T n, d;
ss >> n >> d;
return n / d;
}
int64_t to_llong() const { return to_arithmetic<int64_t>(); }
double to_double() const { return to_arithmetic<double>(); }
long double to_ldouble() const { return to_arithmetic<long double>(); }
explicit operator int() const { return static_cast<int>(to_llong()); }
explicit operator long long() const { return static_cast<long long>(to_llong()); }
explicit operator double() const { return to_double(); }
explicit operator long double() const { return to_ldouble(); }
// The comparison and binary arithmetic operators are hidden friends, so a raw integer operand on
// either side converts through the implicit constructor.
friend bool operator<(const Rational &a, const Rational &b) {
return a.num * b.den < b.num * a.den;
}
friend bool operator==(const Rational &a, const Rational &b) {
return a.num == b.num && a.den == b.den;
}
friend bool operator>(const Rational &a, const Rational &b) { return b < a; }
friend bool operator<=(const Rational &a, const Rational &b) { return !(b < a); }
friend bool operator>=(const Rational &a, const Rational &b) { return !(a < b); }
friend bool operator!=(const Rational &a, const Rational &b) { return !(a == b); }
Rational abs() const { return Rational(num < 0 ? -num : num, den); }
friend Rational abs(const Rational &r) { return r.abs(); }
Int floor() const { return num < 0 ? -((-num + den - 1) / den) : num / den; }
Int ceil() const { return num < 0 ? -(-num / den) : (num + den - 1) / den; }
friend Rational operator+(const Rational &a, const Rational &b) {
return Rational(a.num * b.den + b.num * a.den, a.den * b.den);
}
friend Rational operator-(const Rational &a, const Rational &b) {
return Rational(a.num * b.den - b.num * a.den, a.den * b.den);
}
friend Rational operator*(const Rational &a, const Rational &b) {
return Rational(a.num * b.num, a.den * b.den);
}
friend Rational operator/(const Rational &a, const Rational &b) {
return Rational(a.num * b.den, a.den * b.num);
}
friend Rational operator%(const Rational &a, const Rational &b) {
return a - b * Rational(a.num * b.den / (b.num * a.den), 1);
}
Rational operator-() const { return Rational(-num, den); }
Rational operator++(int) { Rational t(*this); operator++(); return t; }
Rational operator--(int) { Rational t(*this); operator--(); return t; }
Rational &operator++() { *this = *this + 1; return *this; }
Rational &operator--() { *this = *this - 1; return *this; }
Rational &operator+=(const Rational &r) { *this = *this + r; return *this; }
Rational &operator-=(const Rational &r) { *this = *this - r; return *this; }
Rational &operator*=(const Rational &r) { *this = *this * r; return *this; }
Rational &operator/=(const Rational &r) { *this = *this / r; return *this; }
Rational &operator%=(const Rational &r) { *this = *this % r; return *this; }
};
Example Usage
#include <cmath>
using namespace std;
bool EQ(double a, double b) {
return fabs(a - b) < 1e-9;
}
int main() {
using Rational = Rational<int64_t>;
assert(Rational(-21, 1) % 2 == -1);
Rational r(Rational(-53, 10) % Rational(-17, 10));
assert(EQ(r.to_ldouble(), fmod(-5.3, -1.7)));
assert(r.to_string() == "-1/5");
assert(static_cast<int>(Rational(7, 2)) == 3);
assert(static_cast<long long>(Rational(7, 2)) == 3LL);
assert(EQ(static_cast<double>(Rational(1, 2)), 0.5));
// Raw integers work on either side of comparisons and arithmetic.
assert(2 + Rational(1, 2) == Rational(5, 2));
assert(1 < Rational(3, 2) && Rational(3, 2) < 2);
assert(2 % Rational(3, 4) == Rational(1, 2));
// Construction reduces the fraction and moves any sign onto the numerator.
assert(Rational(2, 4).to_string() == "1/2");
assert(Rational(1, -2).to_string() == "-1/2");
assert(Rational(5).to_string() == "5/1");
// Arithmetic between rationals reduces the result too.
assert(Rational(1, 2) + Rational(1, 3) == Rational(5, 6));
assert(Rational(1, 2) - Rational(1, 3) == Rational(1, 6));
assert(Rational(2, 3) * Rational(3, 4) == Rational(1, 2));
assert(Rational(1, 2) / Rational(3, 4) == Rational(2, 3));
// Rounding takes a different branch on each sign, and is exact on whole numbers.
assert(Rational(7, 2).floor() == 3 && Rational(7, 2).ceil() == 4);
assert(Rational(-7, 2).floor() == -4 && Rational(-7, 2).ceil() == -3);
assert(Rational(4, 2).floor() == 2 && Rational(4, 2).ceil() == 2);
assert(Rational(-7, 2).abs() == Rational(7, 2) && abs(Rational(-7, 2)) == Rational(7, 2));
// The named conversions and the general form behind them.
assert(Rational(7, 2).to_llong() == 3 && EQ(Rational(7, 2).to_double(), 3.5));
assert(Rational(7, 2).to_arithmetic<int>() == 3);
// Stream input reads one integer, and output prints the reduced fraction.
Rational streamed;
istringstream in("7");
in >> streamed;
assert(streamed == 7);
ostringstream out;
out << Rational(-3, 6);
assert(out.str() == "-1/2");
return 0;
}
/*
Perform operations on rational numbers internally represented as two integers: a numerator and a
denominator. The template integer type must support streamed input/output, comparisons, and
arithmetic operations.
- `Rational<Int>(n)` constructs a rational number with numerator `n` and denominator $1$.
- `Rational<Int>(n, d)` constructs a rational number with numerator `n` and denominator `d`.
- `operator>>` inputs a rational number using the next integer from the stream as the numerator and
1 as the denominator.
- `operator<<` outputs the rational number as a string consisting of possibly a minus sign followed
by the numerator, followed by a slash, followed by the denominator.
- `to_string()`, `to_llong()`, `to_double()`, and `to_ldouble()` return the rational converted to an
`std::string`, `int64_t`, `double`, and `long double` respectively with potential truncation or
approximation for the primitive types. Equivalent conversions are also available through explicit
casts to `int`, `long long`, `double`, and `long double`.
- `to_arithmetic<T>()` is the general form behind the three numeric conversions above, returning the
rational as any arithmetic type `T` readable from a stream. Integral `T` truncates toward zero.
- `abs()`, `floor()`, and `ceil()` return the absolute value, floor, and ceiling.
- Operators `<`, `>`, `<=`, `>=`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, `++`, `--`, `+=`, `-=`, `*=`,
`/=`, and `%=` are defined analogous to those on numerical primitives. The comparisons and binary
arithmetic are hidden friends, so a raw integer operand works on either side.
Overflow warning: Internal operations do not check for overflow. Comparisons and arithmetic
cross-multiply numerators and denominators, so instantiate with a wider integer type (such as
`__int128`, or even `BigInt`) if the values may grow large.
Time Complexity:
- O(log s) operations on `Int` per call to the constructor `Rational(n, d)`, where $s = |n| + |d|$,
spent reducing to lowest terms.
- O(1) operations on `Int` per call to everything else.
Space Complexity:
- Two `Int` values for storage of the rational number.
- O(1) auxiliary `Int` values for all operations.
*/
#include <cassert>
#include <cstdint>
#include <istream>
#include <ostream>
#include <sstream>
#include <string>
template<typename Int>
class Rational {
Int num, den;
public:
Rational() : num(0), den(1) {}
Rational(const Int &n) : num(n), den(1) {}
Rational(const Int &n, const Int &d) : num(n), den(d) {
assert(den != 0);
if (den < 0) {
num = -num;
den = -den;
}
Int a(num < 0 ? -num : num), b(den), tmp;
while (a != 0 && b != 0) {
tmp = a % b;
a = b;
b = tmp;
}
Int gcd = (b == 0) ? a : b;
num /= gcd;
den /= gcd;
}
friend std::istream &operator>>(std::istream &in, Rational &r) {
in >> r.num;
r.den = 1;
return in;
}
friend std::ostream &operator<<(std::ostream &out, const Rational &r) {
out << r.num << "/" << r.den;
return out;
}
std::string to_string() const {
std::stringstream ss;
ss << num << "/" << den;
return ss.str();
}
// Round-trips through a stringstream so that any Int supporting streamed I/O (e.g. a big-integer
// type) converts, even without a direct cast to the target type. Integral T truncates.
template<typename T>
T to_arithmetic() const {
std::stringstream ss;
ss << num << " " << den;
T n, d;
ss >> n >> d;
return n / d;
}
int64_t to_llong() const { return to_arithmetic<int64_t>(); }
double to_double() const { return to_arithmetic<double>(); }
long double to_ldouble() const { return to_arithmetic<long double>(); }
explicit operator int() const { return static_cast<int>(to_llong()); }
explicit operator long long() const { return static_cast<long long>(to_llong()); }
explicit operator double() const { return to_double(); }
explicit operator long double() const { return to_ldouble(); }
// The comparison and binary arithmetic operators are hidden friends, so a raw integer operand on
// either side converts through the implicit constructor.
friend bool operator<(const Rational &a, const Rational &b) {
return a.num * b.den < b.num * a.den;
}
friend bool operator==(const Rational &a, const Rational &b) {
return a.num == b.num && a.den == b.den;
}
// clang-format off
friend bool operator>(const Rational &a, const Rational &b) { return b < a; }
friend bool operator<=(const Rational &a, const Rational &b) { return !(b < a); }
friend bool operator>=(const Rational &a, const Rational &b) { return !(a < b); }
friend bool operator!=(const Rational &a, const Rational &b) { return !(a == b); }
// clang-format on
Rational abs() const { return Rational(num < 0 ? -num : num, den); }
friend Rational abs(const Rational &r) { return r.abs(); }
Int floor() const { return num < 0 ? -((-num + den - 1) / den) : num / den; }
Int ceil() const { return num < 0 ? -(-num / den) : (num + den - 1) / den; }
friend Rational operator+(const Rational &a, const Rational &b) {
return Rational(a.num * b.den + b.num * a.den, a.den * b.den);
}
friend Rational operator-(const Rational &a, const Rational &b) {
return Rational(a.num * b.den - b.num * a.den, a.den * b.den);
}
friend Rational operator*(const Rational &a, const Rational &b) {
return Rational(a.num * b.num, a.den * b.den);
}
friend Rational operator/(const Rational &a, const Rational &b) {
return Rational(a.num * b.den, a.den * b.num);
}
friend Rational operator%(const Rational &a, const Rational &b) {
return a - b * Rational(a.num * b.den / (b.num * a.den), 1);
}
// clang-format off
Rational operator-() const { return Rational(-num, den); }
Rational operator++(int) { Rational t(*this); operator++(); return t; }
Rational operator--(int) { Rational t(*this); operator--(); return t; }
Rational &operator++() { *this = *this + 1; return *this; }
Rational &operator--() { *this = *this - 1; return *this; }
Rational &operator+=(const Rational &r) { *this = *this + r; return *this; }
Rational &operator-=(const Rational &r) { *this = *this - r; return *this; }
Rational &operator*=(const Rational &r) { *this = *this * r; return *this; }
Rational &operator/=(const Rational &r) { *this = *this / r; return *this; }
Rational &operator%=(const Rational &r) { *this = *this % r; return *this; }
// clang-format on
};
/*** Example Usage ***/
#include <cmath>
using namespace std;
bool EQ(double a, double b) {
return fabs(a - b) < 1e-9;
}
int main() {
using Rational = Rational<int64_t>;
assert(Rational(-21, 1) % 2 == -1);
Rational r(Rational(-53, 10) % Rational(-17, 10));
assert(EQ(r.to_ldouble(), fmod(-5.3, -1.7)));
assert(r.to_string() == "-1/5");
assert(static_cast<int>(Rational(7, 2)) == 3);
assert(static_cast<long long>(Rational(7, 2)) == 3LL);
assert(EQ(static_cast<double>(Rational(1, 2)), 0.5));
// Raw integers work on either side of comparisons and arithmetic.
assert(2 + Rational(1, 2) == Rational(5, 2));
assert(1 < Rational(3, 2) && Rational(3, 2) < 2);
assert(2 % Rational(3, 4) == Rational(1, 2));
// Construction reduces the fraction and moves any sign onto the numerator.
assert(Rational(2, 4).to_string() == "1/2");
assert(Rational(1, -2).to_string() == "-1/2");
assert(Rational(5).to_string() == "5/1");
// Arithmetic between rationals reduces the result too.
assert(Rational(1, 2) + Rational(1, 3) == Rational(5, 6));
assert(Rational(1, 2) - Rational(1, 3) == Rational(1, 6));
assert(Rational(2, 3) * Rational(3, 4) == Rational(1, 2));
assert(Rational(1, 2) / Rational(3, 4) == Rational(2, 3));
// Rounding takes a different branch on each sign, and is exact on whole numbers.
assert(Rational(7, 2).floor() == 3 && Rational(7, 2).ceil() == 4);
assert(Rational(-7, 2).floor() == -4 && Rational(-7, 2).ceil() == -3);
assert(Rational(4, 2).floor() == 2 && Rational(4, 2).ceil() == 2);
assert(Rational(-7, 2).abs() == Rational(7, 2) && abs(Rational(-7, 2)) == Rational(7, 2));
// The named conversions and the general form behind them.
assert(Rational(7, 2).to_llong() == 3 && EQ(Rational(7, 2).to_double(), 3.5));
assert(Rational(7, 2).to_arithmetic<int>() == 3);
// Stream input reads one integer, and output prints the reduced fraction.
Rational streamed;
istringstream in("7");
in >> streamed;
assert(streamed == 7);
ostringstream out;
out << Rational(-3, 6);
assert(out.str() == "-1/2");
return 0;
}