diff options
author | Mark Wiebe <mwwiebe@gmail.com> | 2011-01-18 17:52:42 -0800 |
---|---|---|
committer | Mark Wiebe <mwwiebe@gmail.com> | 2011-01-18 17:54:55 -0800 |
commit | beba8f4e7c7071d0619558a66f0a096ca705c1c5 (patch) | |
tree | f1f606ceaee945cffc357a0e5ec4fabfd0791a80 /numpy | |
parent | 81a28e7309e13f0a22464697b14c2c7d4c272ea5 (diff) | |
download | numpy-beba8f4e7c7071d0619558a66f0a096ca705c1c5.tar.gz |
ENH: core: Add functions PyArray_CanCastArrayTo and PyArray_ResultType
They have also been exposed to Python.
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/add_newdocs.py | 102 | ||||
-rw-r--r-- | numpy/core/code_generators/numpy_api.py | 2 | ||||
-rw-r--r-- | numpy/core/numeric.py | 5 | ||||
-rw-r--r-- | numpy/core/src/multiarray/convert_datatype.c | 474 | ||||
-rw-r--r-- | numpy/core/src/multiarray/convert_datatype.h | 6 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarraymodule.c | 108 | ||||
-rw-r--r-- | numpy/core/src/multiarray/new_iterator.c.src | 92 | ||||
-rw-r--r-- | numpy/core/tests/test_new_iterator.py | 7 | ||||
-rw-r--r-- | numpy/core/tests/test_numeric.py | 109 |
9 files changed, 753 insertions, 152 deletions
diff --git a/numpy/add_newdocs.py b/numpy/add_newdocs.py index a3381d47e..0f58f66a2 100644 --- a/numpy/add_newdocs.py +++ b/numpy/add_newdocs.py @@ -1144,9 +1144,12 @@ add_newdoc('numpy.core.multiarray', 'lexsort', add_newdoc('numpy.core.multiarray', 'can_cast', """ - can_cast(fromtype, totype) + can_cast(from, totype, casting = 'safe') - Returns True if cast between data types can occur without losing precision. + Returns True if cast between data types can occur according to the + casting rule. If from is a scalar or array scalar, also returns + True if the scalar value can be cast without overflow or truncation + to an integer. Parameters ---------- @@ -1154,14 +1157,19 @@ add_newdoc('numpy.core.multiarray', 'can_cast', Data type to cast from. totype : dtype or dtype specifier Data type to cast to. + casting : casting rule + May be any of 'no', 'equiv', 'safe', 'same_kind', or 'unsafe'. Returns ------- out : bool - True if cast can occur without losing precision. + True if cast can occur according to the casting rule. Examples -------- + + Basic examples + >>> np.can_cast(np.int32, np.int64) True >>> np.can_cast(np.float64, np.complex) @@ -1176,6 +1184,52 @@ add_newdoc('numpy.core.multiarray', 'can_cast', >>> np.can_cast('i4', 'S4') True + Casting scalars + + >>> np.can_cast(100, 'i1') + True + >>> np.can_cast(150, 'i1') + False + >>> np.can_cast(150, 'u1') + True + + >>> np.can_cast(3.5e100, np.float32) + False + >>> np.can_cast(1000.0, np.float32) + True + + Array scalar checks the value, array does not + + >>> np.can_cast(np.array(1000.0), np.float32) + True + >>> np.can_cast(np.array([1000.0]), np.float32) + False + + Using the casting rules + + >>> np.can_cast('i8', 'i8', 'no') + True + >>> np.can_cast('<i8', '>i8', 'no') + False + + >>> np.can_cast('<i8', '>i8', 'equiv') + True + >>> np.can_cast('<i4', '>i8', 'equiv') + False + + >>> np.can_cast('<i4', '>i8', 'safe') + True + >>> np.can_cast('<i8', '>i4', 'safe') + False + + >>> np.can_cast('<i8', '>i4', 'same_kind') + True + >>> np.can_cast('<i8', '>u4', 'same_kind') + False + + >>> np.can_cast('<i8', '>u4', 'unsafe') + True + """) add_newdoc('numpy.core.multiarray', 'promote_types', @@ -1255,6 +1309,48 @@ add_newdoc('numpy.core.multiarray', 'min_scalar_type', """) +add_newdoc('numpy.core.multiarray', 'result_type', + """ + result_type(*arrays_and_dtypes) + + Returns the type that results from applying the NumPy + type promotion rules to the arguments. + + Type promotion in NumPy works similarly to the rules in languages + like C++, with some slight differences. When both scalars and + arrays are used, the array's type takes precedence and the actual value + of the scalar is taken into account. + + For example, calculating 3*a, where a is an array of 32-bit floats, + intuitively should result in a 32-bit float output. If the 3 is a + 32-bit integer, the NumPy rules indicate it can't convert losslessly + into a 32-bit float, so a 64-bit float should be the result type. + By examining the value of the constant, '3', we see that it fits in + an 8-bit integer, which can be cast losslessly into the 32-bit float. + + Parameters + ---------- + arrays_and_dtypes : list of arrays and dtypes + The operands of some operation whose result type is needed. + + Returns + ------- + out : dtype + The result type. + + Examples + -------- + >>> np.result_type(3, np.arange(7, dtype='i1')) + dtype('int8') + + >>> np.result_type('i4', 'c8') + dtype('complex128') + + >>> np.result_type(3.0, -2) + dtype('float64') + + """) + add_newdoc('numpy.core.multiarray','newbuffer', """newbuffer(size) diff --git a/numpy/core/code_generators/numpy_api.py b/numpy/core/code_generators/numpy_api.py index 341ff2e5e..1d98a8c4d 100644 --- a/numpy/core/code_generators/numpy_api.py +++ b/numpy/core/code_generators/numpy_api.py @@ -296,6 +296,8 @@ multiarray_funcs_api = { 'PyArray_CountNonzero': 262, 'PyArray_PromoteTypes': 263, 'PyArray_MinScalarType': 264, + 'PyArray_ResultType': 265, + 'PyArray_CanCastArrayTo': 266, } ufunc_types_api = { diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py index a39ef62bd..8609fbc4b 100644 --- a/numpy/core/numeric.py +++ b/numpy/core/numeric.py @@ -2,8 +2,8 @@ __all__ = ['newaxis', 'ndarray', 'flatiter', 'newiter', 'nested_iters', 'ufunc', 'arange', 'array', 'zeros', 'count_nonzero', 'empty', 'broadcast', 'dtype', 'fromstring', 'fromfile', 'frombuffer', 'int_asbuffer', 'where', 'argwhere', - 'concatenate', 'fastCopyAndTranspose', 'lexsort', - 'set_numeric_ops', 'can_cast', 'promote_types', 'min_scalar_type', + 'concatenate', 'fastCopyAndTranspose', 'lexsort', 'set_numeric_ops', + 'can_cast', 'promote_types', 'min_scalar_type', 'result_type', 'asarray', 'asanyarray', 'ascontiguousarray', 'asfortranarray', 'isfortran', 'empty_like', 'zeros_like', 'correlate', 'convolve', 'inner', 'dot', 'outer', 'vdot', @@ -214,6 +214,7 @@ set_numeric_ops = multiarray.set_numeric_ops can_cast = multiarray.can_cast promote_types = multiarray.promote_types min_scalar_type = multiarray.min_scalar_type +result_type = multiarray.result_type lexsort = multiarray.lexsort compare_chararrays = multiarray.compare_chararrays putmask = multiarray.putmask diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index 6d6ec7528..a13eea6b5 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -225,15 +225,17 @@ PyArray_CanCastSafely(int fromtype, int totype) /*NUMPY_API * leaves reference count alone --- cannot be NULL + * + * TODO: For NumPy 2.0, add a NPY_CASTING parameter (can_cast_to function). */ -NPY_NO_EXPORT Bool +NPY_NO_EXPORT npy_bool PyArray_CanCastTo(PyArray_Descr *from, PyArray_Descr *to) { int fromtype=from->type_num; int totype=to->type_num; - Bool ret; + npy_bool ret; - ret = (Bool) PyArray_CanCastSafely(fromtype, totype); + ret = (npy_bool) PyArray_CanCastSafely(fromtype, totype); if (ret) { /* Check String and Unicode more closely */ if (fromtype == PyArray_STRING) { @@ -258,10 +260,211 @@ PyArray_CanCastTo(PyArray_Descr *from, PyArray_Descr *to) return ret; } +/* Provides an ordering for the dtype 'kind' character codes */ +static int +dtype_kind_to_ordering(char kind) +{ + switch (kind) { + /* Boolean kind */ + case 'b': + return 0; + /* Unsigned int kind */ + case 'u': + return 1; + /* Signed int kind */ + case 'i': + return 2; + /* Float kind */ + case 'f': + return 4; + /* Complex kind */ + case 'c': + return 5; + /* String kind */ + case 'S': + case 'a': + return 6; + /* Unicode kind */ + case 'U': + return 7; + /* Void kind */ + case 'V': + return 8; + /* Object kind */ + case 'O': + return 9; + /* Anything else - ideally shouldn't happen... */ + default: + return 10; + } +} + +/* Converts a type number from unsigned to signed */ +static int +type_num_unsigned_to_signed(int type_num) +{ + switch (type_num) { + case NPY_UBYTE: + return NPY_BYTE; + case NPY_USHORT: + return NPY_SHORT; + case NPY_UINT: + return NPY_INT; + case NPY_ULONG: + return NPY_LONG; + case NPY_ULONGLONG: + return NPY_LONGLONG; + default: + return type_num; + } +} + +NPY_NO_EXPORT npy_bool +can_cast_to(PyArray_Descr *from, PyArray_Descr *to, NPY_CASTING casting) +{ + /* If unsafe casts are allowed */ + if (casting == NPY_UNSAFE_CASTING) { + return 1; + } + /* Equivalent types can be cast with any value of 'casting' */ + else if (PyArray_EquivTypenums(from->type_num, to->type_num)) { + /* For complicated case, use EquivTypes (for now) */ + if (PyTypeNum_ISUSERDEF(from->type_num) || + PyDataType_HASFIELDS(from) || + from->subarray != NULL) { + int ret; + + /* Only NPY_NO_CASTING prevents byte order conversion */ + if ((casting != NPY_NO_CASTING) && + (!PyArray_ISNBO(from->byteorder) || + !PyArray_ISNBO(to->byteorder))) { + PyArray_Descr *nbo_from, *nbo_to; + + nbo_from = PyArray_DescrNewByteorder(from, NPY_NATIVE); + nbo_to = PyArray_DescrNewByteorder(to, NPY_NATIVE); + if (nbo_from == NULL || nbo_to == NULL) { + Py_XDECREF(nbo_from); + Py_XDECREF(nbo_to); + PyErr_Clear(); + return 0; + } + ret = PyArray_EquivTypes(nbo_from, nbo_to); + Py_DECREF(nbo_from); + Py_DECREF(nbo_to); + } + else { + ret = PyArray_EquivTypes(from, to); + } + return ret; + } + + switch (casting) { + case NPY_NO_CASTING: + return (from->elsize == to->elsize) && + PyArray_ISNBO(from->byteorder) == + PyArray_ISNBO(to->byteorder); + case NPY_EQUIV_CASTING: + return (from->elsize == to->elsize); + case NPY_SAFE_CASTING: + return (from->elsize <= to->elsize); + default: + return 1; + } + } + /* If safe or same-kind casts are allowed */ + else if (casting == NPY_SAFE_CASTING || casting == NPY_SAME_KIND_CASTING) { + if (PyArray_CanCastTo(from, to)) { + return 1; + } + else if(casting == NPY_SAME_KIND_CASTING) { + /* + * Also allow casting from lower to higher kinds, according + * to the ordering provided by dtype_kind_to_ordering. + */ + return dtype_kind_to_ordering(from->kind) <= + dtype_kind_to_ordering(to->kind); + } + else { + return 0; + } + } + /* NPY_NO_CASTING or NPY_EQUIV_CASTING was specified */ + else { + return 0; + } +} + +/* CanCastArrayTo needs this function */ +static int min_scalar_type_num(char *valueptr, int type_num, + int *is_small_unsigned); + +/*NUMPY_API + * Returns 1 if the array object may be cast to the given data type using + * the casting rule, 0 otherwise. This differs from PyArray_CanCastTo in + * that it handles scalar arrays (0 dimensions) specially, by checking + * their value. + */ +NPY_NO_EXPORT npy_bool +PyArray_CanCastArrayTo(PyArrayObject *arr, PyArray_Descr *to, + NPY_CASTING casting) +{ + PyArray_Descr *from = PyArray_DESCR(arr); + + /* If it's not a scalar, use the standard rules */ + if (PyArray_NDIM(arr) > 0 || !PyTypeNum_ISNUMBER(from->type_num)) { + return can_cast_to(from, to, casting); + } + /* Otherwise, check the value */ + else { + char *data = PyArray_BYTES(arr); + int swap = !PyArray_ISNBO(from->byteorder); + int is_small_unsigned = 0, type_num; + npy_bool ret; + PyArray_Descr *dtype; + + /* An aligned memory buffer large enough to hold any type */ +#if NPY_SIZEOF_LONGLONG >= NPY_SIZEOF_CLONGDOUBLE + npy_longlong value; +#else + npy_clongdouble value; +#endif + from->f->copyswap(&value, data, swap, NULL); + + type_num = min_scalar_type_num((char *)&value, from->type_num, + &is_small_unsigned); + + /* + * If we've got a small unsigned scalar, and the 'to' type + * is not unsigned, then make it signed to allow the value + * to be cast more appropriately. + */ + if (is_small_unsigned && !(PyTypeNum_ISUNSIGNED(to->type_num))) { + type_num = type_num_unsigned_to_signed(type_num); + } + + dtype = PyArray_DescrFromType(type_num); + if (dtype == NULL) { + return 0; + } +#if 0 + printf("min scalar cast "); + PyObject_Print(dtype, stdout, 0); + printf(" to "); + PyObject_Print(to, stdout, 0); + printf("\n"); +#endif + ret = can_cast_to(dtype, to, casting); + Py_DECREF(dtype); + return ret; + } +} + /*NUMPY_API * See if array scalars can be cast. + * + * TODO: For NumPy 2.0, add a NPY_CASTING parameter. */ -NPY_NO_EXPORT Bool +NPY_NO_EXPORT npy_bool PyArray_CanCastScalar(PyTypeObject *from, PyTypeObject *to) { int fromtype; @@ -272,7 +475,57 @@ PyArray_CanCastScalar(PyTypeObject *from, PyTypeObject *to) if (fromtype == PyArray_NOTYPE || totype == PyArray_NOTYPE) { return FALSE; } - return (Bool) PyArray_CanCastSafely(fromtype, totype); + return (npy_bool) PyArray_CanCastSafely(fromtype, totype); +} + +/* + * Internal promote types function which handles unsigned integers which + * fit in same-sized signed integers specially. + */ +static PyArray_Descr * +promote_types(PyArray_Descr *type1, PyArray_Descr *type2, + int is_small_unsigned1, int is_small_unsigned2) +{ + if (is_small_unsigned1) { + int type_num1 = type1->type_num, type_num2 = type2->type_num, ret_type_num; + + if (type_num2 < NPY_NTYPES && !(PyTypeNum_ISBOOL(type_num2) || + PyTypeNum_ISUNSIGNED(type_num2))) { + /* Convert to the equivalent-sized signed integer */ + type_num1 = type_num_unsigned_to_signed(type_num1); + + ret_type_num = _npy_type_promotion_table[type_num1][type_num2]; + /* The table doesn't handle string/unicode/void, check the result */ + if (ret_type_num >= 0) { + return PyArray_DescrFromType(ret_type_num); + } + } + + return PyArray_PromoteTypes(type1, type2); + } + else if (is_small_unsigned2) { + int type_num1 = type1->type_num, + type_num2 = type2->type_num, + ret_type_num; + + if (type_num1 < NPY_NTYPES && !(PyTypeNum_ISBOOL(type_num1) || + PyTypeNum_ISUNSIGNED(type_num1))) { + /* Convert to the equivalent-sized signed integer */ + type_num2 = type_num_unsigned_to_signed(type_num2); + + ret_type_num = _npy_type_promotion_table[type_num1][type_num2]; + /* The table doesn't handle string/unicode/void, check the result */ + if (ret_type_num >= 0) { + return PyArray_DescrFromType(ret_type_num); + } + } + + return PyArray_PromoteTypes(type1, type2); + } + else { + return PyArray_PromoteTypes(type1, type2); + } + } /*NUMPY_API @@ -401,19 +654,28 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) * NOTE: While this is unlikely to be a performance problem, if * it is it could be reverted to a simple positive/negative * check as the previous system used. + * + * The is_small_unsigned output flag indicates whether it's an unsigned integer, + * and would fit in a signed integer of the same bit size. */ -static int min_scalar_type_num(char *valueptr, int type_num) +static int min_scalar_type_num(char *valueptr, int type_num, + int *is_small_unsigned) { switch (type_num) { case NPY_BOOL: { return NPY_BOOL; } case NPY_UBYTE: { + char value = *valueptr; + if (value <= NPY_MAX_BYTE) { + *is_small_unsigned = 1; + } return NPY_UBYTE; } case NPY_BYTE: { char value = *valueptr; if (value >= 0) { + *is_small_unsigned = 1; return NPY_UBYTE; } break; @@ -421,14 +683,21 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_USHORT: { npy_ushort value = *(npy_ushort *)valueptr; if (value <= NPY_MAX_UBYTE) { + if (value <= NPY_MAX_BYTE) { + *is_small_unsigned = 1; + } return NPY_UBYTE; } + + if (value <= NPY_MAX_SHORT) { + *is_small_unsigned = 1; + } break; } case NPY_SHORT: { npy_short value = *(npy_short *)valueptr; if (value >= 0) { - return min_scalar_type_num(valueptr, NPY_USHORT); + return min_scalar_type_num(valueptr, NPY_USHORT, is_small_unsigned); } else if (value >= NPY_MIN_BYTE) { return NPY_BYTE; @@ -441,11 +710,21 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_UINT: { npy_uint value = *(npy_uint *)valueptr; if (value <= NPY_MAX_UBYTE) { + if (value < NPY_MAX_BYTE) { + *is_small_unsigned = 1; + } return NPY_UBYTE; } else if (value <= NPY_MAX_USHORT) { + if (value <= NPY_MAX_SHORT) { + *is_small_unsigned = 1; + } return NPY_USHORT; } + + if (value <= NPY_MAX_INT) { + *is_small_unsigned = 1; + } break; } #if NPY_SIZEOF_LONG == NPY_SIZEOF_INT @@ -454,7 +733,7 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_INT: { npy_int value = *(npy_int *)valueptr; if (value >= 0) { - return min_scalar_type_num(valueptr, NPY_UINT); + return min_scalar_type_num(valueptr, NPY_UINT, is_small_unsigned); } else if (value >= NPY_MIN_BYTE) { return NPY_BYTE; @@ -468,20 +747,33 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_ULONG: { npy_ulong value = *(npy_ulong *)valueptr; if (value <= NPY_MAX_UBYTE) { + if (value <= NPY_MAX_BYTE) { + *is_small_unsigned = 1; + } return NPY_UBYTE; } else if (value <= NPY_MAX_USHORT) { + if (value <= NPY_MAX_SHORT) { + *is_small_unsigned = 1; + } return NPY_USHORT; } else if (value <= NPY_MAX_UINT) { + if (value <= NPY_MAX_INT) { + *is_small_unsigned = 1; + } return NPY_UINT; } + + if (value <= NPY_MAX_LONG) { + *is_small_unsigned = 1; + } break; } case NPY_LONG: { npy_long value = *(npy_long *)valueptr; if (value >= 0) { - return min_scalar_type_num(valueptr, NPY_ULONG); + return min_scalar_type_num(valueptr, NPY_ULONG, is_small_unsigned); } else if (value >= NPY_MIN_BYTE) { return NPY_BYTE; @@ -501,19 +793,35 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_ULONGLONG: { npy_ulonglong value = *(npy_ulonglong *)valueptr; if (value <= NPY_MAX_UBYTE) { + if (value <= NPY_MAX_BYTE) { + *is_small_unsigned = 1; + } return NPY_UBYTE; } else if (value <= NPY_MAX_USHORT) { + if (value <= NPY_MAX_SHORT) { + *is_small_unsigned = 1; + } return NPY_USHORT; } else if (value <= NPY_MAX_UINT) { + if (value <= NPY_MAX_INT) { + *is_small_unsigned = 1; + } return NPY_UINT; } #if NPY_SIZEOF_LONG != NPY_SIZEOF_INT && NPY_SIZEOF_LONG != NPY_SIZEOF_LONGLONG else if (value <= NPY_MAX_ULONG) { + if (value <= NPY_MAX_LONG) { + *is_small_unsigned = 1; + } return NPY_ULONG; } #endif + + if (value <= NPY_MAX_LONGLONG) { + *is_small_unsigned = 1; + } break; } #if NPY_SIZEOF_LONG == NPY_SIZEOF_LONGLONG @@ -522,7 +830,7 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_LONGLONG: { npy_longlong value = *(npy_longlong *)valueptr; if (value >= 0) { - return min_scalar_type_num(valueptr, NPY_ULONGLONG); + return min_scalar_type_num(valueptr, NPY_ULONGLONG, is_small_unsigned); } else if (value >= NPY_MIN_BYTE) { return NPY_BYTE; @@ -584,14 +892,14 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_CFLOAT: { npy_cfloat value = *(npy_cfloat *)valueptr; if (value.imag == 0) { - return min_scalar_type_num((char *)&value.real, NPY_FLOAT); + return min_scalar_type_num((char *)&value.real, NPY_FLOAT, is_small_unsigned); } break; } case NPY_CDOUBLE: { npy_cdouble value = *(npy_cdouble *)valueptr; if (value.imag == 0) { - return min_scalar_type_num((char *)&value.real, NPY_DOUBLE); + return min_scalar_type_num((char *)&value.real, NPY_DOUBLE, is_small_unsigned); } /* TODO: Check overflow values as for float case */ return NPY_CFLOAT; @@ -599,7 +907,7 @@ static int min_scalar_type_num(char *valueptr, int type_num) case NPY_CLONGDOUBLE: { npy_cdouble value = *(npy_cdouble *)valueptr; if (value.imag == 0) { - return min_scalar_type_num((char *)&value.real, NPY_LONGDOUBLE); + return min_scalar_type_num((char *)&value.real, NPY_LONGDOUBLE, is_small_unsigned); } /* TODO: Check overflow values as for float case */ return NPY_CFLOAT; @@ -626,6 +934,7 @@ PyArray_MinScalarType(PyArrayObject *arr) else { char *data = PyArray_BYTES(arr); int swap = !PyArray_ISNBO(dtype->byteorder); + int is_small_unsigned = 0; /* An aligned memory buffer large enough to hold any type */ #if NPY_SIZEOF_LONGLONG >= NPY_SIZEOF_CLONGDOUBLE npy_longlong value; @@ -635,12 +944,149 @@ PyArray_MinScalarType(PyArrayObject *arr) dtype->f->copyswap(&value, data, swap, NULL); return PyArray_DescrFromType( - min_scalar_type_num((char *)&value, dtype->type_num)); + min_scalar_type_num((char *)&value, dtype->type_num, &is_small_unsigned)); } } /*NUMPY_API + * Produces the result type of a bunch of inputs, using the UFunc + * type promotion rules. + * + * If all the inputs are scalars (have 0 dimensions), does a regular + * type promotion. Otherwise, does a type promotion on the MinScalarType + * of all the inputs. Data types passed directly are treated as vector + * types. + * + */ +NPY_NO_EXPORT PyArray_Descr * +PyArray_ResultType(npy_intp narrs, PyArrayObject **arr, + npy_intp ndtypes, PyArray_Descr **dtypes) +{ + npy_intp i; + int all_scalar; + PyArray_Descr *ret = NULL, *tmpret; + int ret_is_small_unsigned = 0; + + /* If there's just one type, pass it through */ + if (narrs + ndtypes == 1) { + if (narrs == 1) { + ret = PyArray_DESCR(arr[0]); + } + else { + ret = dtypes[0]; + } + Py_INCREF(ret); + return ret; + } + + /* Determine if there are any scalars */ + if (ndtypes > 0) { + all_scalar = 0; + } + else { + all_scalar = 1; + for (i = 0; i < narrs; ++i) { + if (PyArray_NDIM(arr[i]) != 0) { + all_scalar = 0; + break; + } + } + } + + /* Loop through all the types, promoting them */ + if (all_scalar) { + for (i = 0; i < narrs; ++i) { + PyArray_Descr *tmp = PyArray_DESCR(arr[i]); + /* Combine it with the existing type */ + if (ret == NULL) { + ret = tmp; + Py_INCREF(ret); + } + else { + tmpret = PyArray_PromoteTypes(tmp, ret); + Py_DECREF(ret); + ret = tmpret; + } + } + } + else { + for (i = 0; i < narrs; ++i) { + /* Get the min scalar type for the array */ + PyArray_Descr *tmp = PyArray_DESCR(arr[i]); + int tmp_is_small_unsigned = 0; + /* + * If it's a scalar, find the min scalar type. The function is expanded here so that + * we can flag whether we've got an unsigned integer which would fit an a signed integer + * of the same size, something not exposed in the public API. + */ + if (PyArray_NDIM(arr[i]) == 0 && PyTypeNum_ISNUMBER(tmp->type_num)) { + char *data = PyArray_BYTES(arr[i]); + int swap = !PyArray_ISNBO(tmp->byteorder); + int type_num; + /* An aligned memory buffer large enough to hold any type */ +#if NPY_SIZEOF_LONGLONG >= NPY_SIZEOF_CLONGDOUBLE + npy_longlong value; +#else + npy_clongdouble value; +#endif + tmp->f->copyswap(&value, data, swap, NULL); + type_num = min_scalar_type_num((char *)&value, tmp->type_num, &tmp_is_small_unsigned); + tmp = PyArray_DescrFromType(type_num); + if (tmp == NULL) { + Py_XDECREF(ret); + return NULL; + } + } + else { + Py_INCREF(tmp); + } + /* Combine it with the existing type */ + if (ret == NULL) { + ret = tmp; + ret_is_small_unsigned = tmp_is_small_unsigned; + } + else { +#if 0 + printf("promoting type "); + PyObject_Print(tmp, stdout, 0); + printf(" (%d) ", tmp_is_small_unsigned); + PyObject_Print(ret, stdout, 0); + printf(" (%d) ", ret_is_small_unsigned); + printf("\n"); +#endif + tmpret = promote_types(tmp, ret, tmp_is_small_unsigned, ret_is_small_unsigned); + ret_is_small_unsigned = tmp_is_small_unsigned && ret_is_small_unsigned; + Py_DECREF(tmp); + Py_DECREF(ret); + ret = tmpret; + } + } + + for (i = 0; i < ndtypes; ++i) { + PyArray_Descr *tmp = dtypes[i]; + /* Combine it with the existing type */ + if (ret == NULL) { + ret = tmp; + Py_INCREF(ret); + } + else { + if (ret_is_small_unsigned) { + tmpret = promote_types(tmp, ret, 0, ret_is_small_unsigned); + } + else { + tmpret = PyArray_PromoteTypes(tmp, ret); + } + Py_DECREF(ret); + ret = tmpret; + } + } + } + + return ret; +} + +/*NUMPY_API * Is the typenum valid? */ NPY_NO_EXPORT int diff --git a/numpy/core/src/multiarray/convert_datatype.h b/numpy/core/src/multiarray/convert_datatype.h index 60bdd8274..b45d0dfbd 100644 --- a/numpy/core/src/multiarray/convert_datatype.h +++ b/numpy/core/src/multiarray/convert_datatype.h @@ -13,7 +13,11 @@ PyArray_GetCastFunc(PyArray_Descr *descr, int type_num); NPY_NO_EXPORT int PyArray_CanCastSafely(int fromtype, int totype); -NPY_NO_EXPORT Bool +/* This can replace PyArray_CanCastTo for NumPy 2.0 (ABI change) */ +NPY_NO_EXPORT npy_bool +can_cast_to(PyArray_Descr *from, PyArray_Descr *to, NPY_CASTING casting); + +NPY_NO_EXPORT npy_bool PyArray_CanCastTo(PyArray_Descr *from, PyArray_Descr *to); NPY_NO_EXPORT int diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index ef4442368..be8a4c107 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -42,6 +42,7 @@ NPY_NO_EXPORT int NPY_NUMUSERTYPES = 0; #include "number.h" #include "scalartypes.h" #include "numpymemoryview.h" +#include "convert_datatype.h" #include "new_iterator_pywrap.h" /*NUMPY_API @@ -2187,23 +2188,51 @@ static PyObject * array_can_cast_safely(PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwds) { + PyObject *from_obj = NULL; PyArray_Descr *d1 = NULL; PyArray_Descr *d2 = NULL; Bool ret; PyObject *retobj = NULL; - static char *kwlist[] = {"from", "to", NULL}; + NPY_CASTING casting = NPY_SAFE_CASTING; + static char *kwlist[] = {"from", "to", "casting", NULL}; - if(!PyArg_ParseTupleAndKeywords(args, kwds, "O&O&", kwlist, - PyArray_DescrConverter2, &d1, PyArray_DescrConverter2, &d2)) { + if(!PyArg_ParseTupleAndKeywords(args, kwds, "OO&|O&", kwlist, + &from_obj, + PyArray_DescrConverter2, &d2, + PyArray_CastingConverter, &casting)) { goto finish; } - if (d1 == NULL || d2 == NULL) { + if (d2 == NULL) { PyErr_SetString(PyExc_TypeError, "did not understand one of the types; 'None' not accepted"); goto finish; } - ret = PyArray_CanCastTo(d1, d2); + /* If the first parameter is an object or scalar, use CanCastArrayTo */ + if (PyArray_Check(from_obj)) { + ret = PyArray_CanCastArrayTo((PyArrayObject *)from_obj, d2, casting); + } + else if (PyArray_IsScalar(from_obj, Generic) || + PyArray_IsPythonNumber(from_obj)) { + PyArrayObject *arr; + arr = (PyArrayObject *)PyArray_FromAny(from_obj, + NULL, 0, 0, 0, NULL); + if (arr == NULL) { + goto finish; + } + ret = PyArray_CanCastArrayTo(arr, d2, casting); + Py_DECREF(arr); + } + /* Otherwise use CanCastTo */ + else { + if (!PyArray_DescrConverter2(from_obj, &d1) || d1 == NULL) { + PyErr_SetString(PyExc_TypeError, + "did not understand one of the types; 'None' not accepted"); + goto finish; + } + ret = can_cast_to(d1, d2, casting); + } + retobj = ret ? Py_True : Py_False; Py_INCREF(retobj); @@ -2259,6 +2288,72 @@ array_min_scalar_type(PyObject *NPY_UNUSED(dummy), PyObject *args) return ret; } +static PyObject * +array_result_type(PyObject *NPY_UNUSED(dummy), PyObject *args) +{ + npy_intp i, len, narr = 0, ndtypes = 0; + PyArrayObject *arr[NPY_MAXARGS]; + PyArray_Descr *dtypes[NPY_MAXARGS]; + PyObject *ret = NULL; + + len = PyTuple_GET_SIZE(args); + if (len == 0) { + PyErr_SetString(PyExc_ValueError, + "at least one array or dtype is required"); + goto finish; + } + + for (i = 0; i < len; ++i) { + PyObject *obj = PyTuple_GET_ITEM(args, i); + if (PyArray_Check(obj)) { + if (narr == NPY_MAXARGS) { + PyErr_SetString(PyExc_ValueError, + "too many arguments"); + goto finish; + } + Py_INCREF(obj); + arr[narr] = (PyArrayObject *)obj; + ++narr; + } + else if (PyArray_IsScalar(obj, Generic) || + PyArray_IsPythonNumber(obj)) { + if (narr == NPY_MAXARGS) { + PyErr_SetString(PyExc_ValueError, + "too many arguments"); + goto finish; + } + arr[narr] = (PyArrayObject *)PyArray_FromAny(obj, + NULL, 0, 0, 0, NULL); + if (arr[narr] == NULL) { + goto finish; + } + ++narr; + } + else { + if (ndtypes == NPY_MAXARGS) { + PyErr_SetString(PyExc_ValueError, + "too many arguments"); + goto finish; + } + if (!PyArray_DescrConverter2(obj, &dtypes[ndtypes])) { + goto finish; + } + ++ndtypes; + } + } + + ret = (PyObject *)PyArray_ResultType(narr, arr, ndtypes, dtypes); + +finish: + for (i = 0; i < narr; ++i) { + Py_DECREF(arr[i]); + } + for (i = 0; i < ndtypes; ++i) { + Py_DECREF(dtypes[i]); + } + return ret; +} + #if !defined(NPY_PY3K) static PyObject * new_buffer(PyObject *NPY_UNUSED(dummy), PyObject *args) @@ -2870,6 +2965,9 @@ static struct PyMethodDef array_module_methods[] = { {"min_scalar_type", (PyCFunction)array_min_scalar_type, METH_VARARGS, NULL}, + {"result_type", + (PyCFunction)array_result_type, + METH_VARARGS, NULL}, #if !defined(NPY_PY3K) {"newbuffer", (PyCFunction)new_buffer, diff --git a/numpy/core/src/multiarray/new_iterator.c.src b/numpy/core/src/multiarray/new_iterator.c.src index 05a187d53..7d45d25d3 100644 --- a/numpy/core/src/multiarray/new_iterator.c.src +++ b/numpy/core/src/multiarray/new_iterator.c.src @@ -4,6 +4,7 @@ #define _MULTIARRAYMODULE #include <numpy/ndarrayobject.h> +#include "convert_datatype.h" #include "lowlevel_strided_loops.h" @@ -279,8 +280,6 @@ npyiter_get_common_dtype(npy_intp niter, PyArrayObject **op, int only_inputs, int output_scalars); static int npyiter_promote_types(int type1, int type2); -static int -npyiter_can_cast(PyArray_Descr *from, PyArray_Descr *to, NPY_CASTING casting); static PyArrayObject * npyiter_new_temp_array(NpyIter *iter, PyTypeObject *subtype, @@ -2627,91 +2626,6 @@ npyiter_prepare_operands(npy_intp niter, PyArrayObject **op_in, return 1; } -/* - * Returns 1 if the from -> to cast can be done, based on the casting - * flags provided in op_flags, and 0 otherwise. - * - * TODO: Maybe this approach, with the NPY_CASTING enum, should replace - * the PyArray_CanCastTo code for NumPy 2.0? - */ -static int -npyiter_can_cast(PyArray_Descr *from, PyArray_Descr *to, NPY_CASTING casting) -{ - /* If unsafe casts are allowed */ - if (casting == NPY_UNSAFE_CASTING) { - return 1; - } - /* Equivalent types can be cast with any value of 'casting' */ - else if (PyArray_EquivTypenums(from->type_num, to->type_num)) { - /* If the types are extended, convert to NBO and compare */ - if (PyTypeNum_ISEXTENDED(from->type_num)) { - int ret; - - /* Only NPY_NO_CASTING prevents byte order conversion */ - if ((casting != NPY_NO_CASTING) && - (!PyArray_ISNBO(from->byteorder) || - !PyArray_ISNBO(to->byteorder))) { - PyArray_Descr *nbo_from, *nbo_to; - - nbo_from = PyArray_DescrNewByteorder(from, NPY_NATIVE); - nbo_to = PyArray_DescrNewByteorder(to, NPY_NATIVE); - if (nbo_from == NULL || nbo_to == NULL) { - Py_XDECREF(nbo_from); - Py_XDECREF(nbo_to); - PyErr_Clear(); - return 0; - } - ret = PyArray_EquivTypes(nbo_from, nbo_to); - Py_DECREF(nbo_from); - Py_DECREF(nbo_to); - } - else { - if (from->type_num == NPY_STRING || - from->type_num == NPY_UNICODE) { - if (casting == NPY_SAME_KIND_CASTING) { - ret = 1; - } - else { - ret = (from->elsize <= to->elsize); - } - } - else { - ret = PyArray_EquivTypes(from, to); - } - } - return ret; - } - - if (casting == NPY_NO_CASTING) { - return PyArray_ISNBO(from->byteorder) == - PyArray_ISNBO(to->byteorder); - } - else { - return 1; - } - } - /* If safe or same-kind casts are allowed */ - else if (casting == NPY_SAFE_CASTING || casting == NPY_SAME_KIND_CASTING) { - if (PyArray_CanCastTo(from, to)) { - return 1; - } - else if(casting == NPY_SAME_KIND_CASTING) { - /* TODO: Should also allow casting from "lower" to "higher - * kinds, but kind is a char so we need to remap to - * an ordered version (like NPY_SCALARKIND is ordered). - */ - return from->kind == to->kind; - } - else { - return 0; - } - } - /* NPY_NO_CASTING or NPY_EQUIV_CASTING was specified */ - else { - return 0; - } -} - static const char * npyiter_casting_to_string(NPY_CASTING casting) { @@ -2745,7 +2659,7 @@ npyiter_check_casting(npy_intp niter, PyArrayObject **op, op_dtype[iiter])) { /* Check read (op -> temp) casting */ if ((op_itflags[iiter]&NPY_OP_ITFLAG_READ) && - !npyiter_can_cast(PyArray_DESCR(op[iiter]), + !PyArray_CanCastArrayTo(op[iiter], op_dtype[iiter], casting)) { PyErr_Format(PyExc_TypeError, @@ -2757,7 +2671,7 @@ npyiter_check_casting(npy_intp niter, PyArrayObject **op, } /* Check write (temp -> op) casting */ if ((op_itflags[iiter]&NPY_OP_ITFLAG_WRITE) && - !npyiter_can_cast(op_dtype[iiter], + !can_cast_to(op_dtype[iiter], PyArray_DESCR(op[iiter]), casting)) { PyErr_Format(PyExc_TypeError, diff --git a/numpy/core/tests/test_new_iterator.py b/numpy/core/tests/test_new_iterator.py index 87c9ce14e..f53b875e7 100644 --- a/numpy/core/tests/test_new_iterator.py +++ b/numpy/core/tests/test_new_iterator.py @@ -978,15 +978,10 @@ def test_iter_common_dtype(): casting='same_kind') assert_equal(i.dtypes[0], np.dtype('f4')); assert_equal(i.dtypes[1], np.dtype('f4')); - # TODO - # This case is weird - the scalar/array combination produces a cast - # classified as unsafe. I think this NumPy rule needs to be revisited. - # For example, when the scalar is writeable, a negative value could - # be written during iteration, invalidating the scalar kind assumed! i = newiter([array([3],dtype='u4'),array(0,dtype='i4')], ['common_dtype'], [['readonly','copy']]*2, - casting='unsafe') + casting='safe') assert_equal(i.dtypes[0], np.dtype('u4')); assert_equal(i.dtypes[1], np.dtype('u4')); i = newiter([array([3],dtype='u4'),array(-12,dtype='i4')], diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index 7a62cc88f..34ba89348 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -332,8 +332,8 @@ class TestFloatExceptions(TestCase): finally: np.seterr(**oldsettings) -class TestCoercion(TestCase): - def test_coercion(self): +class TestTypes(TestCase): + def check_promotion_cases(self, promote_func): """Tests that the scalars get coerced correctly.""" i8, i16, i32, i64 = int8(0), int16(0), int32(0), int64(0) u8, u16, u32, u64 = uint8(0), uint16(0), uint32(0), uint64(0) @@ -341,40 +341,85 @@ class TestCoercion(TestCase): c64, c128, cld = complex64(0), complex128(0), clongdouble(0) # coercion within the same type - assert_equal(np.add(i8,i16).dtype, int16) - assert_equal(np.add(i32,i8).dtype, int32) - assert_equal(np.add(i16,i64).dtype, int64) - assert_equal(np.add(u8,u32).dtype, uint32) - assert_equal(np.add(f32,f64).dtype, float64) - assert_equal(np.add(fld,f32).dtype, longdouble) - assert_equal(np.add(f64,fld).dtype, longdouble) - assert_equal(np.add(c128,c64).dtype, complex128) - assert_equal(np.add(cld,c128).dtype, clongdouble) - assert_equal(np.add(c64,fld).dtype, clongdouble) + assert_equal(promote_func(i8,i16), np.dtype(int16)) + assert_equal(promote_func(i32,i8), np.dtype(int32)) + assert_equal(promote_func(i16,i64), np.dtype(int64)) + assert_equal(promote_func(u8,u32), np.dtype(uint32)) + assert_equal(promote_func(f32,f64), np.dtype(float64)) + assert_equal(promote_func(fld,f32), np.dtype(longdouble)) + assert_equal(promote_func(f64,fld), np.dtype(longdouble)) + assert_equal(promote_func(c128,c64), np.dtype(complex128)) + assert_equal(promote_func(cld,c128), np.dtype(clongdouble)) + assert_equal(promote_func(c64,fld), np.dtype(clongdouble)) # coercion between types - assert_equal(np.add(i8,u8).dtype, int16) - assert_equal(np.add(u8,i32).dtype, int32) - assert_equal(np.add(i64,u32).dtype, int64) - assert_equal(np.add(u64,i32).dtype, float64) - assert_equal(np.add(i32,f32).dtype, float64) - assert_equal(np.add(i64,f32).dtype, float64) - assert_equal(np.add(f32,i16).dtype, float32) - assert_equal(np.add(f32,u32).dtype, float64) - assert_equal(np.add(f32,c64).dtype, complex64) - assert_equal(np.add(c128,f32).dtype, complex128) - assert_equal(np.add(cld,f64).dtype, clongdouble) + assert_equal(promote_func(i8,u8), np.dtype(int16)) + assert_equal(promote_func(u8,i32), np.dtype(int32)) + assert_equal(promote_func(i64,u32), np.dtype(int64)) + assert_equal(promote_func(u64,i32), np.dtype(float64)) + assert_equal(promote_func(i32,f32), np.dtype(float64)) + assert_equal(promote_func(i64,f32), np.dtype(float64)) + assert_equal(promote_func(f32,i16), np.dtype(float32)) + assert_equal(promote_func(f32,u32), np.dtype(float64)) + assert_equal(promote_func(f32,c64), np.dtype(complex64)) + assert_equal(promote_func(c128,f32), np.dtype(complex128)) + assert_equal(promote_func(cld,f64), np.dtype(clongdouble)) # coercion between scalars and 1-D arrays - assert_equal(np.add(array([i8]),i64).dtype, int8) - assert_equal(np.add(u64,array([i32])).dtype, int32) - assert_equal(np.add(i64,array([u32])).dtype, uint32) - assert_equal(np.add(int32(-1),array([u64])).dtype, float64) - assert_equal(np.add(f64,array([f32])).dtype, float32) - assert_equal(np.add(fld,array([f32])).dtype, float32) - assert_equal(np.add(array([f64]),fld).dtype, float64) - assert_equal(np.add(fld,array([c64])).dtype, complex64) - assert_equal(np.add(c64,array([f64])).dtype, complex128) + assert_equal(promote_func(array([i8]),i64), np.dtype(int8)) + assert_equal(promote_func(u64,array([i32])), np.dtype(int32)) + assert_equal(promote_func(i64,array([u32])), np.dtype(uint32)) + assert_equal(promote_func(int32(-1),array([u64])), np.dtype(float64)) + assert_equal(promote_func(f64,array([f32])), np.dtype(float32)) + assert_equal(promote_func(fld,array([f32])), np.dtype(float32)) + assert_equal(promote_func(array([f64]),fld), np.dtype(float64)) + assert_equal(promote_func(fld,array([c64])), np.dtype(complex64)) + + def test_coercion(self): + def res_type(a, b): + return np.add(a, b).dtype + self.check_promotion_cases(res_type) + + f64 = float64(0) + c64 = complex64(0) + # Scalars used to coerce to complex even if the value was real + assert_equal(res_type(c64,array([f64])), np.dtype(complex128)) + + def test_result_type(self): + self.check_promotion_cases(np.result_type) + + f64 = float64(0) + c64 = complex64(0) + # Scalars do not coerce to complex if the value is real + assert_equal(np.result_type(c64,array([f64])), np.dtype(float64)) + # But they do if the value is complex + assert_equal(np.result_type(complex64(3j),array([f64])), + np.dtype(complex128)) + def can_cast(self): + assert_(np.can_cast(np.int32, np.int64)) + assert_(np.can_cast(np.float64, np.complex)) + assert_(not np.can_cast(np.complex, np.float)) + + assert_(np.can_cast('i8', 'f8')) + assert_(not np.can_cast('i8', 'f4')) + assert_(np.can_cast('i4', 'S4')) + + assert_(np.can_cast('i8', 'i8', 'no')) + assert_(not np.can_cast('<i8', '>i8', 'no')) + + assert_(np.can_cast('<i8', '>i8', 'equiv')) + assert_(not np.can_cast('<i4', '>i8', 'equiv')) + + assert_(np.can_cast('<i4', '>i8', 'safe')) + assert_(not np.can_cast('<i8', '>i4', 'safe')) + + assert_(np.can_cast('<i8', '>i4', 'same_kind')) + assert_(not np.can_cast('<i8', '>u4', 'same_kind')) + + assert_(np.can_cast('<i8', '>u4', 'unsafe')) + + assert_raises(TypeError, np.can_cast, 'i4', None) + assert_raises(TypeError, np.can_cast, None, 'i4') class TestFromiter(TestCase): def makegen(self): |