Repository: sympy/sympy

Issue:
`is_zero` broken for integer/float comparison
Not sure if this is a recent regression, but comparing an integer-assumption symbol to a Float (or a non-integer to an `Integer`) returns the wrong value:

```python
>>> i = Symbol('x', integer=True)
>>> Eq(i, 1.0)
False
>>> Eq(i, 1)
Eq(x, 1)

>>> ni = Symbol('x', integer=False)
>>> Eq(ni, 1)
False
>>> Eq(ni, 1.0)
Eq(x, 1.0)
```

Upon further inspection, it has to do with the difference being checked in `is_eq`: `_dif.is_zero` returns False, even though that should not always be true.


Discussion:

Patch:
diff --git a/.github/workflows/ci-sage.yml b/.github/workflows/ci-sage.yml
index caccf9a7eb..8acbc93a2d 100644
--- a/.github/workflows/ci-sage.yml
+++ b/.github/workflows/ci-sage.yml
@@ -100,10 +100,9 @@ jobs:
         ["standard"]
       # Sage distribution packages to build
       targets:                  SAGE_CHECK=no SAGE_CHECK_PACKAGES=sympy build SAGE_DOCTEST_RANDOM_SEED=0 ptest
-      # Test against the Sage PR:
-      # https://github.com/sagemath/sage/pull/36641
+      # Standard setting: Test the current beta release of Sage.
       sage_repo:                sagemath/sage
-      sage_ref:                 refs/pull/36641/merge
+      sage_ref:                 develop
       # To test with a Sage PR, use this:
       #sage_ref:                 refs/pull/PR_NUMBER/merge
       upstream_artifact:        upstream
diff --git a/sympy/assumptions/handlers/ntheory.py b/sympy/assumptions/handlers/ntheory.py
index 4f1397b283..ccb91f726e 100644
--- a/sympy/assumptions/handlers/ntheory.py
+++ b/sympy/assumptions/handlers/ntheory.py
@@ -5,7 +5,7 @@
 from sympy.assumptions import Q, ask
 from sympy.core import Add, Basic, Expr, Float, Mul, Pow, S
 from sympy.core.numbers import (ImaginaryUnit, Infinity, Integer, NaN,
-    NegativeInfinity, NumberSymbol, Rational)
+    NegativeInfinity, NumberSymbol, Rational, int_valued)
 from sympy.functions import Abs, im, re
 from sympy.ntheory import isprime
 
@@ -119,13 +119,15 @@ def _(expr, assumptions):
 
 def _EvenPredicate_number(expr, assumptions):
     # helper method
+    if isinstance(expr, (float, Float)):
+        if int_valued(expr):
+            return None
+        return False
     try:
         i = int(expr.round())
-        if not (expr - i).equals(0):
-            raise TypeError
     except TypeError:
         return False
-    if isinstance(expr, (float, Float)):
+    if not (expr - i).equals(0):
         return False
     return i % 2 == 0
 
diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
index d4e112a0c9..f3839e22d1 100644
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -749,8 +749,11 @@ class Float(Number):
 
     _mpf_: tuple[int, int, int, int]
 
-    # A Float represents many real numbers,
-    # both rational and irrational.
+    # A Float, though rational in form, does not behave like
+    # a rational in all Python expressions so we deal with
+    # exceptions (where we want to deal with the rational
+    # form of the Float as a rational) at the source rather
+    # than assigning a mathematically loaded category of 'rational'
     is_rational = None
     is_irrational = None
     is_number = True
@@ -969,7 +972,10 @@ def _eval_is_infinite(self):
         return False
 
     def _eval_is_integer(self):
-        return self._mpf_ == fzero
+        if self._mpf_ == fzero:
+            return True
+        if not int_valued(self):
+            return False
 
     def _eval_is_negative(self):
         if self._mpf_ in (_mpf_ninf, _mpf_inf):
diff --git a/sympy/core/relational.py b/sympy/core/relational.py
index cce1dfe76c..5ccfc7e963 100644
--- a/sympy/core/relational.py
+++ b/sympy/core/relational.py
@@ -4,6 +4,7 @@
 from .sorting import ordered
 from .evalf import EvalfMixin
 from .function import AppliedUndef
+from .numbers import int_valued
 from .singleton import S
 from .sympify import _sympify, SympifyError
 from .parameters import global_parameters
@@ -1571,6 +1572,15 @@ def split_real_imag(expr):
             if z:
                 return True
 
