summaryrefslogtreecommitdiff
path: root/numpy/core
diff options
context:
space:
mode:
Diffstat (limited to 'numpy/core')
-rw-r--r--numpy/core/src/multiarray/textreading/parser_config.h5
-rw-r--r--numpy/core/src/multiarray/textreading/readtext.c1
-rw-r--r--numpy/core/src/multiarray/textreading/str_to_int.c51
-rw-r--r--numpy/core/tests/test_deprecations.py19
4 files changed, 72 insertions, 4 deletions
diff --git a/numpy/core/src/multiarray/textreading/parser_config.h b/numpy/core/src/multiarray/textreading/parser_config.h
index 67b5c8483..022ba952c 100644
--- a/numpy/core/src/multiarray/textreading/parser_config.h
+++ b/numpy/core/src/multiarray/textreading/parser_config.h
@@ -59,6 +59,11 @@ typedef struct {
*/
bool python_byte_converters;
bool c_byte_converters;
+ /*
+ * Flag to store whether a warning was already given for an integer being
+ * parsed by first converting to a float.
+ */
+ bool gave_int_via_float_warning;
} parser_config;
diff --git a/numpy/core/src/multiarray/textreading/readtext.c b/numpy/core/src/multiarray/textreading/readtext.c
index 7af5ee891..9804fd462 100644
--- a/numpy/core/src/multiarray/textreading/readtext.c
+++ b/numpy/core/src/multiarray/textreading/readtext.c
@@ -201,6 +201,7 @@ _load_from_filelike(PyObject *NPY_UNUSED(mod),
.ignore_leading_whitespace = false,
.python_byte_converters = false,
.c_byte_converters = false,
+ .gave_int_via_float_warning = false,
};
bool filelike = true;
diff --git a/numpy/core/src/multiarray/textreading/str_to_int.c b/numpy/core/src/multiarray/textreading/str_to_int.c
index 11b03e31c..0dd6c0b0e 100644
--- a/numpy/core/src/multiarray/textreading/str_to_int.c
+++ b/numpy/core/src/multiarray/textreading/str_to_int.c
@@ -8,8 +8,19 @@
#include <string.h>
#include "textreading/str_to_int.h"
#include "textreading/parser_config.h"
+#include "conversions.h" /* For the deprecated parse-via-float path */
+const char *deprecation_msg = (
+ "loadtxt(): Parsing an integer via a float is deprecated. To avoid "
+ "this warning, you can:\n"
+ " * make sure the original data is stored as integers.\n"
+ " * use the `converters=` keyword argument. If you only use\n"
+ " NumPy 1.23 or later, `converters=float` will normally work.\n"
+ " * Use `np.loadtxt(...).astype(np.int64)` parsing the file as\n"
+ " floating point and then convert it. (On all NumPy versions.)\n"
+ " (Deprecated NumPy 1.23)");
+
#define DECLARE_TO_INT(intw, INT_MIN, INT_MAX, byteswap_unaligned) \
NPY_NO_EXPORT int \
to_##intw(PyArray_Descr *descr, \
@@ -19,8 +30,24 @@
int64_t parsed; \
intw##_t x; \
\
- if (str_to_int64(str, end, INT_MIN, INT_MAX, &parsed) < 0) { \
- return -1; \
+ if (NPY_UNLIKELY( \
+ str_to_int64(str, end, INT_MIN, INT_MAX, &parsed) < 0)) { \
+ /* DEPRECATED 2022-07-03, NumPy 1.23 */ \
+ double fval; \
+ PyArray_Descr *d_descr = PyArray_DescrFromType(NPY_DOUBLE); \
+ Py_DECREF(d_descr); /* borrowed */ \
+ if (to_double(d_descr, str, end, (char *)&fval, pconfig) < 0) { \
+ return -1; \
+ } \
+ if (!pconfig->gave_int_via_float_warning) { \
+ pconfig->gave_int_via_float_warning = true; \
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, \
+ deprecation_msg, 3) < 0) { \
+ return -1; \
+ } \
+ } \
+ pconfig->gave_int_via_float_warning = true; \
+ x = (intw##_t)fval; \
} \
else { \
x = (intw##_t)parsed; \
@@ -41,8 +68,24 @@
uint64_t parsed; \
uintw##_t x; \
\
- if (str_to_uint64(str, end, UINT_MAX, &parsed) < 0) { \
- return -1; \
+ if (NPY_UNLIKELY( \
+ str_to_uint64(str, end, UINT_MAX, &parsed) < 0)) { \
+ /* DEPRECATED 2022-07-03, NumPy 1.23 */ \
+ double fval; \
+ PyArray_Descr *d_descr = PyArray_DescrFromType(NPY_DOUBLE); \
+ Py_DECREF(d_descr); /* borrowed */ \
+ if (to_double(d_descr, str, end, (char *)&fval, pconfig) < 0) { \
+ return -1; \
+ } \
+ if (!pconfig->gave_int_via_float_warning) { \
+ pconfig->gave_int_via_float_warning = true; \
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, \
+ deprecation_msg, 3) < 0) { \
+ return -1; \
+ } \
+ } \
+ pconfig->gave_int_via_float_warning = true; \
+ x = (uintw##_t)fval; \
} \
else { \
x = (uintw##_t)parsed; \
diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py
index 09bd5f553..a10dacd49 100644
--- a/numpy/core/tests/test_deprecations.py
+++ b/numpy/core/tests/test_deprecations.py
@@ -1213,3 +1213,22 @@ class TestAxisNotMAXDIMS(_DeprecationTestCase):
def test_deprecated(self):
a = np.zeros((1,)*32)
self.assert_deprecated(lambda: np.repeat(a, 1, axis=np.MAXDIMS))
+
+
+class TestLoadtxtParseIntsViaFloat(_DeprecationTestCase):
+ message = r"loadtxt\(\): Parsing an integer via a float is deprecated.*"
+
+ @pytest.mark.parametrize("dtype", np.typecodes["AllInteger"])
+ def test_deprecated_warning(self, dtype):
+ with pytest.warns(DeprecationWarning, match=self.message):
+ np.loadtxt(["10.5"], dtype=dtype)
+
+ @pytest.mark.parametrize("dtype", np.typecodes["AllInteger"])
+ def test_deprecated_raised(self, dtype):
+ # The DeprecationWarning is chained when raised, so test manually:
+ with warnings.catch_warnings():
+ warnings.simplefilter("error", DeprecationWarning)
+ try:
+ np.loadtxt(["10.5"], dtype=dtype)
+ except ValueError as e:
+ assert isinstance(e.__cause__, DeprecationWarning)