Repository: SciTools/cf-units

Issue:
Casting to integer on conversion
Hi there,

I have a question about behaviour which I was hoping you could help with. It focuses on this comment:

https://github.com/SciTools/cf-units/blob/a3eeaf15622bd3ec6f23c18cbd3b3658086aa7cf/cf_units/__init__.py#L1845

My question is: why isn't this also applied to `np.float64` datatypes? 

More info:

The minimal example below shows how this can cause a `np.float64` dtype to be cast to integer even though a `np.float32` dtype is not. This seems surprising, but maybe that is the idea? (As a sidenote, this only appears to happen on a linux operating system, it doesn't happen on mac...)

```python
In [1]: import cf_units

In [2]: import numpy as np

In [3]: cf_units.__version__
Out[3]: '2.1.4'

In [4]: start = cf_units.Unit('days since 2100-01-01', calendar='365_day')

In [5]: target = cf_units.Unit('days since 2006-01-01', calendar='365_day')

In [6]: start.convert(np.array([1, 2, 3]).astype(np.float32), target)
Out[6]: array([34311., 34312., 34313.], dtype=float32)

In [7]: start.convert(np.array([1, 2, 3]).astype(np.float32), target).dtype
Out[7]: dtype('float32')  # type preserved

In [8]: start.convert(np.array([1, 2, 3]).astype(np.float64), target)
Out[8]: array([34311, 34312, 34313])

In [9]: start.convert(np.array([1, 2, 3]).astype(np.float64), target).dtype
Out[9]: dtype('int64')  # type not preserved
```

Thanks!

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/SciTools/cf-units/pull/164?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) Report
> Merging [#164](https://codecov.io/gh/SciTools/cf-units/pull/164?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) (c1d0279) into [master](https://codecov.io/gh/SciTools/cf-units/commit/02ae952af96766bfe998da0c6856373b62373bee?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) (02ae952) will **not change** coverage.
> The diff coverage is `50.00%`.

[![Impacted file tree graph](https://codecov.io/gh/SciTools/cf-units/pull/164/graphs/tree.svg?width=650&height=150&src=pr&token=6LlYlyTUZG&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools)](https://codecov.io/gh/SciTools/cf-units/pull/164?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools)

```diff
@@           Coverage Diff           @@
##           master     #164   +/-   ##
=======================================
  Coverage   90.48%   90.48%           
=======================================
  Files           7        7           
  Lines         830      830           
  Branches      105      105           
=======================================
  Hits          751      751           
  Misses         64       64           
  Partials       15       15           
```


| [Impacted Files](https://codecov.io/gh/SciTools/cf-units/pull/164?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools) | Coverage Δ | |
|---|---|---|
| [cf\_units/\_\_init\_\_.py](https://codecov.io/gh/SciTools/cf-units/pull/164/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools#diff-Y2ZfdW5pdHMvX19pbml0X18ucHk=) | `89.30% <50.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/SciTools/cf-units/pull/164?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/SciTools/cf-units/pull/164?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools). Last update [02ae952...c1d0279](https://codecov.io/gh/SciTools/cf-units/pull/164?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=SciTools).

znicholls: > sorry for letting this go stale

No worries, lots happening

> Please could you rebase, and service my suggestion for making the code more generic?

Done

Patch:
diff --git a/cf_units/__init__.py b/cf_units/__init__.py
index f43b2e9..5395353 100644
--- a/cf_units/__init__.py
+++ b/cf_units/__init__.py
@@ -1738,10 +1738,12 @@ class Unit(_OrderedHashable):
                     result, self.cftime_unit, self.calendar)
                 result = cftime.date2num(
                     result_datetimes, other.cftime_unit, other.calendar)
-                # Preserve the datatype of the input array if it was float32.
-                if (isinstance(value, np.ndarray) and
-                   value.dtype == np.float32):
-                    result = result.astype(np.float32)
+                convert_type = (
+                    isinstance(value, np.ndarray)
+                    and np.issubsctype(value.dtype, np.floating)
+                )
+                if convert_type:
+                    result = result.astype(value.dtype)
             else:
                 try:
                     ut_converter = _ud.get_converter(self.ut_unit,


Test patch:
diff --git a/cf_units/tests/unit/unit/test_Unit.py b/cf_units/tests/unit/unit/test_Unit.py
index 201e845..39a3e4a 100644
--- a/cf_units/tests/unit/unit/test_Unit.py
+++ b/cf_units/tests/unit/unit/test_Unit.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2015 - 2020, Met Office
+# (C) British Crown Copyright 2015 - 2021, Met Office
 #
 # This file is part of cf-units.
 #
@@ -75,11 +75,22 @@ class Test_convert__calendar(unittest.TestCase):
         self.assertEqual(expected.shape, result.shape)
 
     def test_non_gregorian_calendar_conversion_dtype(self):
-        data = np.arange(4, dtype=np.float32)
-        u1 = Unit('hours since 2000-01-01 00:00:00', calendar='360_day')
-        u2 = Unit('hours since 2000-01-02 00:00:00', calendar='360_day')
-        result = u1.convert(data, u2)
-        self.assertEqual(result.dtype, np.float32)
+        for start_dtype, exp_convert in (
+            (np.float32, True),
+            (np.float64, True),
+            (np.int32, False),
+            (np.int64, False),
+            (np.int, False),
+        ):
+            data = np.arange(4, dtype=start_dtype)
+            u1 = Unit('hours since 2000-01-01 00:00:00', calendar='360_day')
+            u2 = Unit('hours since 2000-01-02 00:00:00', calendar='360_day')
+            result = u1.convert(data, u2)
+
+            if exp_convert:
+                self.assertEqual(result.dtype, start_dtype)
+            else:
+                self.assertEqual(result.dtype, np.int64)
 
 
 class Test_convert__endianness_time(unittest.TestCase):