diff --git a/changes/3144.bugfix.rst b/changes/3144.bugfix.rst new file mode 100644 index 0000000000..8dde317bb8 --- /dev/null +++ b/changes/3144.bugfix.rst @@ -0,0 +1 @@ +Ensure that -0.0 is not considered equal to 0.0 when checking if all the values in a chunk are equal to an array's fill value.``` diff --git a/src/zarr/core/buffer/core.py b/src/zarr/core/buffer/core.py index 19125b838f..07bdb8c26e 100644 --- a/src/zarr/core/buffer/core.py +++ b/src/zarr/core/buffer/core.py @@ -523,6 +523,15 @@ def all_equal(self, other: Any, equal_nan: bool = True) -> bool: if other is None: # Handle None fill_value for Zarr V2 return False + # Handle positive and negative zero by comparing bit patterns: + if ( + np.asarray(other).dtype.kind == "f" + and other == 0.0 + and self._data.dtype.kind not in ("U", "S", "T", "O", "V") + ): + _data, other = np.broadcast_arrays(self._data, np.asarray(other, self._data.dtype)) + void_dtype = "V" + str(_data.dtype.itemsize) + return np.array_equal(_data.view(void_dtype), other.view(void_dtype)) # use array_equal to obtain equal_nan=True functionality # Since fill-value is a scalar, isn't there a faster path than allocating a new array for fill value # every single time we have to write data? diff --git a/tests/test_array.py b/tests/test_array.py index f672006f9a..2de932f34b 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -866,6 +866,30 @@ def test_write_empty_chunks_behavior( assert arr.nchunks_initialized == arr.nchunks +@pytest.mark.parametrize("store", ["memory"], indirect=True) +@pytest.mark.parametrize("fill_value", [0.0, -0.0]) +@pytest.mark.parametrize("dtype", ["f4", "f2"]) +def test_write_empty_chunks_negative_zero( + zarr_format: ZarrFormat, store: MemoryStore, fill_value: float, dtype: str +) -> None: + # regression test for https://github.com/zarr-developers/zarr-python/issues/3144 + + arr = zarr.create_array( + store=store, + shape=(2,), + zarr_format=zarr_format, + dtype=dtype, + fill_value=fill_value, + chunks=(1,), + config={"write_empty_chunks": False}, + ) + assert arr.nchunks_initialized == 0 + + # initialize the with the negated fill value (-0.0 for +0.0, +0.0 for -0.0) + arr[:] = -fill_value + assert arr.nchunks_initialized == arr.nchunks + + @pytest.mark.parametrize( ("fill_value", "expected"), [