Open guangyey opened 4 months ago
This is intentional. See gh-25614.
It has already been the case that expressions containing float vs rational compare unequal:
In [1]: sin(2*x) == sin(2.0*x)
Out[1]: False
However until SymPy 1.13 Float and Rational would compare equal but that is now changed:
In [2]: Float(1) == Integer(1)
Out[2]: False
This is because in SymPy ==
is for structural equality and is supposed to verify that two expressions are the same (not that they have the same value).
Thanks for your elaboration.
Let's keep this open. I expect that others will run into this problem as well. We can use this issue for people to report problems caused by it and solutions.
UPDATE: Comment edited from original for clarity.
I think there are still inconsistencies here. With 1.13.1:
In [1]: from sympy import Integer, Float, Number, Rational
In [2]: from itertools import product
In [3]: types = (int, float, Integer, Float, Number, Rational)
In [4]: for v in 0, 0.0:
...: for l_type, r_type in product((int, float, Integer, Float, Number, Rational), repeat=2):
...: if l_type is r_type or l_type in (int, float) and r_type in (int, float):
...: continue
...: print(f"{l_type.__name__}({v}) == {r_type.__name__}({v}) -> {l_type(v) == r_type(v)}")
...:
int(0) == Integer(0) -> True
int(0) == Float(0) -> False
int(0) == Number(0) -> True
int(0) == Rational(0) -> True
float(0) == Integer(0) -> True
float(0) == Float(0) -> True
float(0) == Number(0) -> True
float(0) == Rational(0) -> True
Integer(0) == int(0) -> True
Integer(0) == float(0) -> True
Integer(0) == Float(0) -> True
Integer(0) == Number(0) -> True
Integer(0) == Rational(0) -> True
Float(0) == int(0) -> False
Float(0) == float(0) -> True
Float(0) == Integer(0) -> False
Float(0) == Number(0) -> False
Float(0) == Rational(0) -> False
Number(0) == int(0) -> True
Number(0) == float(0) -> True
Number(0) == Integer(0) -> True
Number(0) == Float(0) -> True
Number(0) == Rational(0) -> True
Rational(0) == int(0) -> True
Rational(0) == float(0) -> True
Rational(0) == Integer(0) -> True
Rational(0) == Float(0) -> True
Rational(0) == Number(0) -> True
int(0.0) == Integer(0.0) -> True
int(0.0) == Float(0.0) -> False
int(0.0) == Number(0.0) -> False
int(0.0) == Rational(0.0) -> True
float(0.0) == Integer(0.0) -> True
float(0.0) == Float(0.0) -> True
float(0.0) == Number(0.0) -> True
float(0.0) == Rational(0.0) -> True
Integer(0.0) == int(0.0) -> True
Integer(0.0) == float(0.0) -> True
Integer(0.0) == Float(0.0) -> True
Integer(0.0) == Number(0.0) -> True
Integer(0.0) == Rational(0.0) -> True
Float(0.0) == int(0.0) -> False
Float(0.0) == float(0.0) -> True
Float(0.0) == Integer(0.0) -> False
Float(0.0) == Number(0.0) -> True
Float(0.0) == Rational(0.0) -> False
Number(0.0) == int(0.0) -> False
Number(0.0) == float(0.0) -> True
Number(0.0) == Integer(0.0) -> False
Number(0.0) == Float(0.0) -> True
Number(0.0) == Rational(0.0) -> False
Rational(0.0) == int(0.0) -> True
Rational(0.0) == float(0.0) -> True
Rational(0.0) == Integer(0.0) -> True
Rational(0.0) == Float(0.0) -> True
Rational(0.0) == Number(0.0) -> True
If I'm understanding the intention correctly, those should all evaluate to False
, no? Even so, the actual results are perplexing, e.g.:
Float(0) == int(0) -> False
Float(0) == float(0) -> True
...
Number(0) == int(0) -> True
Number(0) == float(0) -> True
Many comparisons aren't even symmetrical, e.g.:
Float(0.0) == Rational(0.0) -> False
...
Rational(0.0) == Float(0.0) -> True
If the intention is that equivalent values with disparate types are not considered "equal", I'm quite surprised there aren't already tests to make sure this intention is consistently applied?
I think you'll have problems trying to recast the meaning of __eq__
to take types into account without also addressing the remainder of the numerical comparisons. Many results don't seem consistent:
In [5]: from operator import __ge__, __gt__, __le__, __lt__
In [6]: for op in __ge__, __gt__, __le__, __lt__:
...: for v in 0, 0.0:
...: for l_type, r_type in product((int, float, Integer, Float, Number, Rational), repeat=2):
...: if l_type is r_type or l_type in (int, float) and r_type in (int, float):
...: continue
...: print(f"{l_type.__name__}({v}) {op.__name__} {r_type.__name__}({v}) -> {op(l_type(v), r_type(v))}")
...: print(f"{l_type.__name__}({v}) {op.__name__} {r_type.__name__}({v + 1}) -> {op(l_type(v), r_type(v + 1))}")
...: print(f"{l_type.__name__}({v + 1}) {op.__name__} {r_type.__name__}({v}) -> {op(l_type(v + 1), r_type(v))}")
...:
int(0) ge Integer(0) -> True
int(0) ge Integer(1) -> False
int(1) ge Integer(0) -> True
int(0) ge Float(0) -> True
int(0) ge Float(1) -> False
int(1) ge Float(0) -> True
int(0) ge Number(0) -> True
int(0) ge Number(1) -> False
int(1) ge Number(0) -> True
int(0) ge Rational(0) -> True
int(0) ge Rational(1) -> False
int(1) ge Rational(0) -> True
float(0) ge Integer(0) -> True
float(0) ge Integer(1) -> False
float(1) ge Integer(0) -> True
float(0) ge Float(0) -> True
float(0) ge Float(1) -> False
float(1) ge Float(0) -> True
float(0) ge Number(0) -> True
float(0) ge Number(1) -> False
float(1) ge Number(0) -> True
float(0) ge Rational(0) -> True
float(0) ge Rational(1) -> False
float(1) ge Rational(0) -> True
Integer(0) ge int(0) -> True
Integer(0) ge int(1) -> False
Integer(1) ge int(0) -> True
Integer(0) ge float(0) -> True
Integer(0) ge float(1) -> False
Integer(1) ge float(0) -> True
Integer(0) ge Float(0) -> True
Integer(0) ge Float(1) -> False
Integer(1) ge Float(0) -> True
Integer(0) ge Number(0) -> True
Integer(0) ge Number(1) -> False
Integer(1) ge Number(0) -> True
Integer(0) ge Rational(0) -> True
Integer(0) ge Rational(1) -> False
Integer(1) ge Rational(0) -> True
Float(0) ge int(0) -> True
Float(0) ge int(1) -> False
Float(1) ge int(0) -> True
Float(0) ge float(0) -> True
Float(0) ge float(1) -> False
Float(1) ge float(0) -> True
Float(0) ge Integer(0) -> True
Float(0) ge Integer(1) -> False
Float(1) ge Integer(0) -> True
Float(0) ge Number(0) -> True
Float(0) ge Number(1) -> False
Float(1) ge Number(0) -> True
Float(0) ge Rational(0) -> True
Float(0) ge Rational(1) -> False
Float(1) ge Rational(0) -> True
Number(0) ge int(0) -> True
Number(0) ge int(1) -> False
Number(1) ge int(0) -> True
Number(0) ge float(0) -> True
Number(0) ge float(1) -> False
Number(1) ge float(0) -> True
Number(0) ge Integer(0) -> True
Number(0) ge Integer(1) -> False
Number(1) ge Integer(0) -> True
Number(0) ge Float(0) -> True
Number(0) ge Float(1) -> False
Number(1) ge Float(0) -> True
Number(0) ge Rational(0) -> True
Number(0) ge Rational(1) -> False
Number(1) ge Rational(0) -> True
Rational(0) ge int(0) -> True
Rational(0) ge int(1) -> False
Rational(1) ge int(0) -> True
Rational(0) ge float(0) -> True
Rational(0) ge float(1) -> False
Rational(1) ge float(0) -> True
Rational(0) ge Integer(0) -> True
Rational(0) ge Integer(1) -> False
Rational(1) ge Integer(0) -> True
Rational(0) ge Float(0) -> True
Rational(0) ge Float(1) -> False
Rational(1) ge Float(0) -> True
Rational(0) ge Number(0) -> True
Rational(0) ge Number(1) -> False
Rational(1) ge Number(0) -> True
int(0.0) ge Integer(0.0) -> True
int(0.0) ge Integer(1.0) -> False
int(1.0) ge Integer(0.0) -> True
int(0.0) ge Float(0.0) -> True
int(0.0) ge Float(1.0) -> False
int(1.0) ge Float(0.0) -> True
int(0.0) ge Number(0.0) -> True
int(0.0) ge Number(1.0) -> False
int(1.0) ge Number(0.0) -> True
int(0.0) ge Rational(0.0) -> True
int(0.0) ge Rational(1.0) -> False
int(1.0) ge Rational(0.0) -> True
float(0.0) ge Integer(0.0) -> True
float(0.0) ge Integer(1.0) -> False
float(1.0) ge Integer(0.0) -> True
float(0.0) ge Float(0.0) -> True
float(0.0) ge Float(1.0) -> False
float(1.0) ge Float(0.0) -> True
float(0.0) ge Number(0.0) -> True
float(0.0) ge Number(1.0) -> False
float(1.0) ge Number(0.0) -> True
float(0.0) ge Rational(0.0) -> True
float(0.0) ge Rational(1.0) -> False
float(1.0) ge Rational(0.0) -> True
Integer(0.0) ge int(0.0) -> True
Integer(0.0) ge int(1.0) -> False
Integer(1.0) ge int(0.0) -> True
Integer(0.0) ge float(0.0) -> True
Integer(0.0) ge float(1.0) -> False
Integer(1.0) ge float(0.0) -> True
Integer(0.0) ge Float(0.0) -> True
Integer(0.0) ge Float(1.0) -> False
Integer(1.0) ge Float(0.0) -> True
Integer(0.0) ge Number(0.0) -> True
Integer(0.0) ge Number(1.0) -> False
Integer(1.0) ge Number(0.0) -> True
Integer(0.0) ge Rational(0.0) -> True
Integer(0.0) ge Rational(1.0) -> False
Integer(1.0) ge Rational(0.0) -> True
Float(0.0) ge int(0.0) -> True
Float(0.0) ge int(1.0) -> False
Float(1.0) ge int(0.0) -> True
Float(0.0) ge float(0.0) -> True
Float(0.0) ge float(1.0) -> False
Float(1.0) ge float(0.0) -> True
Float(0.0) ge Integer(0.0) -> True
Float(0.0) ge Integer(1.0) -> False
Float(1.0) ge Integer(0.0) -> True
Float(0.0) ge Number(0.0) -> True
Float(0.0) ge Number(1.0) -> False
Float(1.0) ge Number(0.0) -> True
Float(0.0) ge Rational(0.0) -> True
Float(0.0) ge Rational(1.0) -> False
Float(1.0) ge Rational(0.0) -> True
Number(0.0) ge int(0.0) -> True
Number(0.0) ge int(1.0) -> False
Number(1.0) ge int(0.0) -> True
Number(0.0) ge float(0.0) -> True
Number(0.0) ge float(1.0) -> False
Number(1.0) ge float(0.0) -> True
Number(0.0) ge Integer(0.0) -> True
Number(0.0) ge Integer(1.0) -> False
Number(1.0) ge Integer(0.0) -> True
Number(0.0) ge Float(0.0) -> True
Number(0.0) ge Float(1.0) -> False
Number(1.0) ge Float(0.0) -> True
Number(0.0) ge Rational(0.0) -> True
Number(0.0) ge Rational(1.0) -> False
Number(1.0) ge Rational(0.0) -> True
Rational(0.0) ge int(0.0) -> True
Rational(0.0) ge int(1.0) -> False
Rational(1.0) ge int(0.0) -> True
Rational(0.0) ge float(0.0) -> True
Rational(0.0) ge float(1.0) -> False
Rational(1.0) ge float(0.0) -> True
Rational(0.0) ge Integer(0.0) -> True
Rational(0.0) ge Integer(1.0) -> False
Rational(1.0) ge Integer(0.0) -> True
Rational(0.0) ge Float(0.0) -> True
Rational(0.0) ge Float(1.0) -> False
Rational(1.0) ge Float(0.0) -> True
Rational(0.0) ge Number(0.0) -> True
Rational(0.0) ge Number(1.0) -> False
Rational(1.0) ge Number(0.0) -> True
int(0) gt Integer(0) -> False
int(0) gt Integer(1) -> False
int(1) gt Integer(0) -> True
int(0) gt Float(0) -> False
int(0) gt Float(1) -> False
int(1) gt Float(0) -> True
int(0) gt Number(0) -> False
int(0) gt Number(1) -> False
int(1) gt Number(0) -> True
int(0) gt Rational(0) -> False
int(0) gt Rational(1) -> False
int(1) gt Rational(0) -> True
float(0) gt Integer(0) -> False
float(0) gt Integer(1) -> False
float(1) gt Integer(0) -> True
float(0) gt Float(0) -> False
float(0) gt Float(1) -> False
float(1) gt Float(0) -> True
float(0) gt Number(0) -> False
float(0) gt Number(1) -> False
float(1) gt Number(0) -> True
float(0) gt Rational(0) -> False
float(0) gt Rational(1) -> False
float(1) gt Rational(0) -> True
Integer(0) gt int(0) -> False
Integer(0) gt int(1) -> False
Integer(1) gt int(0) -> True
Integer(0) gt float(0) -> False
Integer(0) gt float(1) -> False
Integer(1) gt float(0) -> True
Integer(0) gt Float(0) -> False
Integer(0) gt Float(1) -> False
Integer(1) gt Float(0) -> True
Integer(0) gt Number(0) -> False
Integer(0) gt Number(1) -> False
Integer(1) gt Number(0) -> True
Integer(0) gt Rational(0) -> False
Integer(0) gt Rational(1) -> False
Integer(1) gt Rational(0) -> True
Float(0) gt int(0) -> False
Float(0) gt int(1) -> False
Float(1) gt int(0) -> True
Float(0) gt float(0) -> False
Float(0) gt float(1) -> False
Float(1) gt float(0) -> True
Float(0) gt Integer(0) -> False
Float(0) gt Integer(1) -> False
Float(1) gt Integer(0) -> True
Float(0) gt Number(0) -> False
Float(0) gt Number(1) -> False
Float(1) gt Number(0) -> True
Float(0) gt Rational(0) -> False
Float(0) gt Rational(1) -> False
Float(1) gt Rational(0) -> True
Number(0) gt int(0) -> False
Number(0) gt int(1) -> False
Number(1) gt int(0) -> True
Number(0) gt float(0) -> False
Number(0) gt float(1) -> False
Number(1) gt float(0) -> True
Number(0) gt Integer(0) -> False
Number(0) gt Integer(1) -> False
Number(1) gt Integer(0) -> True
Number(0) gt Float(0) -> False
Number(0) gt Float(1) -> False
Number(1) gt Float(0) -> True
Number(0) gt Rational(0) -> False
Number(0) gt Rational(1) -> False
Number(1) gt Rational(0) -> True
Rational(0) gt int(0) -> False
Rational(0) gt int(1) -> False
Rational(1) gt int(0) -> True
Rational(0) gt float(0) -> False
Rational(0) gt float(1) -> False
Rational(1) gt float(0) -> True
Rational(0) gt Integer(0) -> False
Rational(0) gt Integer(1) -> False
Rational(1) gt Integer(0) -> True
Rational(0) gt Float(0) -> False
Rational(0) gt Float(1) -> False
Rational(1) gt Float(0) -> True
Rational(0) gt Number(0) -> False
Rational(0) gt Number(1) -> False
Rational(1) gt Number(0) -> True
int(0.0) gt Integer(0.0) -> False
int(0.0) gt Integer(1.0) -> False
int(1.0) gt Integer(0.0) -> True
int(0.0) gt Float(0.0) -> False
int(0.0) gt Float(1.0) -> False
int(1.0) gt Float(0.0) -> True
int(0.0) gt Number(0.0) -> False
int(0.0) gt Number(1.0) -> False
int(1.0) gt Number(0.0) -> True
int(0.0) gt Rational(0.0) -> False
int(0.0) gt Rational(1.0) -> False
int(1.0) gt Rational(0.0) -> True
float(0.0) gt Integer(0.0) -> False
float(0.0) gt Integer(1.0) -> False
float(1.0) gt Integer(0.0) -> True
float(0.0) gt Float(0.0) -> False
float(0.0) gt Float(1.0) -> False
float(1.0) gt Float(0.0) -> True
float(0.0) gt Number(0.0) -> False
float(0.0) gt Number(1.0) -> False
float(1.0) gt Number(0.0) -> True
float(0.0) gt Rational(0.0) -> False
float(0.0) gt Rational(1.0) -> False
float(1.0) gt Rational(0.0) -> True
Integer(0.0) gt int(0.0) -> False
Integer(0.0) gt int(1.0) -> False
Integer(1.0) gt int(0.0) -> True
Integer(0.0) gt float(0.0) -> False
Integer(0.0) gt float(1.0) -> False
Integer(1.0) gt float(0.0) -> True
Integer(0.0) gt Float(0.0) -> False
Integer(0.0) gt Float(1.0) -> False
Integer(1.0) gt Float(0.0) -> True
Integer(0.0) gt Number(0.0) -> False
Integer(0.0) gt Number(1.0) -> False
Integer(1.0) gt Number(0.0) -> True
Integer(0.0) gt Rational(0.0) -> False
Integer(0.0) gt Rational(1.0) -> False
Integer(1.0) gt Rational(0.0) -> True
Float(0.0) gt int(0.0) -> False
Float(0.0) gt int(1.0) -> False
Float(1.0) gt int(0.0) -> True
Float(0.0) gt float(0.0) -> False
Float(0.0) gt float(1.0) -> False
Float(1.0) gt float(0.0) -> True
Float(0.0) gt Integer(0.0) -> False
Float(0.0) gt Integer(1.0) -> False
Float(1.0) gt Integer(0.0) -> True
Float(0.0) gt Number(0.0) -> False
Float(0.0) gt Number(1.0) -> False
Float(1.0) gt Number(0.0) -> True
Float(0.0) gt Rational(0.0) -> False
Float(0.0) gt Rational(1.0) -> False
Float(1.0) gt Rational(0.0) -> True
Number(0.0) gt int(0.0) -> False
Number(0.0) gt int(1.0) -> False
Number(1.0) gt int(0.0) -> True
Number(0.0) gt float(0.0) -> False
Number(0.0) gt float(1.0) -> False
Number(1.0) gt float(0.0) -> True
Number(0.0) gt Integer(0.0) -> False
Number(0.0) gt Integer(1.0) -> False
Number(1.0) gt Integer(0.0) -> True
Number(0.0) gt Float(0.0) -> False
Number(0.0) gt Float(1.0) -> False
Number(1.0) gt Float(0.0) -> True
Number(0.0) gt Rational(0.0) -> False
Number(0.0) gt Rational(1.0) -> False
Number(1.0) gt Rational(0.0) -> True
Rational(0.0) gt int(0.0) -> False
Rational(0.0) gt int(1.0) -> False
Rational(1.0) gt int(0.0) -> True
Rational(0.0) gt float(0.0) -> False
Rational(0.0) gt float(1.0) -> False
Rational(1.0) gt float(0.0) -> True
Rational(0.0) gt Integer(0.0) -> False
Rational(0.0) gt Integer(1.0) -> False
Rational(1.0) gt Integer(0.0) -> True
Rational(0.0) gt Float(0.0) -> False
Rational(0.0) gt Float(1.0) -> False
Rational(1.0) gt Float(0.0) -> True
Rational(0.0) gt Number(0.0) -> False
Rational(0.0) gt Number(1.0) -> False
Rational(1.0) gt Number(0.0) -> True
int(0) le Integer(0) -> True
int(0) le Integer(1) -> True
int(1) le Integer(0) -> False
int(0) le Float(0) -> True
int(0) le Float(1) -> True
int(1) le Float(0) -> False
int(0) le Number(0) -> True
int(0) le Number(1) -> True
int(1) le Number(0) -> False
int(0) le Rational(0) -> True
int(0) le Rational(1) -> True
int(1) le Rational(0) -> False
float(0) le Integer(0) -> True
float(0) le Integer(1) -> True
float(1) le Integer(0) -> False
float(0) le Float(0) -> True
float(0) le Float(1) -> True
float(1) le Float(0) -> False
float(0) le Number(0) -> True
float(0) le Number(1) -> True
float(1) le Number(0) -> False
float(0) le Rational(0) -> True
float(0) le Rational(1) -> True
float(1) le Rational(0) -> False
Integer(0) le int(0) -> True
Integer(0) le int(1) -> True
Integer(1) le int(0) -> False
Integer(0) le float(0) -> True
Integer(0) le float(1) -> True
Integer(1) le float(0) -> False
Integer(0) le Float(0) -> True
Integer(0) le Float(1) -> True
Integer(1) le Float(0) -> False
Integer(0) le Number(0) -> True
Integer(0) le Number(1) -> True
Integer(1) le Number(0) -> False
Integer(0) le Rational(0) -> True
Integer(0) le Rational(1) -> True
Integer(1) le Rational(0) -> False
Float(0) le int(0) -> True
Float(0) le int(1) -> True
Float(1) le int(0) -> False
Float(0) le float(0) -> True
Float(0) le float(1) -> True
Float(1) le float(0) -> False
Float(0) le Integer(0) -> True
Float(0) le Integer(1) -> True
Float(1) le Integer(0) -> False
Float(0) le Number(0) -> True
Float(0) le Number(1) -> True
Float(1) le Number(0) -> False
Float(0) le Rational(0) -> True
Float(0) le Rational(1) -> True
Float(1) le Rational(0) -> False
Number(0) le int(0) -> True
Number(0) le int(1) -> True
Number(1) le int(0) -> False
Number(0) le float(0) -> True
Number(0) le float(1) -> True
Number(1) le float(0) -> False
Number(0) le Integer(0) -> True
Number(0) le Integer(1) -> True
Number(1) le Integer(0) -> False
Number(0) le Float(0) -> True
Number(0) le Float(1) -> True
Number(1) le Float(0) -> False
Number(0) le Rational(0) -> True
Number(0) le Rational(1) -> True
Number(1) le Rational(0) -> False
Rational(0) le int(0) -> True
Rational(0) le int(1) -> True
Rational(1) le int(0) -> False
Rational(0) le float(0) -> True
Rational(0) le float(1) -> True
Rational(1) le float(0) -> False
Rational(0) le Integer(0) -> True
Rational(0) le Integer(1) -> True
Rational(1) le Integer(0) -> False
Rational(0) le Float(0) -> True
Rational(0) le Float(1) -> True
Rational(1) le Float(0) -> False
Rational(0) le Number(0) -> True
Rational(0) le Number(1) -> True
Rational(1) le Number(0) -> False
int(0.0) le Integer(0.0) -> True
int(0.0) le Integer(1.0) -> True
int(1.0) le Integer(0.0) -> False
int(0.0) le Float(0.0) -> True
int(0.0) le Float(1.0) -> True
int(1.0) le Float(0.0) -> False
int(0.0) le Number(0.0) -> True
int(0.0) le Number(1.0) -> True
int(1.0) le Number(0.0) -> False
int(0.0) le Rational(0.0) -> True
int(0.0) le Rational(1.0) -> True
int(1.0) le Rational(0.0) -> False
float(0.0) le Integer(0.0) -> True
float(0.0) le Integer(1.0) -> True
float(1.0) le Integer(0.0) -> False
float(0.0) le Float(0.0) -> True
float(0.0) le Float(1.0) -> True
float(1.0) le Float(0.0) -> False
float(0.0) le Number(0.0) -> True
float(0.0) le Number(1.0) -> True
float(1.0) le Number(0.0) -> False
float(0.0) le Rational(0.0) -> True
float(0.0) le Rational(1.0) -> True
float(1.0) le Rational(0.0) -> False
Integer(0.0) le int(0.0) -> True
Integer(0.0) le int(1.0) -> True
Integer(1.0) le int(0.0) -> False
Integer(0.0) le float(0.0) -> True
Integer(0.0) le float(1.0) -> True
Integer(1.0) le float(0.0) -> False
Integer(0.0) le Float(0.0) -> True
Integer(0.0) le Float(1.0) -> True
Integer(1.0) le Float(0.0) -> False
Integer(0.0) le Number(0.0) -> True
Integer(0.0) le Number(1.0) -> True
Integer(1.0) le Number(0.0) -> False
Integer(0.0) le Rational(0.0) -> True
Integer(0.0) le Rational(1.0) -> True
Integer(1.0) le Rational(0.0) -> False
Float(0.0) le int(0.0) -> True
Float(0.0) le int(1.0) -> True
Float(1.0) le int(0.0) -> False
Float(0.0) le float(0.0) -> True
Float(0.0) le float(1.0) -> True
Float(1.0) le float(0.0) -> False
Float(0.0) le Integer(0.0) -> True
Float(0.0) le Integer(1.0) -> True
Float(1.0) le Integer(0.0) -> False
Float(0.0) le Number(0.0) -> True
Float(0.0) le Number(1.0) -> True
Float(1.0) le Number(0.0) -> False
Float(0.0) le Rational(0.0) -> True
Float(0.0) le Rational(1.0) -> True
Float(1.0) le Rational(0.0) -> False
Number(0.0) le int(0.0) -> True
Number(0.0) le int(1.0) -> True
Number(1.0) le int(0.0) -> False
Number(0.0) le float(0.0) -> True
Number(0.0) le float(1.0) -> True
Number(1.0) le float(0.0) -> False
Number(0.0) le Integer(0.0) -> True
Number(0.0) le Integer(1.0) -> True
Number(1.0) le Integer(0.0) -> False
Number(0.0) le Float(0.0) -> True
Number(0.0) le Float(1.0) -> True
Number(1.0) le Float(0.0) -> False
Number(0.0) le Rational(0.0) -> True
Number(0.0) le Rational(1.0) -> True
Number(1.0) le Rational(0.0) -> False
Rational(0.0) le int(0.0) -> True
Rational(0.0) le int(1.0) -> True
Rational(1.0) le int(0.0) -> False
Rational(0.0) le float(0.0) -> True
Rational(0.0) le float(1.0) -> True
Rational(1.0) le float(0.0) -> False
Rational(0.0) le Integer(0.0) -> True
Rational(0.0) le Integer(1.0) -> True
Rational(1.0) le Integer(0.0) -> False
Rational(0.0) le Float(0.0) -> True
Rational(0.0) le Float(1.0) -> True
Rational(1.0) le Float(0.0) -> False
Rational(0.0) le Number(0.0) -> True
Rational(0.0) le Number(1.0) -> True
Rational(1.0) le Number(0.0) -> False
int(0) lt Integer(0) -> False
int(0) lt Integer(1) -> True
int(1) lt Integer(0) -> False
int(0) lt Float(0) -> False
int(0) lt Float(1) -> True
int(1) lt Float(0) -> False
int(0) lt Number(0) -> False
int(0) lt Number(1) -> True
int(1) lt Number(0) -> False
int(0) lt Rational(0) -> False
int(0) lt Rational(1) -> True
int(1) lt Rational(0) -> False
float(0) lt Integer(0) -> False
float(0) lt Integer(1) -> True
float(1) lt Integer(0) -> False
float(0) lt Float(0) -> False
float(0) lt Float(1) -> True
float(1) lt Float(0) -> False
float(0) lt Number(0) -> False
float(0) lt Number(1) -> True
float(1) lt Number(0) -> False
float(0) lt Rational(0) -> False
float(0) lt Rational(1) -> True
float(1) lt Rational(0) -> False
Integer(0) lt int(0) -> False
Integer(0) lt int(1) -> True
Integer(1) lt int(0) -> False
Integer(0) lt float(0) -> False
Integer(0) lt float(1) -> True
Integer(1) lt float(0) -> False
Integer(0) lt Float(0) -> False
Integer(0) lt Float(1) -> True
Integer(1) lt Float(0) -> False
Integer(0) lt Number(0) -> False
Integer(0) lt Number(1) -> True
Integer(1) lt Number(0) -> False
Integer(0) lt Rational(0) -> False
Integer(0) lt Rational(1) -> True
Integer(1) lt Rational(0) -> False
Float(0) lt int(0) -> False
Float(0) lt int(1) -> True
Float(1) lt int(0) -> False
Float(0) lt float(0) -> False
Float(0) lt float(1) -> True
Float(1) lt float(0) -> False
Float(0) lt Integer(0) -> False
Float(0) lt Integer(1) -> True
Float(1) lt Integer(0) -> False
Float(0) lt Number(0) -> False
Float(0) lt Number(1) -> True
Float(1) lt Number(0) -> False
Float(0) lt Rational(0) -> False
Float(0) lt Rational(1) -> True
Float(1) lt Rational(0) -> False
Number(0) lt int(0) -> False
Number(0) lt int(1) -> True
Number(1) lt int(0) -> False
Number(0) lt float(0) -> False
Number(0) lt float(1) -> True
Number(1) lt float(0) -> False
Number(0) lt Integer(0) -> False
Number(0) lt Integer(1) -> True
Number(1) lt Integer(0) -> False
Number(0) lt Float(0) -> False
Number(0) lt Float(1) -> True
Number(1) lt Float(0) -> False
Number(0) lt Rational(0) -> False
Number(0) lt Rational(1) -> True
Number(1) lt Rational(0) -> False
Rational(0) lt int(0) -> False
Rational(0) lt int(1) -> True
Rational(1) lt int(0) -> False
Rational(0) lt float(0) -> False
Rational(0) lt float(1) -> True
Rational(1) lt float(0) -> False
Rational(0) lt Integer(0) -> False
Rational(0) lt Integer(1) -> True
Rational(1) lt Integer(0) -> False
Rational(0) lt Float(0) -> False
Rational(0) lt Float(1) -> True
Rational(1) lt Float(0) -> False
Rational(0) lt Number(0) -> False
Rational(0) lt Number(1) -> True
Rational(1) lt Number(0) -> False
int(0.0) lt Integer(0.0) -> False
int(0.0) lt Integer(1.0) -> True
int(1.0) lt Integer(0.0) -> False
int(0.0) lt Float(0.0) -> False
int(0.0) lt Float(1.0) -> True
int(1.0) lt Float(0.0) -> False
int(0.0) lt Number(0.0) -> False
int(0.0) lt Number(1.0) -> True
int(1.0) lt Number(0.0) -> False
int(0.0) lt Rational(0.0) -> False
int(0.0) lt Rational(1.0) -> True
int(1.0) lt Rational(0.0) -> False
float(0.0) lt Integer(0.0) -> False
float(0.0) lt Integer(1.0) -> True
float(1.0) lt Integer(0.0) -> False
float(0.0) lt Float(0.0) -> False
float(0.0) lt Float(1.0) -> True
float(1.0) lt Float(0.0) -> False
float(0.0) lt Number(0.0) -> False
float(0.0) lt Number(1.0) -> True
float(1.0) lt Number(0.0) -> False
float(0.0) lt Rational(0.0) -> False
float(0.0) lt Rational(1.0) -> True
float(1.0) lt Rational(0.0) -> False
Integer(0.0) lt int(0.0) -> False
Integer(0.0) lt int(1.0) -> True
Integer(1.0) lt int(0.0) -> False
Integer(0.0) lt float(0.0) -> False
Integer(0.0) lt float(1.0) -> True
Integer(1.0) lt float(0.0) -> False
Integer(0.0) lt Float(0.0) -> False
Integer(0.0) lt Float(1.0) -> True
Integer(1.0) lt Float(0.0) -> False
Integer(0.0) lt Number(0.0) -> False
Integer(0.0) lt Number(1.0) -> True
Integer(1.0) lt Number(0.0) -> False
Integer(0.0) lt Rational(0.0) -> False
Integer(0.0) lt Rational(1.0) -> True
Integer(1.0) lt Rational(0.0) -> False
Float(0.0) lt int(0.0) -> False
Float(0.0) lt int(1.0) -> True
Float(1.0) lt int(0.0) -> False
Float(0.0) lt float(0.0) -> False
Float(0.0) lt float(1.0) -> True
Float(1.0) lt float(0.0) -> False
Float(0.0) lt Integer(0.0) -> False
Float(0.0) lt Integer(1.0) -> True
Float(1.0) lt Integer(0.0) -> False
Float(0.0) lt Number(0.0) -> False
Float(0.0) lt Number(1.0) -> True
Float(1.0) lt Number(0.0) -> False
Float(0.0) lt Rational(0.0) -> False
Float(0.0) lt Rational(1.0) -> True
Float(1.0) lt Rational(0.0) -> False
Number(0.0) lt int(0.0) -> False
Number(0.0) lt int(1.0) -> True
Number(1.0) lt int(0.0) -> False
Number(0.0) lt float(0.0) -> False
Number(0.0) lt float(1.0) -> True
Number(1.0) lt float(0.0) -> False
Number(0.0) lt Integer(0.0) -> False
Number(0.0) lt Integer(1.0) -> True
Number(1.0) lt Integer(0.0) -> False
Number(0.0) lt Float(0.0) -> False
Number(0.0) lt Float(1.0) -> True
Number(1.0) lt Float(0.0) -> False
Number(0.0) lt Rational(0.0) -> False
Number(0.0) lt Rational(1.0) -> True
Number(1.0) lt Rational(0.0) -> False
Rational(0.0) lt int(0.0) -> False
Rational(0.0) lt int(1.0) -> True
Rational(1.0) lt int(0.0) -> False
Rational(0.0) lt float(0.0) -> False
Rational(0.0) lt float(1.0) -> True
Rational(1.0) lt float(0.0) -> False
Rational(0.0) lt Integer(0.0) -> False
Rational(0.0) lt Integer(1.0) -> True
Rational(1.0) lt Integer(0.0) -> False
Rational(0.0) lt Float(0.0) -> False
Rational(0.0) lt Float(1.0) -> True
Rational(1.0) lt Float(0.0) -> False
Rational(0.0) lt Number(0.0) -> False
Rational(0.0) lt Number(1.0) -> True
Rational(1.0) lt Number(0.0) -> False
Many of the different types that you compare are actually the same types:
In [7]: type(Integer(0))
Out[7]: sympy.core.numbers.Zero
In [8]: type(Rational(0))
Out[8]: sympy.core.numbers.Zero
In [9]: type(Number(0))
Out[9]: sympy.core.numbers.Zero
In [10]: type(Integer(0.0))
Out[10]: sympy.core.numbers.Zero
In [11]: type(Rational(0.0))
Out[11]: sympy.core.numbers.Zero
In [12]: type(Number(0.0))
Out[12]: sympy.core.numbers.Float
In [13]: type(Float(0))
Out[13]: sympy.core.numbers.Float
There are only 4 distinct types in your examples: int(0)
, float(0)
, Integer(0)
, Float(0)
.
Inequalities like >
have a different meaning in SymPy compared to ==
which is for structural equality. The equality version of >
is Eq(a,b)
rather than a==b
.
If the intention is that equivalent values with disparate types are not considered "equal"
The intention is that approximate floating point values are not considered equal to exact integer/rational values.
More broadly the intention is that a == b
is True only for structurally equal expressions (and False otherwise). Many of the different "types" that you show reduce to structurally equivalent expressions or even the identical same object in memory e.g. these are all precisely the same object (the singleton S.Zero
):
In [5]: Number(0) is Integer(0) is Rational(0) is Integer(0.0)
Out[5]: True
This is a bug (both should be False):
In [6]: Float(0.0) == Rational(0.0)
Out[6]: False
In [7]: Rational(0.0) == Float(0.0)
Out[7]: True
This is also a bug (both should be False):
In [8]: 0.0 == Integer(0)
Out[8]: True
In [9]: Integer(0) == 0.0
Out[9]: True
There was some discussion earlier about making an exception for zero in Rational/Float comparisons. As long as they are distinct objects though then I think that they need to compare unequal under ==
:
In [12]: S(0.0)
Out[12]: 0.0
In [13]: S(0)
Out[13]: 0
I think that this is the fix:
diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
index 289c41f362..8f7519d281 100644
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -1598,8 +1598,6 @@ def __eq__(self, other):
# S(0) == S.false is False
# S(0) == False is True
return False
- if not self:
- return not other
if other.is_NumberSymbol:
if other.is_irrational:
return False
The prevents S.Zero
from comparing equal to zero Floats (or floats):
In [1]: 0.0 == Integer(0)
Out[1]: False
In [2]: Integer(0) == 0.0
Out[2]: False
In [3]: Float(0) == Integer(0)
Out[3]: False
In [4]: Integer(0) == Float(0)
Out[4]: False
I think that this is the fix:
That leads to a few test failures:
FAILED sympy/core/tests/test_numbers.py::test_Float - assert 0 == 0.0
FAILED sympy/core/tests/test_evalf.py::test_issue_17681 - OverflowError: cannot convert float infinity to integer
FAILED sympy/functions/elementary/tests/test_integers.py::test_ceiling - OverflowError: cannot convert float infinity to integer
FAILED sympy/functions/elementary/tests/test_trigonometric.py::test_sin_rewrite - assert -0.558931746279103 + 6.46234853557053e-26*I == -0.558931746279103 + 2.58493941422821e-26*I
FAILED sympy/functions/elementary/tests/test_trigonometric.py::test_cos - assert False is None
FAILED sympy/geometry/tests/test_point.py::test_arguments - assert Point2D(0, 10.0000000000000) == Point2D(0.0, 10.0000000000000)
FAILED sympy/functions/elementary/tests/test_trigonometric.py::test_tan_rewrite - assert 0.674050331723157 + 4.14132307267789e-25*I == 0.674050331723157 + 3.67372234649511e-25*I
FAILED sympy/polys/matrices/tests/test_linsolve.py::test__linsolve_float - assert {y: 0, x: 0} == {x: 0.0, y: 0.0}
FAILED sympy/functions/elementary/tests/test_trigonometric.py::test_issue_17461 - AttributeError: 'mpc' object has no attribute '_mpf_'. Did you mean: '_mpc_'?
FAILED sympy/physics/quantum/tests/test_qubit.py::test_measure_normalize - assert [(|000>, 0), ...101>, 0), ...] == [(|110>, a*co...conjugate(b))]
FAILED sympy/physics/quantum/tests/test_qubit.py::test_measure_all - assert [(|00>, 0), (...0), (|11>, 1)] == [(|11>, 1)]
FAILED sympy/integrals/tests/test_integrals.py::test_issue_20782 - assert 0 == 0.0
FAILED sympy/polys/tests/test_polytools.py::test_nroots - assert 0 == 0.0
FAILED sympy/solvers/tests/test_numeric.py::test_issue_6408 - assert 0 == 0.0
FAILED sympy/solvers/tests/test_numeric.py::test_issue_6408_integral - assert 0 == 0.0
FAILED sympy/utilities/tests/test_wester.py::test_D1 - assert (0.0 / sqrt(2)) == 0.0
This is what it takes to get the tests passing:
diff --git a/sympy/core/evalf.py b/sympy/core/evalf.py
index c5337c446b..8cef9078c5 100644
--- a/sympy/core/evalf.py
+++ b/sympy/core/evalf.py
@@ -1494,7 +1494,7 @@ def evalf(x: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
re, im = as_real_imag()
if re.has(re_) or im.has(im_):
raise NotImplementedError
- if re == 0.0:
+ if not re:
re = None
reprec = None
elif re.is_number:
@@ -1502,7 +1502,7 @@ def evalf(x: 'Expr', prec: int, options: OPT_DICT) -> TMP_RES:
reprec = prec
else:
raise NotImplementedError
- if im == 0.0:
+ if not im:
im = None
imprec = None
elif im.is_number:
diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
index 289c41f362..8f7519d281 100644
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -1598,8 +1598,6 @@ def __eq__(self, other):
# S(0) == S.false is False
# S(0) == False is True
return False
- if not self:
- return not other
if other.is_NumberSymbol:
if other.is_irrational:
return False
diff --git a/sympy/core/tests/test_numbers.py b/sympy/core/tests/test_numbers.py
index 5d79e7b472..b5092f0be5 100644
--- a/sympy/core/tests/test_numbers.py
+++ b/sympy/core/tests/test_numbers.py
@@ -455,13 +455,27 @@ def eq(a, b):
t = Float("1.0E-15")
return (-t < a - b < t)
- zeros = (0, S.Zero, 0., Float(0))
- for i, j in permutations(zeros[:-1], 2):
- assert i == j
- for i, j in permutations(zeros[-2:], 2):
- assert i == j
- for z in zeros:
- assert z in zeros
+ equal_pairs = [
+ (0, 0.0), # This is just how Python works...
+ (0, S.Zero),
+ (0.0, Float(0)),
+ ]
+ unequal_pairs = [
+ (0.0, S.Zero),
+ (0, Float(0)),
+ (S.Zero, Float(0)),
+ ]
+ for p1, p2 in equal_pairs:
+ assert (p1 == p2) is True
+ assert (p1 != p2) is False
+ assert (p2 == p1) is True
+ assert (p2 != p1) is False
+ for p1, p2 in unequal_pairs:
+ assert (p1 == p2) is False
+ assert (p1 != p2) is True
+ assert (p2 == p1) is False
+ assert (p2 != p1) is True
+
assert S.Zero.is_zero
a = Float(2) ** Float(3)
diff --git a/sympy/geometry/tests/test_point.py b/sympy/geometry/tests/test_point.py
index abe63874a8..1f2b2768eb 100644
--- a/sympy/geometry/tests/test_point.py
+++ b/sympy/geometry/tests/test_point.py
@@ -418,7 +418,7 @@ def test_arguments():
a = Point(0, 1)
assert a/10.0 == Point(0, 0.1, evaluate=False)
a = Point(0, 1)
- assert a*10.0 == Point(0.0, 10.0, evaluate=False)
+ assert a*10.0 == Point(0, 10.0, evaluate=False)
# test evaluate=False when changing dimensions
u = Point(.1, .2, evaluate=False)
diff --git a/sympy/integrals/tests/test_integrals.py b/sympy/integrals/tests/test_integrals.py
index 8436d6127c..9f7552637d 100644
--- a/sympy/integrals/tests/test_integrals.py
+++ b/sympy/integrals/tests/test_integrals.py
@@ -2080,7 +2080,7 @@ def test_issue_20782():
assert integrate(fun1, L) == 1
assert integrate(fun2, L) == 0
assert integrate(-fun1, L) == -1
- assert integrate(-fun2, L) == 0.
+ assert integrate(-fun2, L) == 0
assert integrate(fun_sum, L) == 1.
assert integrate(-fun_sum, L) == -1.
diff --git a/sympy/physics/quantum/qubit.py b/sympy/physics/quantum/qubit.py
index fb75b4c496..10f3df00ec 100644
--- a/sympy/physics/quantum/qubit.py
+++ b/sympy/physics/quantum/qubit.py
@@ -493,7 +493,7 @@ def matrix_to_qubit(matrix):
element = matrix[0, i]
if format in ('numpy', 'scipy.sparse'):
element = complex(element)
- if element != 0.0:
+ if element:
# Form Qubit array; 0 in bit-locations where i is 0, 1 in
# bit-locations where i is 1
qubit_array = [int(i & (1 << x) != 0) for x in range(nqubits)]
@@ -582,7 +582,7 @@ def measure_all(qubit, format='sympy', normalize=True):
size = max(m.shape) # Max of shape to account for bra or ket
nqubits = int(math.log(size)/math.log(2))
for i in range(size):
- if m[i] != 0.0:
+ if m[i]:
results.append(
(Qubit(IntQubit(i, nqubits=nqubits)), m[i]*conjugate(m[i]))
)
diff --git a/sympy/polys/matrices/tests/test_linsolve.py b/sympy/polys/matrices/tests/test_linsolve.py
index 9d8cd7eb9f..25300ef2cb 100644
--- a/sympy/polys/matrices/tests/test_linsolve.py
+++ b/sympy/polys/matrices/tests/test_linsolve.py
@@ -32,7 +32,8 @@ def test__linsolve_float():
y - x,
y - 0.0216 * x
]
- sol = {x:0.0, y:0.0}
+ # Should _linsolve return floats here?
+ sol = {x:0, y:0}
assert _linsolve(eqs, (x, y)) == sol
# Other cases should be close to eps
diff --git a/sympy/polys/tests/test_polytools.py b/sympy/polys/tests/test_polytools.py
index 1ba0e5a69b..c0672af667 100644
--- a/sympy/polys/tests/test_polytools.py
+++ b/sympy/polys/tests/test_polytools.py
@@ -3126,7 +3126,7 @@ def test_nroots():
eps = Float("1e-5")
assert re(roots[0]).epsilon_eq(-0.75487, eps) is S.true
- assert im(roots[0]) == 0.0
+ assert im(roots[0]) == 0
assert re(roots[1]) == Float(-0.5, 5)
assert im(roots[1]).epsilon_eq(-0.86602, eps) is S.true
assert re(roots[2]) == Float(-0.5, 5)
@@ -3139,7 +3139,7 @@ def test_nroots():
eps = Float("1e-6")
assert re(roots[0]).epsilon_eq(-0.75487, eps) is S.false
- assert im(roots[0]) == 0.0
+ assert im(roots[0]) == 0
assert re(roots[1]) == Float(-0.5, 5)
assert im(roots[1]).epsilon_eq(-0.86602, eps) is S.false
assert re(roots[2]) == Float(-0.5, 5)
diff --git a/sympy/solvers/tests/test_numeric.py b/sympy/solvers/tests/test_numeric.py
index f40bab6965..12abd38c80 100644
--- a/sympy/solvers/tests/test_numeric.py
+++ b/sympy/solvers/tests/test_numeric.py
@@ -73,12 +73,12 @@ def getroot(x0):
def test_issue_6408():
x = Symbol('x')
- assert nsolve(Piecewise((x, x < 1), (x**2, True)), x, 2) == 0.0
+ assert nsolve(Piecewise((x, x < 1), (x**2, True)), x, 2) == 0
def test_issue_6408_integral():
x, y = symbols('x y')
- assert nsolve(Integral(x*y, (x, 0, 5)), y, 2) == 0.0
+ assert nsolve(Integral(x*y, (x, 0, 5)), y, 2) == 0
@conserve_mpmath_dps
diff --git a/sympy/utilities/tests/test_wester.py b/sympy/utilities/tests/test_wester.py
index 848dbdae82..c5699a4eb0 100644
--- a/sympy/utilities/tests/test_wester.py
+++ b/sympy/utilities/tests/test_wester.py
@@ -269,7 +269,7 @@ def test_C24():
def test_D1():
- assert 0.0 / sqrt(2) == 0.0
+ assert 0.0 / sqrt(2) == 0
def test_D2():
@posita I have opened a PR gh-26910 which I think fixes the main inconsistency. I am not sure if that covers all the cases you identified since you printed out a very long list of cases but only referred explicitly to one or two cases.
This looks like #26910 addresses the cases raised in https://github.com/sympy/sympy/issues/26817#issuecomment-2266804738 and https://github.com/sympy/sympy/issues/26817#issuecomment-2266931853 , and it's helpful to understand @oscarbenjamin's explanation in https://github.com/sympy/sympy/issues/26817#issuecomment-2267102388 . From that perspective, #26910 makes sense.
However, I strongly urge designers to carefully consider applying different semantics to some built in operators but not others. That has all the hallmarks of a foot gun. If both sets of semantics are valuable, I would expect (or at least hope) that they would be clearly, intuitively, and obviously demarcated by syntax/call site, meaning something like one of the following:
class MyNum:
# Built-in operators *only* deal with structural equivalence
def __eq__(self, other):
return self._structurally_equal(other)
def __ne__(self, other):
return not self._structurally_equal(other)
def __le__(self, other):
return NotImplemented
def __lt__(self, other):
return NotImplemented
def __ge__(self, other):
return NotImplemented
def __gt__(self, other):
return NotImplemented
# Secondary stand-ins *only* deal with value comparisons
def value_eq(self, other):
return self._value_cmp(other) == 0
def value_ne(self, other):
return self._value_cmp(other) != 0
def value_le(self, other):
return self._value_cmp(other) <= 0
def value_lt(self, other):
return self._value_cmp(other) < 0
def value_ge(self, other):
return self._value_cmp(other) >= 0
def value_gt(self, other):
return self._value_cmp(other) > 0
class MyNum:
# Built-in operators *only* deal with value comparisons
def __eq__(self, other):
return self._value_cmp(other) == 0
def __ne__(self, other):
return self._value_cmp(other) != 0
def __le__(self, other):
return self._value_cmp(other) <= 0
def __lt__(self, other):
return self._value_cmp(other) < 0
def __ge__(self, other):
return self._value_cmp(other) >= 0
def __gt__(self, other):
return self._value_cmp(other) > 0
# Secondary stand-ins *only* deal with structural equivalence
def structure_eq(self, other):
return self._structurally_equal(other)
def structure_ne(self, other):
return not self._structurally_equal(other)
My guess is that the second approach (i.e., that built-in operators address value comparisons) is likely what most users would expect. (Also, return
ing NotImplemented
for four of the six built-in operators, but then having all six represented through secondary methods is strong signal that built-ins should be used for value comparisons.)
I doubt anyone could reasonably expect mixed semantics that split between (__eq__, __ne__)
and (__le__, __lt__, __ge__, __gt__)
. That just seems like the the worst of all possible worlds. Assuming the split semantics foot gun is still a bug (and I think it should be), I think this is a legit issue and should likely remain open, even if #26910 is merged, unless I've misunderstood the fix and @oscarbenjamin's comments above?
I doubt anyone could reasonably expect mixed semantics that split between
(__eq__, __ne__)
and(__le__, __lt__, __ge__, __gt__)
Maybe but that split already exists and there is an awkwardness in Python here. The __eq__
method is used by basic data structures like sets and dicts. If we want a == b
then we have to accept that e.g. {a,b}={a}
:
In [3]: {1, 1.0}
Out[3]: {1}
In [4]: {1.0, 1}
Out[4]: {1.0}
In [5]: {S(1), S(1.0)} # With sympy types
Out[5]: {1, 1.0}
In [6]: {S(1.0), S(1)}
Out[6]: {1.0, 1}
The decision that ==
means structural equality in SymPy was made long ago. The change now makes the implementation of that decision consistent.
The decision that
==
means structural equality in SymPy was made long ago.
For example:
In [12]: e1 = (x + 1)**2
In [13]: e2 = expand(e1)
In [14]: e1
Out[14]:
2
(x + 1)
In [15]: e2
Out[15]:
2
x + 2⋅x + 1
In [16]: e1 == e2 # Why is this False?
Out[16]: False
In [17]: Eq(e1, e2)
Out[17]:
2 2
(x + 1) = x + 2⋅x + 1
In [18]: Eq(e1, e2).simplify()
Out[18]: True
In [19]: e1 > e2
Out[19]:
2 2
(x + 1) > x + 2⋅x + 1
In [20]: (e1 > e2).simplify()
Out[20]: False
In [21]: (e1 >= e2).simplify()
Out[21]: True
One thing that we could do to make this more palatable is have comparisons with Python's int and float behave differently from comparisons with only SymPy types. Then:
Integer(0) == int(0) # True
Integer(0) == float(0) # True
Float(0) == int(0) # True
Float(0) == float(0) # True
However we would still have
Integer(0) == Float(0) # False
The primary reason that this last one is important is because it applies when looking at the args in a larger expression. There are many places in SymPy that want to collect expressions together in a set or a dict or that want to check if one expression is equal to another during manipulations. Generic operations that recurse down through expression trees need to be able to compare expressions to see whether they are the same expression and a == b
is the way to do that. In those contexts though we always work with sympified types and do not need to worry about native Python int/float.
A decision is needed here because this is the only outstanding item for SymPy 1.13.2.
Now that gh-26910 is merged I am backporting a fix to the 1.13 branch.
We can still consider other changes if this is problematic but the basic inconsistency is fixed:
In [1]: Float(0) == Integer(0)
Out[1]: False
In [2]: Integer(0) == Float(0)
Out[2]: False
I found a difference between sympy 1.12 and 1.13.
But in different sympy versions,
sympy.Number(0)
always has the same behavior that equals 0.0.Is this a regression or BC breaking change? It confused me that
sympy.Number(0.0) == 0
returnsFalse
for version 1.13.