Geometry Basics

Computational geometry punishes sloppiness. The two decisions that determine whether your solution works are made before you write any algorithm: what number type, and what comparison rule.

Rule 1: stay in integers if you possibly can

With integer input coordinates, these are all exact integer computations:

Only when you need an actual intersection point, a length, or an angle must you leave the integers. Delay that as long as possible.

Overflow: with coordinates up to , a difference reaches and a cross product reaches — inside long long () but only just. A squared distance times another squared distance, or a determinant used for the in-circle test, is not. Use __int128 when in doubt.

Rule 2: one epsilon, used consistently

const double EPS = 1e-9;
int sgnd(double x) { return (x > EPS) - (x < -EPS); }
bool eq(double a, double b) { return fabs(a - b) < EPS; }

Never write a == b on floating point. Choose relative to the coordinate magnitude: with values around and double’s 15-16 significant digits, absolute precision is about — so is too small and will misclassify. Use there, or switch to long double, or rescale.

Rule 3: compare by squared distance, never by square root

if (norm2(a - p) < norm2(b - p)) /* a is closer to p than b, exact */;

Taking a square root just to compare two distances throws away exactness for nothing — is monotone, so the comparison on squares gives the same answer. Same for “is this distance ”: compare . Only take the square root when a length must appear in the output.

Rule 4: prefer cross-product sorting to atan2

gives the angle in and handles all four quadrants, but sorting by it is slow (a transcendental call per comparison) and, on nearly-opposite vectors, imprecise. Angular sort by half-plane and cross product instead — exact for integer input and noticeably faster.

Common degeneracies to test

The cases that break geometry code

  • Collinear points (a hull with all points on a line)
  • Duplicate points
  • Zero-length segments
  • or
  • Polygons given clockwise instead of counter-clockwise
  • Points exactly on a boundary (in / out / on?)
  • Vertical lines (a slope of infinity)
  • Segments that touch at an endpoint
  • Concave and self-intersecting polygons where the algorithm assumes convex

Write the degenerate cases into your own test file before submitting. In geometry they are the rule, not the exception.

Orientation convention

Use counter-clockwise positive throughout: means is counter-clockwise from . Store polygons CCW; if the input might be CW, check the signed area and reverse.

Precision escape hatches

  1. Rational arithmetic — store fractions as pair<long long,long long> with reduction. Exact but slow and prone to overflow.
  2. long double — 18-19 significant digits on x86, one line to switch, often enough.
  3. __int128 — for exact integer products that exceed 64 bits.
  4. Rescale the input — if coordinates are huge but the answer only depends on differences, translate to the centroid first.
  5. Adaptive exact predicates (Shewchuk) — a fast floating-point estimate with a certified error bound, falling back to exact arithmetic only when the estimate is inconclusive. What production geometry kernels do; rarely needed in contest code because inputs are usually integral.

The point struct

A point and a vector are the same pair of numbers; the difference is interpretation. Treating points as position vectors and differences as direction vectors removes most of the case analysis from geometry code.

struct P {
    ll x, y;
    P operator+(const P& o) const { return {x + o.x, y + o.y}; }
    P operator-(const P& o) const { return {x - o.x, y - o.y}; }
    P operator*(ll k) const { return {x * k, y * k}; }
    bool operator<(const P& o) const { return x != o.x ? x < o.x : y < o.y; }
    bool operator==(const P& o) const { return x == o.x && y == o.y; }
};
 
ll cross(const P& a, const P& b) { return a.x * b.y - a.y * b.x; }
ll cross(const P& o, const P& a, const P& b) { return cross(a - o, b - o); }   // relative to o
ll dot(const P& a, const P& b)   { return a.x * b.x + a.y * b.y; }
ll dot(const P& o, const P& a, const P& b) { return dot(a - o, b - o); }
ll norm2(const P& a) { return dot(a, a); }                 // squared length
double abs_(const P& a) { return sqrt((double)norm2(a)); }
ll dist2(const P& a, const P& b) { return norm2(a - b); }  // exact, use for comparisons
double dist(const P& a, const P& b) { return sqrt((double)dist2(a, b)); }
 
