Skip to content

Commit 4011d51

Browse files
d-v-brabernat
andauthored
allow int-like floats for int dtype fill values (#3448)
* allow int-like floats for int dtype fill values * changelog * add specific test for intish float * correct test * Update tests/test_dtype/test_npy/test_common.py Co-authored-by: Ryan Abernathey <[email protected]> --------- Co-authored-by: Ryan Abernathey <[email protected]>
1 parent d3fa3f5 commit 4011d51

File tree

5 files changed

+43
-8
lines changed

5 files changed

+43
-8
lines changed

changes/3448.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Setting ``fill_value`` to a float like ``0.0`` when the data type of the array is an integer is a common
2+
mistake. This change lets Zarr Python read arrays with this erroneous metadata, although Zarr Python
3+
will not create such arrays.

src/zarr/core/dtype/npy/common.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
Any,
1010
Final,
1111
Literal,
12+
NewType,
1213
SupportsComplex,
1314
SupportsFloat,
1415
SupportsIndex,
@@ -54,6 +55,9 @@
5455
"generic",
5556
)
5657

58+
IntishFloat = NewType("IntishFloat", float)
59+
"""A type for floats that represent integers, like 1.0 (but not 1.1)."""
60+
5761
NumpyEndiannessStr = Literal[">", "<", "="]
5862
NUMPY_ENDIANNESS_STR: Final = ">", "<", "="
5963

@@ -467,6 +471,23 @@ def check_json_int(data: JSON) -> TypeGuard[int]:
467471
return bool(isinstance(data, int))
468472

469473

474+
def check_json_intish_float(data: JSON) -> TypeGuard[IntishFloat]:
475+
"""
476+
Check if a JSON value is an "intish float", i.e. a float that represents an integer, like 0.0.
477+
478+
Parameters
479+
----------
480+
data : JSON
481+
The JSON value to check.
482+
483+
Returns
484+
-------
485+
Bool
486+
True if the data is an intish float, False otherwise.
487+
"""
488+
return isinstance(data, float) and data.is_integer()
489+
490+
470491
def check_json_str(data: JSON) -> TypeGuard[str]:
471492
"""
472493
Check if a JSON value is a string.

src/zarr/core/dtype/npy/int.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626
from zarr.core.dtype.npy.common import (
2727
check_json_int,
28+
check_json_intish_float,
2829
endianness_to_numpy_str,
2930
get_endianness_from_numpy_dtype,
3031
)
@@ -206,6 +207,8 @@ def from_json_scalar(self, data: JSON, *, zarr_format: ZarrFormat) -> TIntScalar
206207
"""
207208
if check_json_int(data):
208209
return self._cast_scalar_unchecked(data)
210+
if check_json_intish_float(data):
211+
return self._cast_scalar_unchecked(int(data))
209212
raise TypeError(f"Invalid type: {data}. Expected an integer.")
210213

211214
def to_json_scalar(self, data: object, *, zarr_format: ZarrFormat) -> int:

tests/test_dtype/test_npy/test_common.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
check_json_float_v2,
2121
check_json_float_v3,
2222
check_json_int,
23+
check_json_intish_float,
2324
check_json_str,
2425
complex_float_to_json_v2,
2526
complex_float_to_json_v3,
@@ -320,6 +321,13 @@ def test_check_json_int() -> None:
320321
assert not check_json_int(1.0)
321322

322323

324+
def test_check_json_intish_float() -> None:
325+
assert check_json_intish_float(0.0)
326+
assert check_json_intish_float(1.0)
327+
assert not check_json_intish_float("0")
328+
assert not check_json_intish_float(1.1)
329+
330+
323331
def test_check_json_str() -> None:
324332
assert check_json_str("0")
325333
assert not check_json_str(1.0)

tests/test_dtype/test_npy/test_int.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class TestInt8(BaseTestZDType):
2828
{"name": "int8", "configuration": {"endianness": "little"}},
2929
)
3030

