From d8e2f995492c22f2f7bf544813fe29dce149df05 Mon Sep 17 00:00:00 2001 From: "Morten W. Hansen" Date: Sat, 9 May 2026 14:00:06 +0000 Subject: [PATCH] Fix floating-point precision artifacts in longitude normalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When normalizing longitudes to ±180°, float arithmetic produced representation artifacts (e.g. '41.76' → '41.75999999999999'). Fix by rounding back to the original number of decimal places. Also handles numpy.float64 inputs by converting to str before inspecting decimal places. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- py_mmd_tools/nc_to_mmd.py | 10 +++++++--- tests/test_nc_to_mmd.py | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/py_mmd_tools/nc_to_mmd.py b/py_mmd_tools/nc_to_mmd.py index 0f79e90f..2269984c 100644 --- a/py_mmd_tools/nc_to_mmd.py +++ b/py_mmd_tools/nc_to_mmd.py @@ -1255,10 +1255,14 @@ def get_geographic_extent_rectangle(self, mmd_element, ncin): self.missing_attributes["errors"].append( "%s must be convertible to float type." % acdd_key ) - # Make sure longitudes are within +/-180 degrees + # Make sure longitudes are within +/-180 degrees, preserving original + # string precision to avoid floating-point representation artifacts. if not fail: - data["east"] = str((float(data["east"]) + 180.) % 360. - 180.) - data["west"] = str((float(data["west"]) + 180.) % 360. - 180.) + for key in ("east", "west"): + val_str = str(data[key]) + decimal_places = len(val_str.split(".")[-1]) if "." in val_str else 0 + normalized = (float(val_str) + 180.) % 360. - 180. + data[key] = str(round(normalized, decimal_places)) return data diff --git a/tests/test_nc_to_mmd.py b/tests/test_nc_to_mmd.py index e7387429..2841ee9d 100644 --- a/tests/test_nc_to_mmd.py +++ b/tests/test_nc_to_mmd.py @@ -1192,6 +1192,27 @@ def test_geographic_extent_is_string(self): self.assertIsInstance(value["north"], str) self.assertIsInstance(value["south"], str) + def test_geographic_extent_no_float_precision_artifacts(self): + """Test that longitude normalization does not introduce floating-point + precision artifacts. E.g., input '41.76' should remain '41.76', not + become '41.75999999999999'. + """ + mmd_yaml = yaml.load( + resource_string('py_mmd_tools', 'mmd_elements.yaml'), Loader=yaml.FullLoader + ) + md = Nc_to_mmd(os.path.abspath('tests/data/reference_nc.nc'), check_only=True) + ncin = Dataset(md.netcdf_file, "w", diskless=True) + ncin.geospatial_lat_max = "60.158733" + ncin.geospatial_lat_min = "59.78492" + ncin.geospatial_lon_max = "41.76" + ncin.geospatial_lon_min = "10.5" + value = md.get_geographic_extent_rectangle( + mmd_yaml['geographic_extent']['rectangle'], ncin) + self.assertEqual(value["east"], "41.76") + self.assertEqual(value["west"], "10.5") + self.assertEqual(value["north"], "60.158733") + self.assertEqual(value["south"], "59.78492") + def test_geographic_extent_rectangle_is_floatable(self): """ Test that the provided geospatial coordinates can be converted to float.