summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiddhesh Poyarekar <siddhesh@gotplt.org>2019-10-15 16:21:36 -0400
committerSiddhesh Poyarekar <siddhesh@gotplt.org>2019-10-15 16:21:36 -0400
commit744a2ac0fab372c8ef633ffa15702387a24db204 (patch)
treed6c52cfb5e5452005e83a35ffd1ea2336d19be6a
parentea66b1d154a99ed7e4325150ef1bf509b7a2b268 (diff)
downloadnumpy-744a2ac0fab372c8ef633ffa15702387a24db204.tar.gz
BUG: Do not rely on undefined behaviour to cast from float to datetime
A cast from float to signed integer is only valid for the range of integer, which means that a cast of a NaN to int is undefined. On x86 cvttd2si conveniently seems to put the mantissa into the long (I haven't done an exhaustive check to see if that is what it does, but seems that way), which works for conversion from NaN to NaT. On aarch64 however, the corresponding fcvtzs sets the value to 0 for all undefined ranges, thus breaking the cast. This explicitly checks for NaN for conversions from floating point types to DATETIME and TIMEDELTA and sets the target value as NaT, which should make the behaviour well defined for NaN to NaT translations.
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src22
-rw-r--r--numpy/core/tests/test_datetime.py24
2 files changed, 44 insertions, 2 deletions
diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src
index 5d9e990e8..9639646e2 100644
--- a/numpy/core/src/multiarray/arraytypes.c.src
+++ b/numpy/core/src/multiarray/arraytypes.c.src
@@ -1081,6 +1081,7 @@ TIMEDELTA_setitem(PyObject *op, void *ov, void *vap)
* npy_long, npy_ulong, npy_longlong, npy_ulonglong,
* npy_float, npy_double, npy_longdouble,
* npy_datetime, npy_timedelta#
+ * #supports_nat = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1#
*/
/**begin repeat1
@@ -1092,6 +1093,7 @@ TIMEDELTA_setitem(PyObject *op, void *ov, void *vap)
* npy_long, npy_ulong, npy_longlong, npy_ulonglong,
* npy_float, npy_double, npy_longdouble,
* npy_datetime, npy_timedelta#
+ * #floatingpoint = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0#
*/
static void
@FROMTYPE@_to_@TOTYPE@(void *input, void *output, npy_intp n,
@@ -1101,7 +1103,15 @@ static void
@totype@ *op = output;
while (n--) {
- *op++ = (@totype@)*ip++;
+ @fromtype@ f = *ip++;
+ @totype@ t = (@totype@)f;
+#if @supports_nat@ && @floatingpoint@
+ /* Avoid undefined behaviour for NaN -> NaT */
+ if (npy_isnan(f)) {
+ t = (@totype@)NPY_DATETIME_NAT;
+ }
+#endif
+ *op++ = t;
}
}
/**end repeat1**/
@@ -1119,7 +1129,15 @@ static void
@totype@ *op = output;
while (n--) {
- *op++ = (@totype@)*ip;
+ @fromtype@ f = *ip;
+ @totype@ t = (@totype@)f;
+#if @supports_nat@
+ /* Avoid undefined behaviour for NaN -> NaT */
+ if (npy_isnan(f)) {
+ t = (@totype@)NPY_DATETIME_NAT;
+ }
+#endif
+ *op++ = t;
ip += 2;
}
}
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index f99c0f72b..c60b80edc 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -483,6 +483,30 @@ class TestDateTime(object):
assert_equal(np.datetime64(a, '[Y]'), np.datetime64('NaT', '[Y]'))
assert_equal(np.datetime64(a, '[W]'), np.datetime64('NaT', '[W]'))
+ # NaN -> NaT
+ nan = np.array([np.nan] * 8)
+ fnan = nan.astype('f')
+ lnan = nan.astype('g')
+ cnan = nan.astype('D')
+ cfnan = nan.astype('F')
+ clnan = nan.astype('G')
+
+ nat = np.array([np.datetime64('NaT')] * 8)
+ assert_equal(nan.astype('M8[ns]'), nat)
+ assert_equal(fnan.astype('M8[ns]'), nat)
+ assert_equal(lnan.astype('M8[ns]'), nat)
+ assert_equal(cnan.astype('M8[ns]'), nat)
+ assert_equal(cfnan.astype('M8[ns]'), nat)
+ assert_equal(clnan.astype('M8[ns]'), nat)
+
+ nat = np.array([np.timedelta64('NaT')] * 8)
+ assert_equal(nan.astype('timedelta64[ns]'), nat)
+ assert_equal(fnan.astype('timedelta64[ns]'), nat)
+ assert_equal(lnan.astype('timedelta64[ns]'), nat)
+ assert_equal(cnan.astype('timedelta64[ns]'), nat)
+ assert_equal(cfnan.astype('timedelta64[ns]'), nat)
+ assert_equal(clnan.astype('timedelta64[ns]'), nat)
+
def test_days_creation(self):
assert_equal(np.array('1599', dtype='M8[D]').astype('i8'),
(1600-1970)*365 - (1972-1600)/4 + 3 - 365)