@@ -212,11 +212,27 @@ class CFScaleOffsetCoder(VariableCoder):
212
212
decode_values = encoded_values * scale_factor + add_offset
213
213
"""
214
214
215
+ @staticmethod
216
+ def _choose_float_dtype (data , has_offset ):
217
+ """Return a float dtype sufficient to losslessly represent `data`."""
218
+ # float32 can exactly represent all integers up to 24 bits
219
+ if data .dtype .itemsize <= 2 and np .issubdtype (data .dtype , np .integer ):
220
+ # A scale factor is entirely safe (vanishing into the mantissa),
221
+ # but a large integer offset could lead to loss of precision.
222
+ # Sensitivity analysis can be tricky, so we just use a float64
223
+ # if there's any offset at all - better unoptimised than wrong!
224
+ if not has_offset :
225
+ return np .float32
226
+ # For all other types and circumstances, we just use float64.
227
+ # (safe because eg. complex numbers are not supported in NetCDF)
228
+ return np .float64
229
+
215
230
def encode (self , variable , name = None ):
216
231
dims , data , attrs , encoding = unpack_for_encoding (variable )
217
232
218
233
if 'scale_factor' in encoding or 'add_offset' in encoding :
219
- data = data .astype (dtype = np .float64 , copy = True )
234
+ dtype = self ._choose_float_dtype (data , 'add_offset' in encoding )
235
+ data = data .astype (dtype = dtype , copy = True )
220
236
if 'add_offset' in encoding :
221
237
data -= pop_to (encoding , attrs , 'add_offset' , name = name )
222
238
if 'scale_factor' in encoding :
@@ -230,7 +246,7 @@ def decode(self, variable, name=None):
230
246
if 'scale_factor' in attrs or 'add_offset' in attrs :
231
247
scale_factor = pop_to (attrs , encoding , 'scale_factor' , name = name )
232
248
add_offset = pop_to (attrs , encoding , 'add_offset' , name = name )
233
- dtype = np . float64
249
+ dtype = self . _choose_float_dtype ( data , 'add_offset' in attrs )
234
250
transform = partial (_scale_offset_decoding ,
235
251
scale_factor = scale_factor ,
236
252
add_offset = add_offset ,
0 commit comments