diff --git a/doc/whats-new.rst b/doc/whats-new.rst index bee4156f1e5..ee7a32bed6e 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -62,6 +62,8 @@ Bug fixes calendar of the datetimes from ``"proleptic_gregorian"`` to ``"gregorian"`` and prevents round-tripping them as :py:class:`numpy.datetime64` values (:pull:`10352`). By `Spencer Clark `_. +- Avoid unsafe casts from float to unsigned int in CFMaskCoder (:issue:`9815`, :pull:`9964`). + By ` Elliott Sales de Andrade `_. Performance ~~~~~~~~~~~ diff --git a/xarray/coding/variables.py b/xarray/coding/variables.py index 1b7bc95e2b4..96413da1e8f 100644 --- a/xarray/coding/variables.py +++ b/xarray/coding/variables.py @@ -345,7 +345,17 @@ def encode(self, variable: Variable, name: T_Name = None): if fill_value is not None and has_unsigned: pop_to(encoding, attrs, "_Unsigned") # XXX: Is this actually needed? Doesn't the backend handle this? - data = duck_array_ops.astype(duck_array_ops.around(data), dtype) + # two-stage casting to prevent undefined cast from float to unsigned int + # first float -> int with corresponding itemsize + # second int -> int/uint to final itemsize + signed_dtype = np.dtype(f"i{data.itemsize}") + data = duck_array_ops.astype( + duck_array_ops.astype( + duck_array_ops.around(data), signed_dtype, copy=False + ), + dtype, + copy=False, + ) attrs["_FillValue"] = fill_value return Variable(dims, data, attrs, encoding, fastpath=True)