You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+44-6Lines changed: 44 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,14 +25,52 @@ When using `Point<double>`, `LineString<double>`, `Polygon<double>` (the default
25
25
- Python shapely calls `GEOSDistance_r()` (C API), which internally invokes the same `DistanceOp::distance()` as the C++ `shapelycpp`.
26
26
-`LinearRing` / `Polygon` / `LineString` constructors both populate `geos::geom::CoordinateSequence` via the same `Coordinate(x, y)` path.
27
27
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 |
| 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
+
ifisinstance(coords, tuple):
46
+
returntuple(_f32(list(coords)))
47
+
ifisinstance(coords, (int, float)):
48
+
returnfloat(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
> **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** (`==`):
29
63
30
-
When using `Point<float>`, `LineString<float>`, `Polygon<float>`:
-**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).
0 commit comments