Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Geometry / Geometry Primitives

A straight line in two dimensions, templated on the coordinate type T. The line $ax + by + c = 0$ is canonicalized by default. With EXACT = true, coefficients are divided by their Euclidean GCD and given a fixed sign; this supports built-in integer types as well as BigInt and Rational from previous chapters. With EXACT = false, coefficients are scaled to $b = 1$ when $b$ is nonzero, or to $a = 1$ for vertical lines; this supports floating-point and Modular coefficients. Thus proportional valid coefficient vectors have the same canonical representation, up to floating-point rounding.

Set the template flag CANONICALIZE = false to preserve the supplied coefficients. EXACT defaults to true only for built-in integral types; set it explicitly for custom types such as BigInt and Rational. The internal Euclidean GCD only requires %, comparisons, and basic arithmetic. Calling canonicalized() returns a canonical copy. operator== always compares stored coefficients exactly, while EQ(l1, l2) canonicalizes raw lines if necessary and uses EPS for floating-point coefficients.

Storage and the predicates contains(), is_parallel(), and is_perpendicular() stay exact for integral type T. The inherently-fractional operations slope(), x(), and y() return fp_t, which is T when T is floating-point and double otherwise.

Operations include horizontal(), vertical(), slope(), evaluating $y$ at some $x$ with y() (and vice versa with x()), checking if a point falls on the line with contains(), checking if another line is parallel or perpendicular with is_parallel() or is_perpendicular(), and finding the parallel() or perpendicular() line through a point.

Overflow warning: the exact predicates form products of coefficients (e.g. cross terms in is_parallel()/is_perpendicular() and a*p.x + b*p.y in contains()), which grow like the squared coordinate magnitude. For integral type T, use LineL (TLine<int64_t>) once coordinates reach a few tens of thousands, or the 32-bit products may overflow.

Type aliases:

  • LineI = TLine<int>: exact integer-coefficient lines (small values only; see overflow warning)
  • LineL = TLine<int64_t>: exact integer-coefficient lines for large coordinates
  • LineD = TLine<double>: standard floating-point
  • LineLD = TLine<long double>: extra precision (where supported)
  • Line = LineD: default line type is double

Implementation

#include <cmath>
#include <cstdint>
#include <limits>
#include <ostream>
#include <type_traits>
#include <utility>

const double EPS = 1e-9;
const double M_NAN = std::numeric_limits<double>::quiet_NaN();

// Epsilon-aware for floating-point coordinates; exact for int and for coordinate types like Modular
// or Rational, which therefore compose for all of the predicates below.
template<typename T, typename U, typename C = std::common_type_t<T, U>>
bool EQ(T a, U b) {
  if constexpr (std::is_floating_point_v<C>) return C(a) == C(b) || std::fabs(C(a) - C(b)) <= EPS;
  return C(a) == C(b);
}

// SFINAE guard: valid only when Pt exposes numeric .x/.y members, so templated point constructors
// don't hijack scalar-argument calls.
template<typename Pt>
using if_point = decltype(std::declval<const Pt &>().x, std::declval<const Pt &>().y, void());

template<typename T, bool CANONICALIZE = true, bool EXACT = std::is_integral<T>::value>
struct TLine {
  // Slope and solve operations preserve floating-point coordinates and otherwise promote to double.
  using fp_t = std::conditional_t<std::is_floating_point<T>::value, T, double>;

  T a, b, c;

  void canonicalize() {
    if constexpr (EXACT) {
      auto gcd = [](T x, T y) {
        if (x < T{0}) x = -x;
        if (y < T{0}) y = -y;
        while (y != T{0}) {
          x %= y;
          std::swap(x, y);
        }
        return x;
      };
      T g = gcd(a, gcd(b, c));
      if (g != 0) {
        a /= g;
        b /= g;
        c /= g;
      }
      if (a < 0 || (a == 0 && b < 0)) {
        a = -a;
        b = -b;
        c = -c;
      }
    } else if (!EQ(b, T{0})) {
      a /= b;
      c /= b;
      b = T{1};
    } else if (!EQ(a, T{0})) {
      c /= a;
      a = T{1};
      b = T{0};
    }
  }

  TLine() : a(0), b(0), c(0) {}  // Invalid or uninitialized line.

  TLine(T a, T b, T c) : a(a), b(b), c(c) {
    if constexpr (CANONICALIZE) canonicalize();
  }

  // Line through two points. Coefficients are exact for integral points (overflow warning for c).
  template<typename Pt, typename = if_point<Pt>>
  TLine(const Pt &p, const Pt &q) : a(q.y - p.y), b(p.x - q.x), c(-(a * p.x + b * p.y)) {
    if constexpr (CANONICALIZE) canonicalize();
  }

  // Line of a given slope through a point. Intended for floating-point T (a generic slope
  // produces non-integer coefficients), so this constructor is disabled for integer T.
  template<
      class Pt, typename = if_point<Pt>, typename U = T,
      class = std::enable_if_t<std::is_floating_point<U>::value>>
  TLine(fp_t slope, const Pt &p) : a(-slope), b(1), c(slope * p.x - p.y) {
    if constexpr (CANONICALIZE) canonicalize();
  }

  TLine<T, true, EXACT> canonicalized() const { return TLine<T, true, EXACT>(a, b, c); }

  bool operator==(const TLine &l) const { return a == l.a && b == l.b && c == l.c; }
  bool operator!=(const TLine &l) const { return !(*this == l); }

