Skip to content

.dtype.type class object is not preserved over some operations #11020

Closed

Description

@soupault
import numpy as np

for dt in (np.uint8, np.uint16, np.uint32, np.uint64,
           np.int8, np.int16, np.int32, np.int64,
           np.float16, np.float32, np.float64):
    x = np.array([0, 1], dtype=dt)
    if np.issubdtype(dt, np.floating):
        y = np.finfo(dt).max - x
    else:
        y = np.iinfo(dt).max - x

    id_x = hex(id(x.dtype.type))
    id_y = hex(id(y.dtype.type))

    print('{}: {}, {}, {}'.format(dt, id_x, id_y, id_x == id_y))
<class 'numpy.uint8'>: 0x7fa0e529f740, 0x7fa0e529f740, True
<class 'numpy.uint16'>: 0x7fa0e529f5a0, 0x7fa0e529f5a0, True
<class 'numpy.uint32'>: 0x7fa0e529f400, 0x7fa0e529f400, True
<class 'numpy.uint64'>: 0x7fa0e529f260, 0x7fa0e529f0c0, False
<class 'numpy.int8'>: 0x7fa0e529ff60, 0x7fa0e529ff60, True
<class 'numpy.int16'>: 0x7fa0e529fdc0, 0x7fa0e529fdc0, True
<class 'numpy.int32'>: 0x7fa0e529fc20, 0x7fa0e529fc20, True
<class 'numpy.int64'>: 0x7fa0e529fa80, 0x7fa0e529f8e0, False
<class 'numpy.float16'>: 0x7fa0e529ef20, 0x7fa0e529ef20, True
<class 'numpy.float32'>: 0x7fa0e529ed80, 0x7fa0e529ed80, True
<class 'numpy.float64'>: 0x7fa0e529ebe0, 0x7fa0e529ebe0, True

