def _choose_float_dtype(
    dtype: np.dtype, mapping: MutableMapping
) -> type[np.floating[Any]]:
    """Return a float dtype that can losslessly represent `dtype` values."""
    # check scale/offset first to derive wanted float dtype
    # see https://github.com/pydata/xarray/issues/5597#issuecomment-879561954
    scale_factor = mapping.get("scale_factor")
    add_offset = mapping.get("add_offset")
    if scale_factor is not None or add_offset is not None:
        # get the type from scale_factor/add_offset to determine
        # the needed floating point type
        if scale_factor is not None:
            scale_type = np.dtype(type(scale_factor))
        if add_offset is not None:
            offset_type = np.dtype(type(add_offset))
        # CF conforming, both scale_factor and add-offset are given and
        # of same floating point type (float32/64)
        if (
            add_offset is not None
            and scale_factor is not None
            and offset_type == scale_type
            and scale_type in [np.float32, np.float64]
        ):
            # in case of int32 -> we need upcast to float64
            # due to precision issues
            if dtype.itemsize == 4 and np.issubdtype(dtype, np.integer):
                return np.float64
            return scale_type.type
        # Not CF conforming and add_offset given:
        # A scale factor is entirely safe (vanishing into the mantissa),
        # but a large integer offset could lead to loss of precision.
        # Sensitivity analysis can be tricky, so we just use a float64
        # if there's any offset at all - better unoptimised than wrong!
        if add_offset is not None:
            return np.float64
        # return dtype depending on given scale_factor
        return scale_type.type
    # If no scale_factor or add_offset is given, use some general rules.
    # Keep float32 as-is. Upcast half-precision to single-precision,
    # because float16 is "intended for storage but not computation"
    if dtype.itemsize <= 4 and np.issubdtype(dtype, np.floating):
        return np.float32
    # float32 can exactly represent all integers up to 24 bits
    if dtype.itemsize <= 2 and np.issubdtype(dtype, np.integer):
        return np.float32
    # For all other types and circumstances, we just use float64.
    # Todo: with nc-complex from netcdf4-python >= 1.7.0 this is available
    # (safe because eg. complex numbers are not supported in NetCDF)
    return np.float64
Return a float dtype that can losslessly represent `dtype` values.
xarray/coding/variables.py