Repository: numpy/numpy
File: numpy/_core/src/multiarray/unique.cpp (C++)

Code before:
size_t hash_integer(const T *value, npy_bool equal_nan) {
    return std::hash<T>{}(*value);
}

template <typename S, typename T, S (*real)(T), S (*imag)(T)>
size_t hash_complex(const T *value, npy_bool equal_nan) {
    S value_real = real(*value);
    S value_imag = imag(*value);
    int hasnan = npy_isnan(value_real) || npy_isnan(value_imag);
    if (equal_nan && hasnan) {
        return 0;
    }

    // Now, equal_nan is false or neither of the values is not NaN.
    // SO we don't need to worry about NaN here.
    size_t hval = std::hash<S>{}(value_real);
    #if NPY_SIZEOF_SIZE_T == 8
        /* multiply by the 64 bit FNV magic prime */
        /* hval *= 0x100000001b3ULL; */
        hval += (hval << 1) + (hval << 4) + (hval << 5) +
		        (hval << 7) + (hval << 8) + (hval << 40);
    #else /* NPY_SIZEOF_SIZE_T == 4 */
        /* multiply by the 32 bit FNV magic prime */
        /* hval *= 0x01000193; */
        hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
    #endif
    hval ^= std::hash<S>{}(value_imag);

    return hval;
}

template <typename T>
int equal_integer(const T *lhs, const T *rhs, npy_bool equal_nan) {
    return *lhs == *rhs;
}

template <typename S, typename T, S (*real)(T), S (*imag)(T)>
int equal_complex(const T *lhs, const T *rhs, npy_bool equal_nan) {
    S lhs_real = real(*lhs);
    S lhs_imag = imag(*lhs);
    int lhs_isnan = npy_isnan(lhs_real) || npy_isnan(lhs_imag);
    S rhs_real = real(*rhs);
    S rhs_imag = imag(*rhs);
    int rhs_isnan = npy_isnan(rhs_real) || npy_isnan(rhs_imag);

    if (lhs_isnan && rhs_isnan) {
        return equal_nan;
    }
    if (lhs_isnan || rhs_isnan) {
        return false;
    }

Review comment (bug):
This is wrong. The FNV algorithm operates on *bytes* of data. This code is operating on size_t sized ints. 

We want you to hash the bytes of the complex value. Very schematically, without actually compiling any code, take this worth a grain of salt, but something like this:

```
unsigned char* value_bytes = (unsigned char *)&value
size_t hash = npy_fnv1a(value_bytes, sizeof(T))
```

Code after:
size_t hash_integer(const T *value, npy_bool equal_nan) {
    return std::hash<T>{}(*value);
}

template <typename S, typename T, S (*real)(T), S (*imag)(T)>
size_t hash_complex(const T *value, npy_bool equal_nan) {
    S value_real = real(*value);
    S value_imag = imag(*value);
    int hasnan = npy_isnan(value_real) || npy_isnan(value_imag);
    if (equal_nan && hasnan) {
        return 0;
    }

    // Now, equal_nan is false or neither of the values is not NaN.
    // So we don't need to worry about NaN here.
    const unsigned char* value_bytes = reinterpret_cast<const unsigned char*>(value);
    size_t hash = npy_fnv1a(value_bytes, sizeof(T));

    return hash;
}

size_t hash_complex_clongdouble(const npy_clongdouble *value, npy_bool equal_nan) {
    npy_longdouble value_real = npy_creall(*value);
    npy_longdouble value_imag = npy_cimagl(*value);
    int hasnan = npy_isnan(value_real) || npy_isnan(value_imag);
    if (equal_nan && hasnan) {
        return 0;
    }

    // Now, equal_nan is false or neither of the values is not NaN.
    // So we don't need to worry about NaN here.
    // Some floating-point complex dtypes (e.g., npy_complex256) include undefined or
    // unused bits in their binary representation
    // (see: https://github.com/numpy/numpy/blob/main/numpy/_core/src/npymath/npy_math_private.h#L254-L261).
    // Because hashing the raw bit pattern would make the hash depend on those
    // undefined bits, we extract the mantissa, exponent, and sign components
    // explicitly and pack them into a buffer to ensure the hash is well-defined.
    #if defined(HAVE_LDOUBLE_INTEL_EXTENDED_12_BYTES_LE) || \
        defined(HAVE_LDOUBLE_INTEL_EXTENDED_16_BYTES_LE) || \
        defined(HAVE_LDOUBLE_MOTOROLA_EXTENDED_12_BYTES_BE)
    constexpr size_t SIZEOF_LDOUBLE_MAN = sizeof(ldouble_man_t);
    constexpr size_t SIZEOF_LDOUBLE_EXP = sizeof(ldouble_exp_t);
    constexpr size_t SIZEOF_LDOUBLE_SIGN = sizeof(ldouble_sign_t);
    constexpr size_t SIZEOF_BUFFER = 2 * (SIZEOF_LDOUBLE_MAN + SIZEOF_LDOUBLE_MAN + SIZEOF_LDOUBLE_EXP + SIZEOF_LDOUBLE_SIGN);
    unsigned char buffer[SIZEOF_BUFFER];

    union IEEEl2bitsrep bits_real{value_real}, bits_imag{value_imag};
    size_t offset = 0;

    for (const IEEEl2bitsrep &bits: {bits_real, bits_imag}) {
        ldouble_man_t manh = GET_LDOUBLE_MANH(bits);