31-
scalar_v2_params = ((Int8(), 1), (Int8(), -1))
31+
scalar_v2_params = ((Int8(), 1), (Int8(), -1), (Int8(), 1.0))
3232
scalar_v3_params = ((Int8(), 1), (Int8(), -1))
3333
cast_value_params = (
3434
(Int8(), 1, np.int8(1)),
@@ -63,7 +63,7 @@ class TestInt16(BaseTestZDType):
6363
{"name": "int16", "configuration": {"endianness": "little"}},
6464
)
6565

66-
scalar_v2_params = ((Int16(), 1), (Int16(), -1))
66+
scalar_v2_params = ((Int16(), 1), (Int16(), -1), (Int16(), 1.0))
6767
scalar_v3_params = ((Int16(), 1), (Int16(), -1))
6868
cast_value_params = (
6969
(Int16(), 1, np.int16(1)),
@@ -101,7 +101,7 @@ class TestInt32(BaseTestZDType):
101101
{"name": "int32", "configuration": {"endianness": "little"}},
102102
)
103103

104-
scalar_v2_params = ((Int32(), 1), (Int32(), -1))
104+
scalar_v2_params = ((Int32(), 1), (Int32(), -1), (Int32(), 1.0))
105105
scalar_v3_params = ((Int32(), 1), (Int32(), -1))
106106
cast_value_params = (
107107
(Int32(), 1, np.int32(1)),
@@ -136,7 +136,7 @@ class TestInt64(BaseTestZDType):
136136
{"name": "int64", "configuration": {"endianness": "little"}},
137137
)
138138

139-
scalar_v2_params = ((Int64(), 1), (Int64(), -1))
139+
scalar_v2_params = ((Int64(), 1), (Int64(), -1), (Int64(), 1.0))
140140
scalar_v3_params = ((Int64(), 1), (Int64(), -1))
141141
cast_value_params = (
142142
(Int64(), 1, np.int64(1)),
@@ -168,7 +168,7 @@ class TestUInt8(BaseTestZDType):
168168
{"name": "uint8", "configuration": {"endianness": "little"}},
169169
)
170170

171-
scalar_v2_params = ((UInt8(), 1), (UInt8(), 0))
171+
scalar_v2_params = ((UInt8(), 1), (UInt8(), 0), (UInt8(), 1.0))
172172
scalar_v3_params = ((UInt8(), 1), (UInt8(), 0))
173173
cast_value_params = (
174174
(UInt8(), 1, np.uint8(1)),
@@ -203,7 +203,7 @@ class TestUInt16(BaseTestZDType):
203203
{"name": "uint16", "configuration": {"endianness": "little"}},
204204
)
205205

206-
scalar_v2_params = ((UInt16(), 1), (UInt16(), 0))
206+
scalar_v2_params = ((UInt16(), 1), (UInt16(), 0), (UInt16(), 1.0))
207207
scalar_v3_params = ((UInt16(), 1), (UInt16(), 0))
208208
cast_value_params = (
209209
(UInt16(), 1, np.uint16(1)),
@@ -238,7 +238,7 @@ class TestUInt32(BaseTestZDType):
238238
{"name": "uint32", "configuration": {"endianness": "little"}},
239239
)
240240

241-
scalar_v2_params = ((UInt32(), 1), (UInt32(), 0))
241+
scalar_v2_params = ((UInt32(), 1), (UInt32(), 0), (UInt32(), 1.0))
242242
scalar_v3_params = ((UInt32(), 1), (UInt32(), 0))
243243
cast_value_params = (
244244
(UInt32(), 1, np.uint32(1)),
@@ -273,7 +273,7 @@ class TestUInt64(BaseTestZDType):
273273
{"name": "uint64", "configuration": {"endianness": "little"}},
274274
)
275275

276-
scalar_v2_params = ((UInt64(), 1), (UInt64(), 0))
276+
scalar_v2_params = ((UInt64(), 1), (UInt64(), 0), (UInt64(), 1.0))
277277
scalar_v3_params = ((UInt64(), 1), (UInt64(), 0))
278278
cast_value_params = (
279279
(UInt64(), 1, np.uint64(1)),

0 commit comments

Comments
 (0)