You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
BUG: numpy int64/float64 comparisons are inexact #32071
Python guarantees that a == b implies hash(a) == hash(b). Around 2**53 this breaks for numpy scalars: == promotes the int64 to float64 (losing precision) so the values compare equal, but each hashes as its own exact value, so the hashes differ.
In the bigger picture, it is also not clear whether np.int64(9007199254740993) == np.float64(9007199254740992.0) should be True in the first place, but I would consider this a separate issue.
Reproduce the code example:
# /// script# requires-python = ">=3.10"# dependencies = ["numpy"]# ///importnumpyasnpa=np.int64(2**53+1) # 9007199254740993 (exact as int64)b=np.float64(2**53+1) # 9007199254740992.0 (2**53 + 1 is not representable in float64)print(f"numpy {np.__version__}")
print(f"a = {a!r}")
print(f"b = {b!r}")
print(f"a == b -> {bool(a==b)}")
print(f"hash(a) == hash(b) -> {hash(a) ==hash(b)}")
Error message:
a = np.int64(9007199254740993)
b = np.float64(9007199254740992.0)
a == b -> True
hash(a) == hash(b) -> False
Python and NumPy Versions:
numpy: 2.5.1
python: 3.12.11
Runtime Environment:
No response
How does this issue affect you or how did you find it:
Relevant prior art in #2046 and particularly #12525, which collates this whole family of issues. The float64 case was purposely excluded when this generic issue was fixed for NumPy 1.25.
The basic problem is this:
>>> a, b = np.int64(2**53+1), np.float64(2**53)
>>> (a, b)
(np.int64(9007199254740993), np.float64(9007199254740992.0))
>>> int(a) ==float(b)
False
>>> # Ok, fine...
>>> a == b
np.True_
>>> # wat
>>> # hashing uses Python's conception of equality
>>> len({a, b})
2
IMO what we should really do is make integer and float comparisons exact and not touch the hashing behavior. That would make a == b on the above example return np.False_, following Python.
changed the title [-]BUG: numpy int64/float64 scalars can be `==` yet hash differently[/-][+]BUG: numpy int64/float64 `==` can return `np.True_` for unequal values[/+]on Jul 23, 2026
changed the title [-]BUG: numpy int64/float64 `==` can return `np.True_` for unequal values[/-][+]BUG: numpy int64/float64 comparisons are inexact[/+]on Jul 23, 2026
For anyone wanting to work on this: see comments in #32157. This is likely a medium-to-big project and needs research on whether this needs a deprecation.
Thanks for pointing me toward the history. I read through #12525, #8851, #11801, and PR #23713.
I could not find a comment from the NumPy 1.25 change that directly explains why float64 was left out. The implementation added exact loops for mixed signed and unsigned integers, while floating-point comparisons continued using normal promotion. My reading of the surrounding discussion is that integer comparisons had a clearly defined exact answer. For integer and float comparisons, it is less clear whether NumPy should compare the original values or first apply its documented common-dtype promotion.
I agree that this PR changes more behavior than the original hash issue suggests. The NumPy test suite already found a few comparisons that depended on the existing behavior, so downstream code may depend on it too.
I see two possible approaches to warnings:
We could emit a FutureWarning based on the operand dtypes during type resolution. That should be inexpensive, but it would warn for many comparisons whose result would stay the same.
We could warn only when the future result differs. For arrays, the loop would need to detect both outcomes and carry a mismatch flag back to the ufunc caller so it can emit one warning. I have not found an existing ufunc mechanism that supports this cleanly. The NEP 50 warning path also skips comparison-only precision changes because warnings for them would be noisy.
Changing the hash behavior does not seem viable. np.int64 still needs to hash like an equal Python int, so some part of the current equality behavior has to change.
Would you prefer that I look into limiting this PR to NumPy scalar comparisons, which addresses the equality and hash issue directly? The other option is to keep the wider scalar and array implementation as a prototype while we gather compatibility data and plan a transition. @ngoldbaum
I dunno, I guess we can't change the has really either probably? I think if we do this we need to try to keep complexity low.
In general, it's one of those things where Python goes well beyond normal Programming on grounds of not being compiled, so I feel we should probably discuss this a bit.
Activity
ngoldbaum commentedon Jul 23, 2026
Relevant prior art in #2046 and particularly #12525, which collates this whole family of issues. The float64 case was purposely excluded when this generic issue was fixed for NumPy 1.25.
The basic problem is this:
IMO what we should really do is make integer and float comparisons exact and not touch the hashing behavior. That would make
a == bon the above example returnnp.False_, following Python.[-]BUG: numpy int64/float64 scalars can be `==` yet hash differently[/-][+]BUG: numpy int64/float64 `==` can return `np.True_` for unequal values[/+][-]BUG: numpy int64/float64 `==` can return `np.True_` for unequal values[/-][+]BUG: numpy int64/float64 comparisons are inexact[/+]ngoldbaum commentedon Jul 23, 2026
I changed the title to reflect that the problem is that comparison are inexact.
ngoldbaum commentedon Jul 31, 2026
For anyone wanting to work on this: see comments in #32157. This is likely a medium-to-big project and needs research on whether this needs a deprecation.
abdulm5 commentedon Jul 31, 2026
Thanks for pointing me toward the history. I read through #12525, #8851, #11801, and PR #23713.
I could not find a comment from the NumPy 1.25 change that directly explains why float64 was left out. The implementation added exact loops for mixed signed and unsigned integers, while floating-point comparisons continued using normal promotion. My reading of the surrounding discussion is that integer comparisons had a clearly defined exact answer. For integer and float comparisons, it is less clear whether NumPy should compare the original values or first apply its documented common-dtype promotion.
I agree that this PR changes more behavior than the original hash issue suggests. The NumPy test suite already found a few comparisons that depended on the existing behavior, so downstream code may depend on it too.
I see two possible approaches to warnings:
FutureWarningbased on the operand dtypes during type resolution. That should be inexpensive, but it would warn for many comparisons whose result would stay the same.Changing the hash behavior does not seem viable.
np.int64still needs to hash like an equal Pythonint, so some part of the current equality behavior has to change.Would you prefer that I look into limiting this PR to NumPy scalar comparisons, which addresses the equality and hash issue directly? The other option is to keep the wider scalar and array implementation as a prototype while we gather compatibility data and plan a transition. @ngoldbaum
ngoldbaum commentedon Jul 31, 2026
I think this one will need an opinion from someone else besides me.
abdulm5 commentedon Jul 31, 2026
cc @seberg @mhvk
seberg commentedon Aug 3, 2026
I dunno, I guess we can't change the has really either probably? I think if we do this we need to try to keep complexity low.
In general, it's one of those things where Python goes well beyond normal Programming on grounds of not being compiled, so I feel we should probably discuss this a bit.