This could lead to some issues in users code heavily relying on dtype.type comparison (e.g. scikit-image/scikit-image#3043).

Linux, x64, numpy 1.14.2.

Activity

  1. eric-wieser commented on Apr 30, 2018

    @eric-wieser
    Member

    This is probably related to #10151. There's a fifth integer type that's not in your list there - you should iterate over [np.byte, np.short, np.intc, np.int_, np.longlong] + [np.ubyte, np.ushort, np.uintc, np.uint, np.ulonglong] to hit all the types

  2. eric-wieser commented on Sep 13, 2019

    @eric-wieser
    Member

    A better test now that gh-10151 is in:

    import numpy as np
    
    for dt in (np.ubyte, np.ushort, np.uintc, np.uint, np.ulonglong,
               np.byte, np.short, np.intc, np.int_, np.longlong,
               np.half, np.single, np.double):
        x = np.array([0, 1], dtype=dt)
        if np.issubdtype(dt, np.floating):
            y = np.finfo(dt).max - x
        else:
            y = np.iinfo(dt).max - x
    
        xt = x.dtype.type
        yt = y.dtype.type
    
        print('{}: {}, {}, {}'.format(dt, xt, yt, xt is yt))

    On 64 bit windows, this works as expected:

    <class 'numpy.uint8'>: <class 'numpy.uint8'>, <class 'numpy.uint8'>, True
    <class 'numpy.uint16'>: <class 'numpy.uint16'>, <class 'numpy.uint16'>, True
    <class 'numpy.uintc'>: <class 'numpy.uintc'>, <class 'numpy.uintc'>, True
    <class 'numpy.uint32'>: <class 'numpy.uint32'>, <class 'numpy.uint32'>, True
    <class 'numpy.uint64'>: <class 'numpy.uint64'>, <class 'numpy.uint64'>, True
    <class 'numpy.int8'>: <class 'numpy.int8'>, <class 'numpy.int8'>, True
    <class 'numpy.int16'>: <class 'numpy.int16'>, <class 'numpy.int16'>, True
    <class 'numpy.intc'>: <class 'numpy.intc'>, <class 'numpy.intc'>, True
    <class 'numpy.int32'>: <class 'numpy.int32'>, <class 'numpy.int32'>, True
    <class 'numpy.int64'>: <class 'numpy.int64'>, <class 'numpy.int64'>, True
    <class 'numpy.float16'>: <class 'numpy.float16'>, <class 'numpy.float16'>, True
    <class 'numpy.float32'>: <class 'numpy.float32'>, <class 'numpy.float32'>, True
    <class 'numpy.float64'>: <class 'numpy.float64'>, <class 'numpy.float64'>, True
    

    Can someone test this on linux (where int_ is 64-bit not 32-bit)?

  3. seberg commented on Sep 13, 2019

    @seberg
    Member

    Output on 64bit linux:

    <class 'numpy.uint8'>: <class 'numpy.uint8'>, <class 'numpy.uint8'>, True
    <class 'numpy.uint16'>: <class 'numpy.uint16'>, <class 'numpy.uint16'>, True
    <class 'numpy.uint32'>: <class 'numpy.uint32'>, <class 'numpy.uint32'>, True
    <class 'numpy.uint64'>: <class 'numpy.uint64'>, <class 'numpy.ulonglong'>, False
    <class 'numpy.ulonglong'>: <class 'numpy.ulonglong'>, <class 'numpy.ulonglong'>, True
    <class 'numpy.int8'>: <class 'numpy.int8'>, <class 'numpy.int8'>, True
    <class 'numpy.int16'>: <class 'numpy.int16'>, <class 'numpy.int16'>, True
    <class 'numpy.int32'>: <class 'numpy.int32'>, <class 'numpy.int32'>, True
    <class 'numpy.int64'>: <class 'numpy.int64'>, <class 'numpy.longlong'>, False
    <class 'numpy.longlong'>: <class 'numpy.longlong'>, <class 'numpy.longlong'>, True
    <class 'numpy.float16'>: <class 'numpy.float16'>, <class 'numpy.float16'>, True
    <class 'numpy.float32'>: <class 'numpy.float32'>, <class 'numpy.float32'>, True
    <class 'numpy.float64'>: <class 'numpy.float64'>, <class 'numpy.float64'>, True
    
  4. eric-wieser commented on Sep 13, 2019

    @eric-wieser
    Member

    Does replacing np.iinfo(dt).max with 2 give the same results? Would be nice to narrow down the issue

  5. seberg commented on Sep 13, 2019

    @seberg
    Member

    Ah, did not look at it carefully. Yes, that will fix things,

  6. seberg commented on Sep 13, 2019

    @seberg
    Member

    To be clear: Replacing by two gives the correct output (i.e. no False prints).

  7. eric-wieser commented on Sep 13, 2019

    @eric-wieser
    Member

    Just fired up WSL with numpy 1.11 (!), and I think I can pinpoint the bug:

    assert np.result_type(np.iinfo(np.int64).max, np.int64).type is np.int64  # fails
    assert np.result_type(np.iinfo(np.longlong).max, np.longlong).type is np.longlong # passes

    In my opinion, we should swap between these which one passes and which fails.

    We're being inconsistent about when we pick long and when we pick long long for int64 on linux. By name, long takes precedence - but it seems that when assigning dtypes to scalars based on their value, we prefer long long.

    On windows, it seems we handle int vs long for int32 consistently.

  8. seberg commented on Sep 13, 2019

    @seberg
    Member

    Yeah, the bug is that it falls to the unsigned path, and that fallback does not convert the original typenum to its unsigned version, but instead always uses ulonglong. If you use .min instead of max, it works ;).

  9. seberg commented on Nov 3, 2023

    @seberg
    Member

    The above is still there, I don't want to consider it a serious bug and NEP 50 adoption means that the repro's now all pass, since we ignore the oddity of the Python integer size now. (i.e. the path still exists, but it isn't hit here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions