diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 3b6a8420e3..db1147eef1 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -1658,9 +1658,14 @@ def fill_value(self): @fill_value.setter def fill_value(self, fill_value): if fill_value is not None: + # Convert the given value to the dtype of the cube. + fill_value = np.asarray([fill_value])[0] + target_dtype = self.dtype + if fill_value.dtype.kind == 'f' and target_dtype.kind == 'i': + # Perform rounding when converting floats to ints. + fill_value = np.rint(fill_value) try: - fill_value = np.asarray([fill_value], - dtype=self.dtype)[0] + fill_value = np.asarray([fill_value], dtype=target_dtype)[0] except OverflowError: emsg = 'Fill value of {!r} invalid for cube {!r}.' raise ValueError(emsg.format(fill_value, self.dtype)) @@ -1763,12 +1768,17 @@ def data(self, value): raise ValueError('Require cube data with shape %r, got ' '%r.' % (self.shape, value.shape)) + # Set lazy or real data, and reset the other. if is_lazy_data(value): self._dask_array = value self._numpy_array = None - else: self._numpy_array = value + self._dask_array = None + + # Cancel any 'realisation' datatype conversion, and fill value. + self._dtype = None + self.fill_value = None def has_lazy_data(self): return self._numpy_array is None diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 74686d33f4..9260d8dc86 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -1359,7 +1359,6 @@ def test_lazydata_dtype_change(self): self.assertEqual(cube.core_data.dtype, np.dtype('f4')) self.assertIsNone(cube.fill_value) - def test_lazydata_maskedints_dtype_change(self): # Check that re-assigning dtype resets fill_value. cube = self._sample_cube(dtype=np.int16, masked=True, lazy=True, @@ -1384,6 +1383,31 @@ def test_fill_value__float_dtype_to_int(self): self.assertEqual(cube.fill_value.dtype, np.dtype('i2')) self.assertArrayAllClose(cube.fill_value, 1735) + def test_set_fill_value(self): + cube = self._sample_cube() + cube.fill_value = -74.6 + self.assertEqual(cube.fill_value.dtype, np.dtype('f4')) + self.assertArrayAllClose(cube.fill_value, -74.6) + + def test_set_fill_value__typecast(self): + cube = self._sample_cube(dtype=np.int16) + cube.fill_value = -74.6 + self.assertEqual(cube.fill_value.dtype, np.dtype('i2')) + self.assertArrayAllClose(cube.fill_value, -75) + + def test_set_fill_value__casterror(self): + cube = self._sample_cube(dtype=np.int16) + msg = "invalid for cube dtype\('int16'\)" + with self.assertRaisesRegexp(ValueError, msg): + # NOTE: this doesn't actually work properly in most cases. + # E.G. it will happily assign 1e12 to an int16 and gets 4096. + cube.fill_value = -1.0e23 + + def test_clear_fill_value(self): + cube = self._sample_cube(cube_fill_value=123.768) + cube.fill_value = None + self.assertIsNone(cube.fill_value) + class TestSubset(tests.IrisTest): def test_scalar_coordinate(self):