Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG/question: Should np.abs always return a positive number? #26145

Open
mhvk opened this issue Mar 27, 2024 · 12 comments
Open

BUG/question: Should np.abs always return a positive number? #26145

mhvk opened this issue Mar 27, 2024 · 12 comments
Labels
00 - Bug 33 - Question Question about NumPy usage or development component: numpy._core

Comments

@mhvk
Copy link
Contributor

mhvk commented Mar 27, 2024

Describe the issue:

Currently, np.abs for the most negative integer of a given types cannot return the absolute value in the same type, so instead wraps and gives a negative value. This is surprising, and could be avoided by promoting to the unsigned type (e.g., i1->u1). However, this may have its own issues if further calculations are done, so really not sure what is the best option. (Hence, not sure whether this is a bug or a question...)

Note that passing in dtype='u1' works, though one then has to also give casting='unsafe' - even though in this case it is of course not unsafe at all...

Reproduce the code example:

import numpy as np
np.abs(np.array([-128, -127], 'i1'))
# array([-128,  127], dtype=int8)
np.abs(np.array([-128, -127], 'i1'), dtype='u1', casting='unsafe')
# array([128, 129], dtype=uint8)

EDIT: I didn't even notice that using u1 actually produces nonsense for -127. From gh-5657, the "correct" way to avoid the overflow (or, rather, correct for the overflow) is,

np.abs(np.array([-128, -127], 'i1')).view('u1')
# array([128, 127], dtype=uint8)

Error message:

No response

Python and NumPy Versions:

2.1.0.dev0+git20240326.9992c3a
3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0]

Runtime Environment:

N/A

Context for the issue:

Found indirectly in #26143

@seberg
Copy link
Member

seberg commented Mar 27, 2024

It would subtly change promotion and in strange ways if you consider things like:

x = np.int64(3)
res = x + abs(x)

and voila, you got a float64 :). I think we have an old issue about whether we should just raise an error here.

Note that passing in dtype='u1' works, though one then has to also give casting='unsafe' - even though in this case it is of course not unsafe at all...

We would have to encode that part, e.g. by adding explicit i1->u1 loops (maybe there is a shorter way). So that is plausible, but seems not very high priority to me.

@eendebakpt
Copy link
Contributor

Also see #21289

@mhvk
Copy link
Contributor Author

mhvk commented Mar 27, 2024

Sorry for the duplication! Looking at #21648,it seems np.abs(np.int8(-128)) should actually warn or raise - but it does nothing (while -np.uint8(1) does correctly warn/raise).

For arrays, I guess this ends up getting back to the more general question of whether one should have optional over/underflow warnings.

@mattip
Copy link
Member

mattip commented Mar 27, 2024

At one point I think I remember a discussion about having overflow-warning integer dtypes that could be used instead of the faster overflow-ignoring ones.

@eendebakpt
Copy link
Contributor

Even without putting the data in something like np.uint8 one can get surprising results:

In[203]: np.abs(-2147483648)
Out[203]: -2147483648

@mhvk
Copy link
Contributor Author

mhvk commented Mar 27, 2024

My ideal would be a solution that uses (or is similar to) seterr and has something for integer overflow that perhaps by default is 'ignore', but can be set to 'warn' or 'raise'. This would be quite easy to pass inside the loops (which then still would have separate loops so that the 'ignore' case can be maximally fast).

@ngoldbaum
Copy link
Member

ngoldbaum commented Mar 28, 2024

Also see #25396, I got pinged the other day about it so Lysandros might be looking into this now as well.

@mhvk
Copy link
Contributor Author

mhvk commented Mar 28, 2024

Looking at #21648,it seems np.abs(np.int8(-128)) should actually warn or raise - but it does nothing (while -np.uint8(1) does correctly warn/raise).

Looked a bit more into this, but #21648 just adjusted scalarmath.c.src, which means abs(np.int8(-128)) gives a warning but np.abs(...) does not. This perhaps is logical: a ufunc warning should not really depend on whether the input is a scalar or array.

@matthew-brett
Copy link
Contributor

I really miss having an abs-like function that always returns non-negative numbers, regardless of type - and I have previously suggested we should add a uabs function that specifically returns unsigned integers from an integer call. Having a different function would remove the back-compatibility concerns.

@testhowtest
Copy link

maybe related to #14984 , #5781 , Is that right?

@mhvk
Copy link
Contributor Author

mhvk commented May 29, 2024

Following that trace, also gh-5657, which has more discussion

@mhvk
Copy link
Contributor Author

mhvk commented May 29, 2024

From gh-5657, there also is the suggestion to have an errstate that can be set to check for integer overflow. That might not be a bad idea.

Separately, perhaps for abs itself, we can add explicit loops for signed -> unsigned int, so that np.abs(i, signature=('i1', 'u1')) would work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
00 - Bug 33 - Question Question about NumPy usage or development component: numpy._core
Projects
None yet
Development

No branches or pull requests

7 participants