+        # is_zero cannot help decide integer/rational with Float
+        c, t = dif.as_coeff_Add()
+        if c.is_Float:
+            if int_valued(c):
+                if t.is_integer is False:
+                    return False
+            elif t.is_rational is False:
+                return False
+
         n2 = _n2(lhs, rhs)
         if n2 is not None:
             return _sympify(n2 == 0)
diff --git a/sympy/functions/combinatorial/numbers.py b/sympy/functions/combinatorial/numbers.py
index 52f73a2660..df0fa1ebd4 100644
--- a/sympy/functions/combinatorial/numbers.py
+++ b/sympy/functions/combinatorial/numbers.py
@@ -539,8 +539,8 @@ def _calc_bernoulli(n):
 
     # We implement a specialized memoization scheme to handle each
     # case modulo 6 separately
-    _cache = {0: S.One, 2: Rational(1, 6), 4: Rational(-1, 30)}
-    _highest = {0: 0, 2: 2, 4: 4}
+    _cache = {0: S.One, 1: Rational(1, 2), 2: Rational(1, 6), 4: Rational(-1, 30)}
+    _highest = {0: 0, 1: 1, 2: 2, 4: 4}
 
     @classmethod
     def eval(cls, n, x=None):
diff --git a/sympy/matrices/expressions/matexpr.py b/sympy/matrices/expressions/matexpr.py
index 5fce1478c4..5cee3a8f1e 100644
--- a/sympy/matrices/expressions/matexpr.py
+++ b/sympy/matrices/expressions/matexpr.py
@@ -235,7 +235,8 @@ def _eval_derivative(self, x):
     @classmethod
     def _check_dim(cls, dim):
         """Helper function to check invalid matrix dimensions"""
