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:
- cross product and orientation,
- squared distances,
- polygon area (twice the area is an integer),
- segment intersection tests,
- convex hull,
- point-in-polygon.
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
- Rational arithmetic — store fractions as
pair<long long,long long>with reduction. Exact but slow and prone to overflow. long double— 18-19 significant digits on x86, one line to switch, often enough.__int128— for exact integer products that exceed 64 bits.- Rescale the input — if coordinates are huge but the answer only depends on differences, translate to the centroid first.
- 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
Pby adouble
P’soperator*takes all; passing adoublesilently 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 inPD, notP. The functions below takePinput (exact) but returnPD.
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 lengthMultiplication 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
| Task | Test |
|---|---|
| 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)? |
Proof: and the sign table
By the law of cosines on the triangle with sides , , (as vectors from the origin), the angle between and satisfies
Expanding the left side coordinate-wise, . Comparing the two expressions gives , which is . Since , the sign of is exactly the sign of , which is positive for , zero at , negative for — the claimed table.
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] firstThe 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));
}Proof:
atan2(|cross|, dot)is more stable thanacos(dot/(|a||b|))Both compute , since up to the positive scale , and
atan2is scale-invariant in its two arguments ( for ).The
acosroute first divides by — a source of rounding error — and then applies , whose derivative diverges as , i.e. exactly where or . A tiny input error near (including, from floating-point rounding, landing outside and makingacosreturnNaN) is amplified without bound.
atan2(y,x)has derivative , , both bounded by — no blow-up as (where but stays away from ), and no division by is needed at all. Henceatan2(|cross|, dot)is well-conditioned everywhereacosis ill-conditioned, for the same mathematical quantity.
Dot and cross together
| Want | Use |
|---|---|
| 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:
| Task | Test |
|---|---|
| Is left of the line ? | |
| Are three points collinear? | |
| Convex hull turn direction | sign 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 polygon | same 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.
Proof: , the sign, and the area claim
Write , . Then
and is exactly the signed angle you turn through going from to (CCW positive). This is positive for a left turn (), negative for a right turn, and zero exactly when are parallel (same or opposite direction) — collinearity of after translating by . Its absolute value is base height of the parallelogram spanned by (base , height the component of perpendicular to ), i.e. the parallelogram’s area; half of it is the triangle ‘s area.
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.
Proof:
onSegmentandproperIntersectare correct
onSegment: given collinear with , write for some real (exists since collinear and, if , unique). Then and , so . Since (for ), this is iff iff , which is exactly ” lies on the segment ”.
properIntersect: means lie strictly on opposite sides of line ; by the intermediate value theorem applied to the signed distance from line (a continuous, in fact affine, function along segment ), segment then crosses line at some interior point of . Symmetrically means segment crosses line at an interior point of . Both crossings refer to the same intersection point of the two lines (two non-parallel lines meet in exactly one point, and parallel lines give or automatically unless collinear, a case excluded by strict inequality), so that point is interior to both segments — a proper crossing. Conversely a proper crossing forces each pair of orientations to differ in sign, since the crossing point separates each segment’s endpoints across the other line.
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.
Proof: the in-circle determinant, via lifting to a paraboloid
Lift every point to on the paraboloid in . The plane through meets the paraboloid exactly where the circle through meets the plane — i.e. exactly on that circle, since substituting a point on the circle (a relation linear in ) into the plane equation (also linear in with ) gives the same linear relation. So the plane through the three lifted points is the lift of the circle through : points lifted above the plane correspond to points inside the circle (their lies above where the plane says the circle’s lift sits at that , which after unwinding the algebra means they are closer to the circle’s center than ), and points lifted below correspond to points outside.
Whether is above or below the plane through is exactly the sign of the orientation determinant of in homogeneous form, i.e. (positive when is on the same side as “up”, given CCW). Subtracting row 4 from rows 1–3 (a determinant-preserving operation) turns this into exactly the stated determinant in the shifted coordinates .
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
| Pair | Distance |
|---|---|
| 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
| Metric | Formula | Note |
|---|---|---|
| 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.
Proof: the rotation trick
Let , , and , . Then and . Case on the signs of :
- Same sign (): (since don’t cancel), and , so .
- Opposite sign: symmetric argument swaps the roles, giving .
Either way . Maximizing over pairs decouples into the two coordinates: .
In dimensions, the same idea gives over transformed coordinates (fixing the sign of the first term by symmetry).
Nearest-neighbour queries
| Situation | Method |
|---|---|
| One query, points | linear scan, |
| Many queries, static points | KD-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 hull | rotating calipers |
| Nearest with insertions | Delaunay / Voronoi, or a rebuilt KD-tree |
| High dimensions | LSH / 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)).