Repository: sympy/sympy

Issue:
doit() method *sometimes* ignores floor and ceiling within Sum
**Summary of problem**: the `doit()` method will sometimes (depending on the expression) ignore any `floor` or `ceiling` that is located inside a `Sum`.

**Example detailing the bug**

The following code sets up the problem:

```
from sympy import floor, Sum, Symbol
i = Symbol('i')
a = Sum(floor(2*2**(-i)), (i, 1, 2))
b = floor(2*2**(-1)) + floor(2*2**(-2))
```

Note that these two expressions `a` and `b` are the same, just `a` is written with the summation notation and `b` is written out explicitly. They both equal `1`. 

Printing these variables give us the expected results:

![image](https://user-images.githubusercontent.com/10404731/122962576-da35c480-d385-11eb-8927-94dff7f43a0c.png)

However, if we run the `doit()` method on them we see a discrepancy:

![image](https://user-images.githubusercontent.com/10404731/122962993-2bde4f00-d386-11eb-8fc3-cb365bb5790f.png)

where we see that the `floor` function inside the summation has been ignored. The same issue also arises when using the `ceiling` method (i.e. the `doit()` method ignores it when it is in a summation).

However it seems like this problem is not consistent, e.g. if you remove the `2*` from the expression it works fine. More precisely if you run

```
from sympy import floor, Sum, Symbol
i = Symbol('i')
a = Sum(floor(2**(-i)), (i, 1, 2))
b = floor(2**(-1)) + floor(2**(-2))
print(a.doit())
print(b.doit())
```
you get the exptected results (this works for `ceiling` too)

(bug discovered on sympy version 1.8 on python 3.7.3)

Discussion:

Patch:
diff --git a/sympy/core/mul.py b/sympy/core/mul.py
index bc1575846d..2c1d3b1ec8 100644
--- a/sympy/core/mul.py
+++ b/sympy/core/mul.py
@@ -1351,7 +1351,7 @@ def _eval_is_integer(self):
                 b, e = a.as_base_exp()
                 if not b.is_integer or not e.is_integer: return
                 if e.is_negative:
-                    denominators.append(b)
+                    denominators.append(2 if a is S.Half else Pow(a, S.NegativeOne))
                 else:
                     # for integer b and positive integer e: a = b**e would be integer
                     assert not e.is_positive


Test patch:
diff --git a/sympy/concrete/tests/test_sums_products.py b/sympy/concrete/tests/test_sums_products.py
index cd7884744e..d7b33ae196 100644
--- a/sympy/concrete/tests/test_sums_products.py
+++ b/sympy/concrete/tests/test_sums_products.py
@@ -1429,6 +1429,13 @@ def test_matrixsymbol_summation_numerical_limits():
     assert Sum(A**n*B**n, (n, 1, 3)).doit() == ans
 
 
+def test_issue_21651():
+    from sympy import floor, Sum, Symbol
+    i = Symbol('i')
+    a = Sum(floor(2*2**(-i)), (i, S.One, 2))
+    assert a.doit() == S.One
+
+
 @XFAIL
 def test_matrixsymbol_summation_symbolic_limits():
     N = Symbol('N', integer=True, positive=True)
diff --git a/sympy/core/tests/test_assumptions.py b/sympy/core/tests/test_assumptions.py
index bef5be2dd7..08da66af7e 100644
--- a/sympy/core/tests/test_assumptions.py
+++ b/sympy/core/tests/test_assumptions.py
@@ -1198,6 +1198,12 @@ def test_issue_17556():
     assert z.is_finite is False
 
 
+def test_issue_21651():
+    k = Symbol('k', positive=True, integer=True)
+    exp = 2*2**(-k)
+    assert exp.is_integer is None
+
+
 def test_assumptions_copy():
     assert assumptions(Symbol('x'), dict(commutative=True)
         ) == {'commutative': True}
diff --git a/sympy/functions/elementary/tests/test_integers.py b/sympy/functions/elementary/tests/test_integers.py
index 76d20afc81..b2d140ca1a 100644
--- a/sympy/functions/elementary/tests/test_integers.py
+++ b/sympy/functions/elementary/tests/test_integers.py
@@ -548,6 +548,12 @@ def test_issue_4149():
     assert floor(3 + E + pi*I + y*I) == 5 + floor(pi + y)*I
 
 
+def test_issue_21651():
+    k = Symbol('k', positive=True, integer=True)
+    exp = 2*2**(-k)
+    assert isinstance(floor(exp), floor)
+
+
 def test_issue_11207():
     assert floor(floor(x)) == floor(x)
     assert floor(ceiling(x)) == ceiling(x)