Skip to content

Commit d4b7a60

Browse files
authored
conventions: decode unsigned integers to signed if _Unsigned=false (#4966)
1 parent 213e352 commit d4b7a60

File tree

3 files changed

+38
-0
lines changed

3 files changed

+38
-0
lines changed

doc/whats-new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ Bug fixes
6969
By `Victor Negîrneac <https://github.com/caenrigen>`_.
7070
- Don't allow passing ``axis`` to :py:meth:`Dataset.reduce` methods (:issue:`3510`, :pull:`4940`).
7171
By `Justus Magin <https://github.com/keewis>`_.
72+
- Decode values as signed if attribute `_Unsigned = "false"` (:issue:`4954`)
73+
By `Tobias Kölling <https://github.com/d70-t>`_.
7274

7375
Documentation
7476
~~~~~~~~~~~~~

xarray/coding/variables.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,14 @@ def decode(self, variable, name=None):
316316
if "_FillValue" in attrs:
317317
new_fill = unsigned_dtype.type(attrs["_FillValue"])
318318
attrs["_FillValue"] = new_fill
319+
elif data.dtype.kind == "u":
320+
if unsigned == "false":
321+
signed_dtype = np.dtype("i%s" % data.dtype.itemsize)
322+
transform = partial(np.asarray, dtype=signed_dtype)
323+
data = lazy_elemwise_func(data, transform, signed_dtype)
324+
if "_FillValue" in attrs:
325+
new_fill = signed_dtype.type(attrs["_FillValue"])
326+
attrs["_FillValue"] = new_fill
319327
else:
320328
warnings.warn(
321329
"variable %r has _Unsigned attribute but is not "

xarray/tests/test_coding.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,31 @@ def test_scaling_offset_as_list(scale_factor, add_offset):
117117
encoded = coder.encode(original)
118118
roundtripped = coder.decode(encoded)
119119
assert_allclose(original, roundtripped)
120+
121+
122+
@pytest.mark.parametrize("bits", [1, 2, 4, 8])
123+
def test_decode_unsigned_from_signed(bits):
124+
unsigned_dtype = np.dtype(f"u{bits}")
125+
signed_dtype = np.dtype(f"i{bits}")
126+
original_values = np.array([np.iinfo(unsigned_dtype).max], dtype=unsigned_dtype)
127+
encoded = xr.Variable(
128+
("x",), original_values.astype(signed_dtype), attrs={"_Unsigned": "true"}
129+
)
130+
coder = variables.UnsignedIntegerCoder()
131+
decoded = coder.decode(encoded)
132+
assert decoded.dtype == unsigned_dtype
133+
assert decoded.values == original_values
134+
135+
136+
@pytest.mark.parametrize("bits", [1, 2, 4, 8])
137+
def test_decode_signed_from_unsigned(bits):
138+
unsigned_dtype = np.dtype(f"u{bits}")
139+
signed_dtype = np.dtype(f"i{bits}")
140+
original_values = np.array([-1], dtype=signed_dtype)
141+
encoded = xr.Variable(
142+
("x",), original_values.astype(unsigned_dtype), attrs={"_Unsigned": "false"}
143+
)
144+
coder = variables.UnsignedIntegerCoder()
145+
decoded = coder.decode(encoded)
146+
assert decoded.dtype == signed_dtype
147+
assert decoded.values == original_values

0 commit comments

Comments
 (0)