  friend bool EQ(const TLine &l1, const TLine &l2) {
    if constexpr (CANONICALIZE) {
      return EQ(l1.a, l2.a) && EQ(l1.b, l2.b) && EQ(l1.c, l2.c);
    }
    return EQ(l1.canonicalized(), l2.canonicalized());
  }

  // Returns whether the line is a valid line (not both a and b zero).
  bool valid() const { return !(EQ(a, 0) && EQ(b, 0)); }
  bool horizontal() const { return valid() && EQ(a, 0); }
  bool vertical() const { return valid() && EQ(b, 0); }

  // Slope -a/b, or NaN if the line is vertical or invalid.
  fp_t slope() const { return (!valid() || EQ(b, 0)) ? M_NAN : -(fp_t)a / b; }

  // Solve for x at a given y (NaN if horizontal or invalid).
  fp_t x(fp_t y) const { return (!valid() || EQ(a, 0)) ? M_NAN : -((fp_t)b * y + c) / a; }

  // Solve for y at a given x (NaN if vertical or invalid).
  fp_t y(fp_t x) const { return (!valid() || EQ(b, 0)) ? M_NAN : -((fp_t)a * x + c) / b; }

  // Whether point p lies on the line. Exact for integer T and integer p.
  template<typename Pt>
  bool contains(const Pt &p) const {
    return valid() && EQ(a * p.x + b * p.y + c, 0);  // Overflow warning.
  }

  // Parallel iff the normals are parallel (cross == 0); perpendicular iff normals are perpendicular
  // (dot == 0). Both exact for integer T. Overflow warning.
  bool is_parallel(const TLine &l) const { return valid() && l.valid() && EQ(a * l.b, l.a * b); }

  bool is_perpendicular(const TLine &l) const {
    return valid() && l.valid() && EQ(a * l.a, -(b * l.b));
  }

  // Parallel/perpendicular line through point p. Exact for integer T and integer p.
  template<typename Pt, typename = if_point<Pt>>
  TLine parallel(const Pt &p) const {
    return TLine(a, b, -(a * p.x + b * p.y));  // Overflow warning.
  }

  template<typename Pt, typename = if_point<Pt>>
  TLine perpendicular(const Pt &p) const {
    return TLine(-b, a, b * p.x - a * p.y);  // Overflow warning.
  }

  friend std::ostream &operator<<(std::ostream &out, const TLine &l) {
    auto clean = [](T v) {
      if constexpr (std::is_floating_point_v<T>) {
        return std::fabs(v) < EPS ? T{0} : v;
      }
      return v;
    };
    auto flags = out.flags();
    out << clean(l.a) << "x" << std::showpos << clean(l.b) << "y" << clean(l.c) << "=0";
    out.flags(flags);
    return out;
  }
};

using LineI = TLine<int>;
using LineL = TLine<int64_t>;
using LineD = TLine<double>;
using LineLD = TLine<long double>;
using Line = LineD;  // Default line type is double.

// Can compose with numerical types from chapter 6:
// using LineB = TLine<BigInt, true, true>;
// using LineR = TLine<Rational<int64_t>, true, true>;

Example Usage

#include <cassert>

struct Point {
  double x, y;
  Point(double x = 0, double y = 0) : x(x), y(y) {}
};

struct PointI {
  int x, y;
  PointI(int x = 0, int y = 0) : x(x), y(y) {}
};

int main() {
  Line invalid;
  assert(!invalid.contains(Point(0, 0)) && !invalid.is_parallel(invalid));

  // Floating-point line.
  Line l(2, -5, -8);
  Line para = Line(2, -5, -8).parallel(Point(-6, -2));
  Line perp = Line(2, -5, -8).perpendicular(Point(-6, -2));
  assert(l.is_parallel(para) && l.is_perpendicular(perp));
  assert(l.slope() == 0.4);
  assert(EQ(para, Line(-0.4, 1, -0.4)));
  assert(EQ(perp, Line(2.5, 1, 17)));
  assert(l.contains(Point(1.5, -1)));  // 2(1.5) - 5(-1) - 8 = 0.

  // Integer line through integer points - exact canonical coefficients and predicates.
  LineI il(PointI(0, 0), PointI(2, 1));  // a=1, b=-2, c=0  ->  x - 2y = 0
  assert(il.a == 1 && il.b == -2 && il.c == 0);
  assert(il.contains(PointI(4, 2)));  // exact integer check
  assert(!il.contains(PointI(4, 3)));
  assert(il.is_parallel(LineI(PointI(1, 1), PointI(3, 2))));        // both slope 1/2
  assert(il.is_perpendicular(LineI(PointI(0, 0), PointI(1, -2))));  // dot of normals = 0
  assert(il.slope() == 0.5);                                        // fp_t result

  LineI reduced(2, -4, -6);
  assert(reduced == LineI(1, -2, -3));

  using RawLine = TLine<double, false>;
  RawLine raw(2, -5, -8), scaled(-0.4, 1, 1.6);
  assert(raw != scaled);    // Stored coefficients differ.
  assert(EQ(raw, scaled));  // Canonicalized copies represent the same line.
  RawLine canonical = raw;
  canonical.canonicalize();
  assert(canonical == scaled);
  assert(EQ(raw.canonicalized(), Line(-0.4, 1, 1.6)));
  return 0;
}