Skip to content

Commit 994b618

Browse files
author
peng.li24
committed
docs: add comprehensive precision alignment section with float32 truncation strategy
- Document float32 bit-alignment via input truncation (identical GEOS doubles) - Explain why float32→float64 widening preserves values exactly - Add collision-critical patterns table (6 patterns from production code) - Add code example of _f32() truncation helper - List edge cases tested at collision decision boundaries
1 parent ab1ba41 commit 994b618

1 file changed

Lines changed: 44 additions & 6 deletions

File tree

README.md

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,52 @@ When using `Point<double>`, `LineString<double>`, `Polygon<double>` (the default
2525
- Python shapely calls `GEOSDistance_r()` (C API), which internally invokes the same `DistanceOp::distance()` as the C++ `shapelycpp`.
2626
- `LinearRing` / `Polygon` / `LineString` constructors both populate `geos::geom::CoordinateSequence` via the same `Coordinate(x, y)` path.
2727

28-
### `float` (C++) ↔ Python shapely: NOT bit-identical
28+
### `float` (C++) vs Python shapely: Bit-identical with input truncation
29+
30+
Python shapely has no `float32` mode — all coordinates are auto-promoted to Python `float` (C `double`). However, **bit-identical results can still be achieved** by truncating inputs to `float32` *before* passing them to both sides:
31+
32+
| Strategy | C++ side | Python side | GEOS internal `double` value | Result |
33+
|----------|----------|-------------|------------------------------|--------|
34+
| Truncate inputs to `float32`, then pass to both | `Point<float>` constructor widens to `double` | `PyPoint(f32_x, f32_y)` receives `float` | **Identical** | **Bit-identical**|
35+
36+
This works because:
37+
1. Every `float32` value is **exactly representable** in `float64` — no rounding occurs during widening.
38+
2. `np.float32(x)``float(np.float32(x))` produces the same `double` value that C++ `Point<float>` uses internally.
39+
3. Both C++ and Python then feed the **identical `double` value** into GEOS's `Coordinate(x, y)``CoordinateSequence``DistanceOp::distance()`**same result**.
40+
41+
```python
42+
# Example: float32 bit-alignment strategy
43+
def _f32(coords):
44+
"""Truncate to float32 first so both sides receive identical doubles."""
45+
if isinstance(coords, tuple):
46+
return tuple(_f32(list(coords)))
47+
if isinstance(coords, (int, float)):
48+
return float(np.float32(coords))
49+
return [(_f32(c[0]), _f32(c[1])) for c in coords]
50+
51+
# Usage in tests
52+
f32_coords = _f32(original_coords)
53+
cpp_result = cpp_point_f32.distance(cpp_polygon_f32) # C++ f32 path
54+
py_result = PyPoint(f32_coords).distance(PyPolygon(f32_coords)) # Python f64 path
55+
assert cpp_result == py_result # strict equality — bit-identical
56+
```
57+
58+
> **Note**: Without input truncation, C++ `float` results will differ from Python `double` results (up to ~`1e-5` relative error), because the C++ side truncates to `float32` while Python retains full `float64` precision — the two sides feed *different* `double` values into GEOS.
59+
60+
### Collision-critical operations
61+
62+
The following patterns from production collision detection code are covered by exhaustive tests (1,000+ random pairs per pattern, plus deterministic boundary edge cases), all verified with **strict equality** (`==`):
2963

30-
When using `Point<float>`, `LineString<float>`, `Polygon<float>`:
64+
| # | Pattern | Production usage |
65+
|---|---------|-----------------|
66+
| 1 | Polygon ↔ Polygon distance | `bool collision = (distance == 0.0)` |
67+
| 2 | Polygon ↔ LineString distance | `double d = poly.distance(line)` |
68+
| 3 | `intersects` + `distance` | `intersects(g1, g2) && distance(g1, g2) < 1e-12` |
69+
| 4 | LineString ↔ Point distance | `dist = line.distance(ego_pt)` |
70+
| 5 | LineString ↔ LineString distance | `half_lane_width = line1.distance(line2) / 2.0` |
71+
| 6 | LineString ↔ Polygon (both directions) | `centerline.distance(poly)`; `poly.distance(line)` |
3172

32-
- **Python shapely has no `float32` mode.** All coordinates are automatically promoted to Python `float` (C `double`).
33-
- The C++ `float``double` widening during `Coordinate` construction introduces precision loss (~7 significant digits for `float32` vs ~15 for `float64`).
34-
- Results may differ from Python shapely by up to `1e-5` in relative/absolute terms for `float32`.
35-
- Use `float` precision only when bit-identical alignment with Python shapely is NOT required (e.g., GPU-accelerated computation where `float32` throughput matters).
73+
Edge cases tested at collision decision boundaries: touching edges/vertices, ε-overlap (`1e-12`), near-touching (`1e-12` gap), point-on-boundary, parallel-near segments, collinear-near segments, and endpoint-on-boundary.
3674

3775
## Quick Start
3876

0 commit comments

Comments
 (0)