From 5dffe285bd93a8292f399db9bee3cf7d52652e8c Mon Sep 17 00:00:00 2001 From: Konstantin Bogdanov Date: Thu, 8 Oct 2026 16:56:21 +0200 Subject: [PATCH] Fix precision loss in cagnoli for edges crossing the antimeridian For an edge whose endpoints lie on opposite sides of +-180 degrees the raw longitude difference is close to +-2*pi, and the true tiny step between the vertices is lost to the ulp of 2*pi before sin and cos are applied. Nothing cancels the error, so cells straddling the antimeridian at resolutions 12 and above come out up to 7.8% too small (res 15). Shift both longitudes to the same side of the antimeridian before subtracting, so the difference is between two nearby values. sin and cos are 2*pi-periodic, so the result is unchanged mathematically. Global area sums at res 0-3 still equal 4*pi to within 5e-13. Regression from v4.5.0 (uber/h3#1101), reported in https://github.com/ClickHouse/ClickHouse/issues/124447 Co-Authored-By: Claude Fable 5.1 --- src/apps/testapps/testH3CellArea.c | 17 +++++++++++++++++ src/h3lib/lib/area.c | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/src/apps/testapps/testH3CellArea.c b/src/apps/testapps/testH3CellArea.c index 4189dfc7de..9b0ab6ac0e 100644 --- a/src/apps/testapps/testH3CellArea.c +++ b/src/apps/testapps/testH3CellArea.c @@ -47,6 +47,23 @@ SUITE(h3CellArea) { } } + TEST(cell_area_antimeridian) { + // Cells straddling the antimeridian at fine resolutions, paired with + // an adjacent cell that does not. Their areas must agree closely; a + // naive longitude difference across +-180 degrees loses precision + // and makes the straddling cell up to several percent off. + H3Index pairs[][2] = {{0x8d056898db628bfULL, 0x8d056898db62d7fULL}, + {0x8e32b605c51e067ULL, 0x8e32b605c51e15fULL}, + {0x8f7f809893b5b9dULL, 0x8f7f809893b5b9cULL}}; + for (int i = 0; i < 3; i++) { + double a, b; + t_assertSuccess(H3_EXPORT(cellAreaRads2)(pairs[i][0], &a)); + t_assertSuccess(H3_EXPORT(cellAreaRads2)(pairs[i][1], &b)); + t_assert(fabs(a / b - 1.0) < 1e-5, + "antimeridian cell area should match neighbor"); + } + } + TEST(cell_area_invalid) { H3Index invalid = 0xFFFFFFFFFFFFFFFF; double area; diff --git a/src/h3lib/lib/area.c b/src/h3lib/lib/area.c index cebd605c4f..f46e86dff6 100644 --- a/src/h3lib/lib/area.c +++ b/src/h3lib/lib/area.c @@ -37,7 +37,15 @@ static inline double cagnoli(LatLng x, LatLng y) { double sa = sin(x.lat) * sin(y.lat); double ca = cos(x.lat) * cos(y.lat); + // For an edge crossing the antimeridian, the raw difference is close to + // +-2*pi, and the true (tiny) longitude step is lost to the ulp of 2*pi. + // Shift both longitudes to the same side of the antimeridian first so + // that the subtraction is between two nearby values; sin and cos are + // 2*pi-periodic, so the result is mathematically unchanged. double d = y.lng - x.lng; + if (d > M_PI || d < -M_PI) { + d = (y.lng - copysign(M_PI, y.lng)) - (x.lng - copysign(M_PI, x.lng)); + } double sd = sin(d); double cd = cos(d);