Represent as a reduced pair of integers. Exact, comparable, and free of every floating-point question — at the cost of overflow risk.
Implementation
struct Frac {
long long p, q; // q > 0, gcd(|p|, q) == 1
Frac(long long p = 0, long long q = 1) : p(p), q(q) { norm(); }
void norm() {
if (q < 0) { p = -p; q = -q; }
long long g = __gcd(llabs(p), q);
if (g) { p /= g; q /= g; }
else q = 1; // p == 0
}
Frac operator+(const Frac& o) const { return Frac(p*o.q + o.p*q, q*o.q); }
Frac operator-(const Frac& o) const { return Frac(p*o.q - o.p*q, q*o.q); }
Frac operator*(const Frac& o) const { return Frac(p*o.p, q*o.q); }
Frac operator/(const Frac& o) const { return Frac(p*o.q, q*o.p); }
bool operator<(const Frac& o) const { return (__int128)p*o.q < (__int128)o.p*q; }
bool operator==(const Frac& o) const { return p == o.p && q == o.q; }
};Two details matter: normalise after every operation (otherwise denominators explode), and compare with __int128 cross-multiplication (otherwise comparison overflows long before the values do).
Overflow — the real constraint
Even reduced, denominators grow fast. Adding fractions with denominators up to can produce a denominator up to in the worst case. Practical limits:
| Situation | Feasible |
|---|---|
| A handful of operations | yes |
| Sorting / comparing given fractions | yes — comparison alone does not grow anything |
| Summing hundreds of fractions | no — overflows |
| Gaussian elimination with fractions | overflows quickly; use Bareiss |
Reduce before multiplying, not after — cross-cancel the gcds of the operands first:
Frac mul(Frac a, Frac b) {
long long g1 = __gcd(llabs(a.p), b.q), g2 = __gcd(llabs(b.p), a.q);
return Frac((a.p/g1) * (b.p/g2), (a.q/g2) * (b.q/g1));
}Alternatives
| Instead of fractions | Use |
|---|---|
| Rational answer mod a prime | — the modern contest convention |
| Comparing vs | __int128 cross-multiplication, no struct needed |
| Money / bounded decimals | scale to integers (cents) |
| Exact linear algebra | Bareiss — fraction-free by construction |
| Geometry | keep intersection points as (num_x, num_y, den) triples |
| Probabilities | modular inverses |
"Output "
Most modern problems asking for a rational answer phrase it this way, precisely so that no fraction arithmetic is needed: work in throughout, and division is multiplication by a modular inverse. This is exact and has no overflow issues.
The Stern-Brocot tree
Every positive rational appears exactly once in an infinite binary search tree built from mediants:
Navigating it finds the simplest fraction in an interval, and the path from the root is the continued fraction expansion.
// simplest fraction strictly inside (lo, hi)
Frac simplest(Frac lo, Frac hi) {
Frac l{0,1}, r{1,0}; // 0/1 and "infinity"
while (true) {
Frac m{l.p + r.p, l.q + r.q};
if (lo < m && m < hi) return m;
if (m < lo) l = m; else r = m;
}
}Uses: best rational approximation with a bounded denominator, “simplest fraction in this range” problems, and the theory behind continued fractions.
Farey sequences
is the sorted list of reduced fractions in with denominator . It has terms, and consecutive terms satisfy — which gives an way to step to the next term:
// next Farey term after p/q, with the previous term a/b
long long k = (n + b) / q;
long long np = k * p - a, nq = k * q - b;Useful for enumerating fractions in order without sorting, and for counting coprime pairs — see Euler Totient.
Continued fractions
, with the being exactly the quotients of the Euclidean algorithm on .
The convergents are the best rational approximations with bounded denominator, which is what Pell’s equation exploits.
See also: Floating Point · Modular Inverse · Pell’s Equation