|
| 1 | +""" Investigate casting rules |
| 2 | +
|
| 3 | +Specifically, does the data affect the output type? |
| 4 | +
|
| 5 | +To do this, we combine all types and investigate the output, when: |
| 6 | +
|
| 7 | +* type A, B have data (0) |
| 8 | +* type A has max / min for type A, type B has data (0) |
| 9 | +* type A has max / min for type A, type B has max / min for type B |
| 10 | +
|
| 11 | +Expecting that in all cases the same dtype will result. |
| 12 | +
|
| 13 | +In fact what happens is that this _is_ true if A and B are atleast_1d, but it is |
| 14 | +not true if (A or B is a scalar, for numpy 1.6.1). It looks like numpy 1.6.1 is |
| 15 | +first checking whether the scalar B np.can_cast to type A, if so, then the |
| 16 | +return type is type of A, otherwise it uses the array casting rules. |
| 17 | +
|
| 18 | +Thus - for numpy 1.6.1:: |
| 19 | +
|
| 20 | + >>> import numpy as np |
| 21 | + >>> Adata = np.array([127], dtype=np.int8) |
| 22 | + >>> Bdata = np.int16(127) |
| 23 | + >>> (Adata + Bdata).dtype |
| 24 | + dtype('int8') |
| 25 | + >>> Bdata = np.int16(128) |
| 26 | + >>> (Adata + Bdata).dtype |
| 27 | + dtype('int16') |
| 28 | + >>> Bdata = np.array([127], dtype=np.int16) |
| 29 | + >>> (Adata + Bdata).dtype |
| 30 | + dtype('int16') |
| 31 | +""" |
| 32 | + |
| 33 | +from distutils.version import LooseVersion |
| 34 | + |
| 35 | +import numpy as np |
| 36 | + |
| 37 | +from nose.tools import assert_equal |
| 38 | + |
| 39 | +NP_VERSION = LooseVersion(np.__version__) |
| 40 | + |
| 41 | +ALL_TYPES = (np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float'] + |
| 42 | + np.sctypes['complex']) |
| 43 | + |
| 44 | +def get_info(type): |
| 45 | + try: |
| 46 | + return np.finfo(type) |
| 47 | + except ValueError: |
| 48 | + return np.iinfo(type) |
| 49 | + |
| 50 | + |
| 51 | +def test_cast_assumptions(): |
| 52 | + # Check that dtype is predictable from binary operations |
| 53 | + npa = np.array |
| 54 | + for A in ALL_TYPES: |
| 55 | + a_info = get_info(A) |
| 56 | + for B in ALL_TYPES: |
| 57 | + b_info = get_info(B) |
| 58 | + Adata = np.zeros((2,), dtype=A) |
| 59 | + Bdata = np.zeros((2,), dtype=B) |
| 60 | + Bscalar = B(0) # 0 can always be cast to type A |
| 61 | + out_dtype = (Adata + Bdata).dtype |
| 62 | + out_sc_dtype = (Adata + Bscalar).dtype |
| 63 | + assert_equal(out_dtype, (Adata * Bdata).dtype) |
| 64 | + assert_equal(out_sc_dtype, (Adata * Bscalar).dtype) |
| 65 | + Adata[0], Adata[1] = a_info.min, a_info.max |
| 66 | + assert_equal(out_dtype, (Adata + Bdata).dtype) |
| 67 | + assert_equal(out_dtype, (Adata * Bdata).dtype) |
| 68 | + # Compiled array gives same dtype |
| 69 | + assert_equal(out_dtype, npa([Adata[0:1], Bdata[0:1]]).dtype) |
| 70 | + assert_equal(out_sc_dtype, (Adata + Bscalar).dtype) |
| 71 | + assert_equal(out_sc_dtype, (Adata * Bscalar).dtype) |
| 72 | + # Compiled array with scalars gives promoted (can_cast) dtype |
| 73 | + assert_equal(out_dtype, npa([Adata[0], Bscalar]).dtype) |
| 74 | + Bdata[0], Bdata[1] = b_info.min, b_info.max |
| 75 | + Bscalar = B(b_info.max) # cannot always be cast to type A |
| 76 | + assert_equal(out_dtype, (Adata + Bdata).dtype) |
| 77 | + assert_equal(out_dtype, (Adata * Bdata).dtype) |
| 78 | + # Compiled array with scalars - promoted dtype |
| 79 | + assert_equal(out_dtype, npa([Adata[0], Bscalar]).dtype) |
| 80 | + # Here numpy >= 1.6.1 differs from previous versions |
| 81 | + if NP_VERSION <= '1.5.1' or np.can_cast(Bscalar, A): |
| 82 | + assert_equal(out_sc_dtype, (Adata + Bscalar).dtype) |
| 83 | + assert_equal(out_sc_dtype, (Adata * Bscalar).dtype) |
| 84 | + else: # casting rules changed for 1.6 onwards |
| 85 | + assert_equal(out_dtype, (Adata + Bscalar).dtype) |
| 86 | + assert_equal(out_dtype, (Adata * Bscalar).dtype) |
0 commit comments