-        ok = check_assumptions(dim, integer=True, nonnegative=True)
+        ok = not dim.is_Float and check_assumptions(
+            dim, integer=True, nonnegative=True)
         if ok is False:
             raise ValueError(
                 "The dimension specification {} should be "
diff --git a/sympy/matrices/expressions/sets.py b/sympy/matrices/expressions/sets.py
index 90816c684d..de29b1db14 100644
--- a/sympy/matrices/expressions/sets.py
+++ b/sympy/matrices/expressions/sets.py
@@ -57,7 +57,8 @@ def _contains(self, other):
     @classmethod
     def _check_dim(cls, dim):
         """Helper function to check invalid matrix dimensions"""
-        ok = check_assumptions(dim, integer=True, nonnegative=True)
+        ok = not dim.is_Float and check_assumptions(
+            dim, integer=True, nonnegative=True)
         if ok is False:
             raise ValueError(
                 "The dimension specification {} should be "
diff --git a/sympy/stats/stochastic_process_types.py b/sympy/stats/stochastic_process_types.py
index 5b72a6c389..e1cdd0ea84 100644
--- a/sympy/stats/stochastic_process_types.py
+++ b/sympy/stats/stochastic_process_types.py
@@ -1644,7 +1644,7 @@ class BernoulliProcess(DiscreteTimeStochasticProcess):
     >>> B = BernoulliProcess("B", p=0.7, success=1, failure=0)
     >>> B.state_space
     {0, 1}
-    >>> (B.p).round(2)
+    >>> B.p.round(2)
     0.70
     >>> B.success
     1


Test patch:
diff --git a/sympy/assumptions/tests/test_query.py b/sympy/assumptions/tests/test_query.py
index 40ed2a8c2d..4a398b80e6 100644
--- a/sympy/assumptions/tests/test_query.py
+++ b/sympy/assumptions/tests/test_query.py
@@ -89,7 +89,7 @@ def test_int_12():
 def test_float_1():
     z = 1.0
     assert ask(Q.commutative(z)) is True
-    assert ask(Q.integer(z)) is False
+    assert ask(Q.integer(z)) is None
     assert ask(Q.rational(z)) is None
     assert ask(Q.real(z)) is True
     assert ask(Q.complex(z)) is True
@@ -97,11 +97,11 @@ def test_float_1():
     assert ask(Q.imaginary(z)) is False
     assert ask(Q.positive(z)) is True
     assert ask(Q.negative(z)) is False
-    assert ask(Q.even(z)) is False
-    assert ask(Q.odd(z)) is False
+    assert ask(Q.even(z)) is None
+    assert ask(Q.odd(z)) is None
     assert ask(Q.finite(z)) is True
-    assert ask(Q.prime(z)) is False
-    assert ask(Q.composite(z)) is False
+    assert ask(Q.prime(z)) is None
+    assert ask(Q.composite(z)) is None
     assert ask(Q.hermitian(z)) is True
     assert ask(Q.antihermitian(z)) is False
 
@@ -2307,11 +2307,11 @@ def test_check_old_assumption():
 
 
 def test_issue_9636():
-    assert ask(Q.integer(1.0)) is False
-    assert ask(Q.prime(3.0)) is False
-    assert ask(Q.composite(4.0)) is False
-    assert ask(Q.even(2.0)) is False
-    assert ask(Q.odd(3.0)) is False
+    assert ask(Q.integer(1.0)) is None
+    assert ask(Q.prime(3.0)) is None
+    assert ask(Q.composite(4.0)) is None
+    assert ask(Q.even(2.0)) is None
+    assert ask(Q.odd(3.0)) is None
 
 
 def test_autosimp_used_to_fail():
diff --git a/sympy/core/tests/test_numbers.py b/sympy/core/tests/test_numbers.py
index 1834524a44..82a221cf44 100644
--- a/sympy/core/tests/test_numbers.py
+++ b/sympy/core/tests/test_numbers.py
@@ -504,7 +504,7 @@ def eq(a, b):
     # rationality properties
     # if the integer test fails then the use of intlike
     # should be removed from gamma_functions.py
-    assert Float(1).is_integer is False
+    assert Float(1).is_integer is None
     assert Float(1).is_rational is None
     assert Float(1).is_irrational is None
     assert sqrt(2).n(15).is_rational is None
diff --git a/sympy/core/tests/test_relational.py b/sympy/core/tests/test_relational.py
index 723ec4cafe..6ab6405c49 100644
--- a/sympy/core/tests/test_relational.py
+++ b/sympy/core/tests/test_relational.py
@@ -5,7 +5,7 @@
 from sympy.assumptions.ask import Q
 from sympy.core.add import Add
 from sympy.core.basic import Basic
-from sympy.core.expr import Expr
+from sympy.core.expr import Expr, unchanged
 from sympy.core.function import Function
 from sympy.core.mul import Mul
 from sympy.core.numbers import (Float, I, Rational, nan, oo, pi, zoo)
@@ -1252,6 +1252,19 @@ def test_weak_strict():
     assert eq.strict == Lt(x, 1)
     assert eq.weak == eq
 
+
+def test_issue_23731():
+    i = symbols('i', integer=True)
+    assert unchanged(Eq, i, 1.0)
+    assert unchanged(Eq, i/2, 0.5)
+    ni = symbols('ni', integer=False)
+    assert Eq(ni, 1) == False
+    assert unchanged(Eq, ni, .1)
+    assert Eq(ni, 1.0) == False
+    nr = symbols('nr', rational=False)
+    assert Eq(nr, .1) == False
+
+
 def test_rewrite_Add():
     from sympy.testing.pytest import warns_deprecated_sympy
     with warns_deprecated_sympy():
diff --git a/sympy/sets/tests/test_fancysets.py b/sympy/sets/tests/test_fancysets.py
index 08f097b934..b23c2a99fc 100644
--- a/sympy/sets/tests/test_fancysets.py
+++ b/sympy/sets/tests/test_fancysets.py
@@ -114,7 +114,8 @@ def test_ImageSet():
     harmonics = ImageSet(Lambda(x, 1/x), S.Naturals)
     assert Rational(1, 5) in harmonics
     assert Rational(.25) in harmonics
-    assert 0.25 not in harmonics
+    assert harmonics.contains(.25) == Contains(
+        0.25, ImageSet(Lambda(x, 1/x), S.Naturals), evaluate=False)
     assert Rational(.3) not in harmonics
     assert (1, 2) not in harmonics
 
@@ -1268,7 +1269,8 @@ def test_Rationals():
         Rational(1, 3), 3, Rational(-1, 3), -3, Rational(2, 3)]
     assert Basic() not in S.Rationals
     assert S.Half in S.Rationals
-    assert S.Rationals.contains(0.5) == Contains(0.5, S.Rationals, evaluate=False)
+    assert S.Rationals.contains(0.5) == Contains(
+        0.5, S.Rationals, evaluate=False)
     assert 2 in S.Rationals
     r = symbols('r', rational=True)
     assert r in S.Rationals