struct PD { double x, y; };                          // the floating-point twin of P
PD operator+(PD a, PD b) { return {a.x + b.x, a.y + b.y}; }
PD operator-(PD a, PD b) { return {a.x - b.x, a.y - b.y}; }
PD operator*(PD a, double k) { return {a.x * k, a.y * k}; }
PD toPD(P a) { return {(double)a.x, (double)a.y}; }
double cross(PD a, PD b) { return a.x * b.y - a.y * b.x; }     // same names, overloaded on PD
double cross(PD o, PD a, PD b) { return cross(a - o, b - o); }
double dot(PD a, PD b)   { return a.x * b.x + a.y * b.y; }
double dot(PD o, PD a, PD b) { return dot(a - o, b - o); }
double norm2(PD a)       { return dot(a, a); }
double abs_(PD a)        { return sqrt(norm2(a)); }

Two operations do almost all the work: cross (orientation, area, parallelism) and dot (angle sign, projection, perpendicularity).

Never scale an integer P by a double

P’s operator* takes a ll; passing a double silently truncates it to an integer before multiplying, which is almost never what you want ( becomes ). Any construction that is inherently fractional — rotation by a general angle, scaling to a target length, a point partway along a segment — must live in PD, not P. The functions below take P input (exact) but return PD.

Standard transformations

P perp(P a) { return {-a.y, a.x}; }                  // rotate 90 degrees CCW, exact, stays in P
PD perp(PD a) { return {-a.y, a.x}; }                // same, for a floating direction
PD rotate(P a, double t) {                           // rotate by angle t
    return {a.x * cos(t) - a.y * sin(t), a.x * sin(t) + a.y * cos(t)};
}
PD scaleTo(P a, double len) { double d = abs_(a); return {a.x * len / d, a.y * len / d}; }
PD reflectAcross(P p, P a, P b) {                    // reflect p across line ab
    P d = b - a;
    double t = (double)dot(p - a, d) / norm2(d);
    PD proj = toPD(a) + toPD(d) * t;
    return proj * 2 - toPD(p);
}

perp is exact in integers and is the most useful of these — perpendicular bisectors, normals, and half-plane directions all come from it.

Complex numbers as points

std::complex<double> gives vector arithmetic for free:

using Pc = complex<double>;
double dotC(Pc a, Pc b)   { return (conj(a) * b).real(); }
double crossC(Pc a, Pc b) { return (conj(a) * b).imag(); }
Pc rotateC(Pc a, double t){ return a * polar(1.0, t); }
// abs(a); arg(a); norm(a);                            // length, angle, squared length

Multiplication by is rotation, which makes rotation code trivial, and conj(a)*b packs dot into the real part and cross into the imaginary part in one multiplication. The drawbacks: it forces floating point (losing integer exactness), and norm means squared length in <complex>, which surprises people. Use it for rotation-heavy problems, a plain integer struct P otherwise.

Dot Product

The dot product measures alignment.

What the sign means

TaskTest
Is the angle at in triangle obtuse?
Is the triangle right-angled?some
Is point within the “slab” of segment (perpendicular to through , and through )? and
Are two lines perpendicular?direction
Is on ray (given collinear)?

Projection

// closest point on the LINE through a,b to point p
double t = (double)dot(p - a, b - a) / norm2(b - a);
PD closest = toPD(a) + toPD(b - a) * t;
 
// closest point on the SEGMENT: clamp t to [0,1] first

The clamp is the whole difference between “closest point on the line” and “closest point on the segment” — a one-line change that is easy to forget.

double distToSegment(P p, P a, P b) {
    if (a == b) return dist(p, a);
    double t = (double)dot(p - a, b - a) / norm2(b - a);
    t = max(0.0, min(1.0, t));
    double px = a.x + (b.x - a.x) * t, py = a.y + (b.y - a.y) * t;
    return hypot(p.x - px, p.y - py);
}

