Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Geospatial

Geospatial

Precision and determinism

On this page

A geo. operation gives the same digits on the interpreter, a native build and a wasm32 build, and a query gives the same records the code would keep. Floats appear only where PostGIS stores and searches:

Where Arithmetic Same on every target?
geo. operations in code: interpreter, native, wasm32 Jadpo's own whole-number fixed point, 256 bits with 136 after the point (about 41 decimal places), its sqrt a whole square root and its sin, cos and atan2 the series of the standard library's decimal maths (Numbers); results rounded half to even to the result type's scale Yes: integer arithmetic only, no platform maths library
Reading GeoJSON The double JSON gives, held as its shortest decimal Yes
Coordinates built in code Rounded to the nearest double's shortest decimal when built Yes
Storage PostGIS doubles; exact, given the row above Yes
Query conditions and order PostGIS's doubles find the rows, through the index and a millimetre wider than the condition; Jadpo's own code decides each and sorts them Yes: the code's answer

No floats in Jadpo's own code

Karney's method needs additions, multiplications, divisions, square roots, sin, cos and atan2. All of it runs on whole numbers far wider than the result, and is rounded once at the end. A float's sin differs between platforms' maths libraries, so a float version could give different digits on wasm32.

Each result is rounded half to even from the exact binary value. It isn't correctly rounded, since the series are finite, but it is the same digits on every target. The checker and the runtime share the code, so a polygon literal is read by the same code the runtime runs.

Coordinates are doubles' shortest decimals

A Latitude or a Longitude, as a value, a field or a point's part, is rounded to the shortest decimal of the nearest IEEE double as it is built, before its range is checked, as a Vector's items are. That is the same on every target. Every coordinate with up to 15 significant digits, which is all a person writes and every GPS gives, is unchanged.

Rounding can't take a value in range out of it, since ±90 and ±180 are doubles. A double whose shortest decimal has more than 38 places (below 10⁻²³ of a degree) is rounded half to even to 38. A literal is checked as written.

Checked against reference answers

geo.distance gives the reference distance of Karney's published test set of 10,000 geodesics, rounded half to even to the micrometre. Before rounding, every distance is within 75 femtometres of the reference. geo.bearing and geo.area are checked against the same set, and every predicate, centroid and box against PostGIS's answers on 169 shapes: small, continental and pole-holding polygons, polygons across the antimeridian and the equator, holes, concave shapes, lines and multi-part shapes. Every yes or no is PostGIS's, except where ST_Covers parts from geo.contains (contains, within and intersects). The gate runs both checks natively and under WebAssembly, which give the same answers. The design notes give the method and its tolerances.

Predicates are exact

Whether a point is inside a shape, or two shapes meet, is decided exactly, by whole numbers, with no tolerance. So a point exactly on an edge, a corner, a meridian or the equator is found on it, and inside includes the edge. Where three points lie exactly on one great circle, the tie is broken by a fixed rule (Simulation of Simplicity, as Google's S2 library does). No float decides anything.

What it costs

These figures are measured natively on an Apple M1 Pro (cargo run --release -p jadpo-value --example geodesics -- bench).

  • Measures: a distance along a meridian takes 9 µs; 2 km across London 31 µs; London to Paris 42 µs; London to Sydney 58 µs; nearly antipodal 64 µs. A bearing takes 47 µs, an 11-point line's length 315 µs, and 71 µs for the 1° square's area (meridians and the equator are quick). A 100-point patch's area takes 4.5 ms, about 45 µs an edge.
  • Under WebAssembly: checking 500 of the test set's geodesics takes 0.30 s natively and 0.83 s under Node's WebAssembly, about 2.8 times, so a distance there takes about 110 µs, and 10 ms of CPU measures about 90 distances.
  • Predicates and shapes: a point in a 100-point patch takes 0.44 ms, in a 1,000-point one 4.3 ms. A 100-point patch in another takes 30 ms, since each piece of every edge is classified, about n² in the points. Two 100-point patches intersecting take 0.9 ms, within(distance:) 33 µs, a 100-point patch's centroid 0.37 ms and its box 1.2 ms. Turning each corner into a direction (two sines and cosines) is most of a small polygon's cost.

A request measures a handful of things. A batch measuring millions, or the area of a 10,000-point outline (about half a second), would notice. No time limit is enforced, as none is elsewhere; the figures are the baseline that tuning measures against.

Where code and the database could disagree

They could disagree on a point on, or within a hair of, a polygon's edge, and on a distance within a hair of a radius: PostGIS decides in doubles with its own tolerances, and Jadpo exactly. ST_Covers also parts from geo.contains on four kinds of polygon. So a query's condition uses PostGIS only to find the rows, through the index, a millimetre wider than the condition, and the runtime decides each row with the same code a body runs (Queries). The index is used, and the answer is the code's.

The cost is the code's test on each row found: about 40 µs a distance natively, and 0.44 ms for a point in a 100-point patch. So a radius holding a thousand bakeries spends some 40 ms sorting them by distance.

Markdown for agents: /docs/precision-and-determinism.md.Built from Jadpo main at a707efb.