Squared distance — use it, not the root

Exact in integers, no square root, and order-preserving (Rule 3). Use it for comparing distances, “is this within radius ” (), nearest-neighbour searches, and closest pair.

Law of cosines and the angle between vectors


is exactly the identity proved above, rearranged. For the magnitude of the angle itself:

double angle(P a, P b) {            // angle between vectors, in [0, pi]
    return atan2(fabs((double)cross(a, b)), (double)dot(a, b));
}

Dot and cross together

WantUse
Angle magnitude
Signed angle — gives
Perpendicular?
Parallel?
Which side?sign of
In front or behind?sign of

The pair is exactly of the complex number , which is why complex arithmetic works so smoothly for 2D geometry (see above).

In 3D

The dot product is unchanged: , still giving perpendicularity, projection and the angle via the same law-of-cosines argument. Only the cross product changes character (scalar vector) in 3D — see 3D Geometry.

Cross Product

The single most important primitive in 2D geometry. Exact in integers, and it answers orientation, area and parallelism at once.

What the sign means

This is the orientation test, and it is the basis of:

TaskTest
Is left of the line ?
Are three points collinear?
Convex hull turn directionsign of the cross product
Do segments and straddle each other?opposite signs on both sides
Is a polygon CCW?signed area
Is a polygon convex?all cross products have the same sign
Point inside a convex polygonsame sign for every edge

What the magnitude means

so the triangle area is , and — crucially — twice the area is an integer for integer input. Work with throughout and never divide until output.

Shoelace formula


for a simple polygon with . The signed version tells you the orientation as a bonus — see Polygon Area for the proof (it is the triangle-area identity above, telescoped over a fan from the origin).

Distance from a point to a line

double distToLine(P p, P a, P b) {
    return fabs((double)cross(b - a, p - a)) / abs_(b - a);
}

This is immediate from the area identity: is twice the area of triangle , which also equals (base times height); divide.

For comparisons only, skip the division and the square root entirely: keep numerator squared and denominator squared separately and cross-multiply in __int128.

The sgn idiom and orientation

Most uses only need the sign, not the value:

int sgn(ll x) { return (x > 0) - (x < 0); }
int orient(P a, P b, P c) { return sgn(cross(a, b, c)); }   // +1 CCW, 0 collinear, -1 CW
 
bool onSegment(P p, P a, P b) {                // assumes p is collinear with a, b
    return dot(p - a, p - b) <= 0;
}
bool properIntersect(P a, P b, P c, P d) {      // strict crossing, not merely touching
    int o1 = orient(a, b, c), o2 = orient(a, b, d);
    int o3 = orient(c, d, a), o4 = orient(c, d, b);
    return o1 * o2 < 0 && o3 * o4 < 0;
}
bool isConvex(const vector<P>& poly) {
    int n = poly.size(), sign = 0;
    for (int i = 0; i < n; i++) {
        int o = orient(poly[i], poly[(i + 1) % n], poly[(i + 2) % n]);
        if (o == 0) continue;                  // allow collinear points
        if (sign == 0) sign = o;
        else if (sign != o) return false;
    }
    return true;
}

Working with avoids overflow in comparisons and makes the code read as geometry rather than arithmetic. Note isConvex accepts polygons with collinear vertices; whether that counts as convex depends on the problem.

The in-circle predicate

The other classical predicate, needed for Delaunay triangulation: is inside the circle through (taken CCW)?

Degree 4 in the coordinates, so with inputs the determinant reaches roughly — far beyond both long long and exact double. This is why Delaunay implementations need exact predicates (__int128 only pushes the bound to about , still short) or heavily restricted coordinate ranges.

Overflow

With coordinates up to , differences reach and the cross product reaches — inside long long (), but only just. Any further multiplication (e.g. comparing two cross products, or the in-circle determinant) needs __int128.

If coordinates can be , use __int128 from the start, or translate all points by the first point to shrink the range.

3D cross product

a vector perpendicular to both, with length equal to the parallelogram area. Uses: plane normal from three points (), triangle area in 3D (half that length), coplanarity of four points (scalar triple product ), tetrahedron volume ( the triple product). See 3D Geometry.

Why cross beats angles

Comparing angles requires atan2, which is slow and has a discontinuity at to handle with care. The cross product answers the same left/right question exactly, in integers, with two multiplications. Reach for cross first, angles only when you genuinely need a numeric angle.

Angular sorting

Radial sweeps, Graham scan, and visibility problems all need to sort vectors by angle. Do not use atan2 for this — sort by half-plane, then by cross product:

int half(P p) { return (p.y < 0 || (p.y == 0 && p.x < 0)) ? 1 : 0; }
int half(PD p) { return (p.y < 0 || (p.y == 0 && p.x < 0)) ? 1 : 0; }   // same split, for floating directions
bool angleLess(const P& a, const P& b) {
    int ha = half(a), hb = half(b);
    if (ha != hb) return ha < hb;
    ll c = cross(a, b);
    return c != 0 ? c > 0 : norm2(a) < norm2(b);     // tie-break collinear same-direction vectors by length
}

Exact for integer coordinates, about 5x faster than comparing atan2 values, and free of the wraparound bug at : half splits the plane into the lower half (, i.e. angle measuring from standard position with axis included) and the upper half, so within a half the cross product alone totally orders the angle (no vector pair inside one half is more than apart, where cross’s sign stops being a reliable comparator).

Distances

Point to point, point to line, point to segment

Covered above: dist2/dist, distToLine, distToSegment. Segment-to-segment distance:

// requires segmentsIntersect from the segment-intersection toolkit, see next note
double segToSeg(P a, P b, P c, P d, bool intersects) {
    if (intersects) return 0;
    return min({distToSegment(a, c, d), distToSegment(b, c, d),
                distToSegment(c, a, b), distToSegment(d, a, b)});
}

If the segments intersect the distance is ; otherwise, since the distance function between two segments is convex and piecewise-linear/-quadratic with no interior critical point when they don’t cross, the minimum is attained at an endpoint of one of them against the other segment — checking all four endpoint-to-opposite-segment distances suffices. The intersection check first is essential — otherwise crossing segments would report a spurious positive distance from only checking endpoints.

Point to polygon

  • Inside (test first) distance 0, or the distance to the nearest edge if the problem wants the boundary distance.
  • Outside the minimum over all edges of distToSegment, .
  • Convex polygon, many queries binary search the angular sector, per query (see Convex Polygon Queries).

Circles

PairDistance
Point to circle (0 if on the circle)
Circle to circle (external)
Circle inside circle if nested
Line to circle
Segment to circle

Convex polygon to convex polygon

with rotating calipers, or via the Minkowski difference: the distance between two convex polygons equals the distance from the origin to , itself convex — see Minkowski Sum.

Other metrics

MetricFormulaNote
Euclidean ()the default
Manhattan ()grid movement
Chebyshev ()king moves
Minkowski interpolates

The rotation trick

Rotating by 45° (and scaling by ) converts Manhattan distance into Chebyshev distance, which decouples the coordinates — so “maximum Manhattan distance among points” becomes in . See Manhattan Geometry.

In dimensions, the same idea gives over transformed coordinates (fixing the sign of the first term by symmetry).

Nearest-neighbour queries

SituationMethod
One query, pointslinear scan,
Many queries, static pointsKD-tree, expected — see 09-kd-tree-and-spatial-structures
Closest pair among divide and conquer, — [[19-geometry/16-closest-pair
Nearest among points on a convex hullrotating calipers
Nearest with insertionsDelaunay / Voronoi, or a rebuilt KD-tree
High dimensionsLSH / approximate

Precision reminder

Compare squared distances wherever possible (Rule 3). Take the square root only for output, and print with enough precision (printf("%.10f") or cout << fixed << setprecision(10)).