Repository: sympy/sympy

Issue:
perfect_power(-64) fails to find (-4)**3
From SO: 
https://stackoverflow.com/questions/79172182/why-does-sympy-perfect-power-64-return-false

This is inconsistent:
```python
In [21]: perfect_power(-8)
Out[21]: (-2, 3)

In [22]: (-2)**3
Out[22]: -8

In [23]: perfect_power(-64)
Out[23]: False

In [24]: (-4)**3
Out[24]: -64
```
If it is supposed to work for negative numbers with odd exponent then the `-64` case is a bug.

The logic here is incorrect:
https://github.com/sympy/sympy/blob/91ab0d56601537db30574666941424d4c24f3083/sympy/ntheory/factor_.py#L521-L528
It checks if the exponent is even so in the case of `-64` we get:
```python
In [28]: perfect_power(64)
Out[28]: (2, 6)
```
This means that `64 = 2**6` but then that is `(2**2)**3` which has odd exponent. The proper check should be if the exponent is a power of `2`. If there is any odd factor then a negative perfect power can be found.

In general I think that it is a mistake to allow `perfect_power` to be defined for negative arguments but I guess it is too late for that.

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



* No release notes entry will be added for this pull request.

<details><summary>Click here to see the pull request description that was parsed.</summary>

    This PR fixes a bug in `perfect_power` which was handling negative integers with odd powers incorrectly. 

    What it did was:

    - It would first find perfect power of the positive number(converting the negative int to positive)
    - Then ONLY return a result if the exponent was odd
    - But this missed cases where the exponent was even but could be rewritten with an odd exponent (see [comment](https://github.com/sympy/sympy/issues/27238#issue-2645983668))

    <!-- Your title above should be a short description of what
    was changed. Do not include the issue number in the title. -->

    #### References to other Issues or PRs
    <!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
    format, e.g. "Fixes #1234" (see
    https://tinyurl.com/auto-closing for more information). Also, please
    write a comment on that issue linking back to this pull request once it is
    open. -->
    Fixes #27238 

    #### Brief description of what is fixed or changed


    #### Other comments


    #### Release Notes

    <!-- Write the release notes for this release below between the BEGIN and END
    statements. The basic format is a bulleted list with the name of the subpackage
    and the release note for this PR. For example:

    * solvers
      * Added a new solver for logarithmic equations.

    * functions
      * Fixed a bug with log of integers. Formerly, `log(-x)` incorrectly gave `-log(x)`.

    * physics.units
      * Corrected a semantical error in the conversion between volt and statvolt which
        reported the volt as being larger than the statvolt.

    or if no release note(s) should be included use:

    NO ENTRY

    See https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more
    information on how to write release notes. The bot will check your release
    notes automatically to see if they are formatted correctly. -->

    <!-- BEGIN RELEASE NOTES -->
    NO ENTRY
    <!-- END RELEASE NOTES -->


</details><p>

oscarbenjamin: The different examples that are pointed out need to be added to the tests.

The OP should not claim to fix the issue unless it also handles the other case mentioned there: https://github.com/sympy/sympy/issues/27238#issuecomment-2466462427

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9252f015ad..bdac126d88 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,14 +3,14 @@
 
 # This is separate from the GitHub Actions build that builds the docs, which
 # also builds the docs PDF.
-version: 2.1
+version: 2
 
 # Aliases to reuse
 _defaults: &defaults
   docker:
     # CircleCI maintains a library of pre-built images
     # documented at https://circleci.com/docs/2.0/circleci-images/
-    - image: cimg/python:3.12
+    - image: cimg/python:3.10.2
   working_directory: ~/repo
 
 jobs:
diff --git a/.mailmap b/.mailmap
index b2d2541c4a..4ce46fc25b 100644
--- a/.mailmap
+++ b/.mailmap
@@ -197,6 +197,7 @@ Aaron Meurer <asmeurer@gmail.com>
 Aaron Miller <acmiller273@gmail.com>
 Aaron Stiff <69512633+AaronStiff@users.noreply.github.com>
 Aaryan Dewan <aaryandewan@yahoo.com> Aaryan Dewan <49852384+aaryandewan@users.noreply.github.com>
+Aasim Ali <aasim250205@gmail.com> Aasim Ali <94692076+aasim-maverick@users.noreply.github.com>
 Aasim Ali <aasim250205@gmail.com> aasim-maverick <aasim250205@gmail.com>
 Abbas <iamabbas7@gmail.com> Abbas <42001049+iam-abbas@users.noreply.github.com>
 Abbas Mohammed <42001049+iam-abbas@users.noreply.github.com> Abbas <42001049+iam-abbas@users.noreply.github.com>
@@ -872,7 +873,6 @@ Klaus Rettinghaus <klaus.rettinghaus@enote.com>
 Konrad Meyer <konrad.meyer@gmail.com>
 Konstantin Togoi <konstantin.togoi@gmail.com> <togoi.konstantin@outlook.com>
 Konstantinos Riganas <kostasriganas24@gmail.com> kostas-rigan <t8200145@aueb.gr>
-Kris Katterjohn <katterjohn@gmail.com>
 Kristian Brünn <hello@kristianbrunn.com> Kristianmitk <hello@kristianbrunn.com>
 Krit Karan <kritkaran.b13@iiits.in> <kritkaran94@users.noreply.github.com>
 Kshitij <kshitijparwani.mat18@itbhu.ac.in> Kshitij Parwani <44468674+P-Kshitij@users.noreply.github.com>
@@ -1024,8 +1024,6 @@ Mikhail Remnev <maremnev@gmail.com> Mikhail Remnev <141655736+maremnev@users.nor
 Milan Jolly <milan.cs16@iitp.ac.in> mijo2 <milan.cs16@iitp.ac.in>
 Min Ragan-Kelley <benjaminrk@gmail.com>
 Miro Hrončok <miro@hroncok.cz>
-Mivik <mivikq@gmail.com> Mivik <54128043+Mivik@users.noreply.github.com>
-Mivik <mivikq@gmail.com> Mivik <mivikq@gmail.com>
 Mohak Malviya <mohakmalviya2000@gmail.com> Senku <mohakmalviya2000@gmail.com>
 Mohamed Rezk <mohrizq895@gmail.com>
 Mohammad Sadeq Dousti <msdousti@gmail.com>
diff --git a/sympy/core/basic.py b/sympy/core/basic.py
index 3274da7240..30f12c331f 100644
--- a/sympy/core/basic.py
+++ b/sympy/core/basic.py
@@ -19,12 +19,12 @@
 from sympy.utilities.iterables import iterable, numbered_symbols
 from sympy.utilities.misc import filldedent, func_name
 
+from inspect import getmro
+
 
 if TYPE_CHECKING:
     from typing import ClassVar
-    from typing_extensions import Self
     from .assumptions import StdFactKB
-    from .symbol import Symbol
 
 
 def as_Basic(expr):
@@ -393,6 +393,41 @@ def compare(self, other):
                 return c
         return 0
 
+    @staticmethod
+    def _compare_pretty(a, b):
+        """return -1, 0, 1 if a is canonically less, equal or
+        greater than b. This is used when 'order=old' is selected
+        for printing. This puts Order last, orders Rationals
+        according to value, puts terms in order wrt the power of
+        the last power appearing in a term. Ties are broken using
+        Basic.compare.
+        """
+        from sympy.series.order import Order
+        if isinstance(a, Order) and not isinstance(b, Order):
+            return 1
+        if not isinstance(a, Order) and isinstance(b, Order):
+            return -1
+
+        if a.is_Rational and b.is_Rational:
+            l = a.p * b.q
+            r = b.p * a.q
+            return (l > r) - (l < r)
+        else:
+            from .symbol import Wild
+            p1, p2, p3 = Wild("p1"), Wild("p2"), Wild("p3")
+            r_a = a.match(p1 * p2**p3)
+            if r_a and p3 in r_a:
+                a3 = r_a[p3]
+                r_b = b.match(p1 * p2**p3)
+                if r_b and p3 in r_b:
+                    b3 = r_b[p3]
+                    c = Basic.compare(a3, b3)
+                    if c != 0:
+                        return c
+
+        # break ties
+        return Basic.compare(a, b)
+
     @classmethod
     def fromiter(cls, args, **assumptions):
         """
@@ -412,7 +447,7 @@ def fromiter(cls, args, **assumptions):
         return cls(*tuple(args), **assumptions)
 
     @classmethod
-    def class_key(cls) -> tuple[int, int, str]:
+    def class_key(cls):
         """Nice order of classes."""
         return 5, 0, cls.__name__
 
@@ -704,7 +739,7 @@ def expr_free_symbols(self):
             active_deprecations_target="deprecated-expr-free-symbols")
         return set()
 
-    def as_dummy(self) -> "Self":
+    def as_dummy(self):
         """Return the expression with any objects having structurally
         bound symbols replaced with unique, canonical symbols within
         the object in which they appear and having only the default
@@ -729,7 +764,7 @@ def as_dummy(self) -> "Self":
         =====
 
         Any object that has structurally bound variables should have
-        a property, ``bound_symbols`` that returns those symbols
+        a property, `bound_symbols` that returns those symbols
         appearing in the object.
         """
         from .symbol import Dummy, Symbol
@@ -748,10 +783,10 @@ def can(x):
         return self.replace(
             lambda x: hasattr(x, 'bound_symbols'),
             can,
-            simultaneous=False) # type:ignore
+            simultaneous=False)
 
     @property
-    def canonical_variables(self) -> dict[Basic, Symbol]:
+    def canonical_variables(self):
         """Return a dictionary mapping any variable defined in
         ``self.bound_symbols`` to Symbols that do not clash
         with any free symbols in the expression.
@@ -764,18 +799,14 @@ def canonical_variables(self) -> dict[Basic, Symbol]:
         >>> Lambda(x, 2*x).canonical_variables
         {x: _0}
         """
-        bound: list[Basic] | None = getattr(self, 'bound_symbols', None)
-        if bound is None:
+        if not hasattr(self, 'bound_symbols'):
             return {}
         dums = numbered_symbols('_')
         reps = {}
         # watch out for free symbol that are not in bound symbols;
         # those that are in bound symbols are about to get changed
-
-        # XXX: free_symbols only returns particular kinds of expressions that
-        # generally have a .name attribute. There is not a proper class/type
-        # that represents this.
-        names = {i.name for i in self.free_symbols - set(bound)} # type: ignore
+        bound = self.bound_symbols
+        names = {i.name for i in self.free_symbols - set(bound)}
         for b in bound:
             d = next(dums)
             if b.is_Symbol:
@@ -799,13 +830,28 @@ def rcall(self, *args):
         >>> (x + Lambda(y, 2*y)).rcall(z)
         x + 2*z
         """
-        if callable(self):
-            return self(*args)
-        elif self.args:
-            newargs = [sub.rcall(*args) for sub in self.args]
-            return self.func(*newargs)
+        return Basic._recursive_call(self, args)
+
+    @staticmethod
+    def _recursive_call(expr_to_call, on_args):
+        """Helper for rcall method."""
+        from .symbol import Symbol
+        def the_call_method_is_overridden(expr):
+            for cls in getmro(type(expr)):
+                if '__call__' in cls.__dict__:
+                    return cls != Basic
+
+        if callable(expr_to_call) and the_call_method_is_overridden(expr_to_call):
+            if isinstance(expr_to_call, Symbol):  # XXX When you call a Symbol it is
+                return expr_to_call               # transformed into an UndefFunction
+            else:
+                return expr_to_call(*on_args)
+        elif expr_to_call.args:
+            args = [Basic._recursive_call(
+                sub, on_args) for sub in expr_to_call.args]
+            return type(expr_to_call)(*args)
         else:
-            return self
+            return expr_to_call
 
     def is_hypergeometric(self, k):
         from sympy.simplify.simplify import hypersimp
@@ -841,11 +887,25 @@ def is_comparable(self):
         1
 
         """
-        return self._eval_is_comparable()
-
-    def _eval_is_comparable(self):
-        # Expr.is_comparable overrides this
-        return False
+        is_extended_real = self.is_extended_real
+        if is_extended_real is False:
+            return False
+        if not self.is_number:
+            return False
+        # don't re-eval numbers that are already evaluated since
+        # this will create spurious precision
+        n, i = [p.evalf(2) if not p.is_Number else p
+            for p in self.as_real_imag()]
+        if not (i.is_Number and n.is_Number):
+            return False
+        if i:
+            # if _prec = 1 we can't decide and if not,
+            # the answer is False because numbers with
+            # imaginary parts can't be compared
+            # so return False
+            return False
+        else:
+            return n._prec != 1
 
     @property
     def func(self):
@@ -1498,7 +1558,7 @@ def _has(self, iterargs, *patterns):
         # no success
         return False
 
-    def replace(self, query, value, map=False, simultaneous=True, exact=None) -> Basic:
+    def replace(self, query, value, map=False, simultaneous=True, exact=None):
         """
         Replace matching subexpressions of ``self`` with ``value``.
 
@@ -1754,7 +1814,7 @@ def rec_replace(expr):
             return expr
 
         rv = walk(self, rec_replace)
-        return (rv, mapping) if map else rv # type: ignore
+        return (rv, mapping) if map else rv
 
     def find(self, query, group=False):
         """Find all subexpressions matching a query."""
@@ -1887,7 +1947,7 @@ def match(self, pattern, old=False):
         pattern = sympify(pattern)
         return pattern.matches(self, old=old)
 
-    def count_ops(self, visual=False):
+    def count_ops(self, visual=None):
         """Wrapper for count_ops that returns the operation count."""
         from .function import count_ops
         return count_ops(self, visual)
@@ -1939,13 +1999,11 @@ def _eval_derivative_n_times(self, s, n):
         if isinstance(n, (int, Integer)):
             obj = self
             for i in range(n):
-                prev = obj
-                obj = obj._eval_derivative(s)
-                if obj is None:
-                    return None
-                elif obj == prev:
+                obj2 = obj._eval_derivative(s)
+                if obj == obj2 or obj2 is None:
                     break
-            return obj
+                obj = obj2
+            return obj2
         else:
             return None
 
@@ -2113,7 +2171,7 @@ def _sage_(self):
         This version of the method is merely a placeholder.
         """
         old_method = self._sage_
-        from sage.interfaces.sympy import sympy_init # type: ignore
+        from sage.interfaces.sympy import sympy_init
         sympy_init()  # may monkey-patch _sage_ method into self's class or superclasses
         if old_method == self._sage_:
             raise NotImplementedError('conversion to SageMath is not implemented')
diff --git a/sympy/core/expr.py b/sympy/core/expr.py
index f3ca5b3618..0225dd9a07 100644
--- a/sympy/core/expr.py
+++ b/sympy/core/expr.py
@@ -446,40 +446,6 @@ def is_number(self):
         """
         return all(obj.is_number for obj in self.args)
 
-    def _eval_is_comparable(self):
-        # Basic._eval_is_comparable always returns False, so we override it
-        # here
-        is_extended_real = self.is_extended_real
-        if is_extended_real is False:
-            return False
-        if not self.is_number:
-            return False
-
-        # XXX: as_real_imag() can be a very expensive operation. It should not
-        # be used here because is_comparable is used implicitly in many places.
-        # Probably this method should just return self.evalf(2).is_Number.
-
-        n, i = self.as_real_imag()
-
-        if not n.is_Number:
-            n = n.evalf(2)
-            if not n.is_Number:
-                return False
-
-        if not i.is_Number:
-            i = i.evalf(2)
-            if not i.is_Number:
-                return False
-
-        if i:
-            # if _prec = 1 we can't decide and if not,
-            # the answer is False because numbers with
-            # imaginary parts can't be compared
-            # so return False
-            return False
-        else:
-            return n._prec != 1
-
     def _random(self, n=None, re_min=-1, im_min=-1, re_max=1, im_max=1):
         """Return self evaluated, if possible, replacing free symbols with
         random complex values, if necessary.
diff --git a/sympy/ntheory/factor_.py b/sympy/ntheory/factor_.py
index b3baeeee47..a796ab80a1 100644
--- a/sympy/ntheory/factor_.py
+++ b/sympy/ntheory/factor_.py
@@ -502,31 +502,82 @@ def perfect_power(n, candidates=None, big=True, factor=True):
     sympy.core.intfunc.integer_nthroot
     sympy.ntheory.primetest.is_square
     """
-    if isinstance(n, Rational) and not n.is_Integer:
-        p, q = n.as_numer_denom()
-        if p is S.One:
-            pp = perfect_power(q)
-            if pp:
-                pp = (n.func(1, pp[0]), pp[1])
-        else:
-            pp = perfect_power(p)
-            if pp:
-                num, e = pp
-                pq = perfect_power(q, [e])
-                if pq:
-                    den, _ = pq
-                    pp = n.func(num, den), e
-        return pp
-
-    n = as_int(n)
+    # negative handling
     if n < 0:
-        pp = perfect_power(-n)
-        if pp:
+        if candidates is None:
+            pp = perfect_power(-n, big=True, factor=factor)
+            if not pp:
+                return False
+
             b, e = pp
-            if e % 2:
+            e2 = e & (-e)
+            b, e = b ** e2, e // e2
+
+            if e <= 1:
+                return False
+
+            if big or isprime(e):
                 return -b, e
+
+            for p in primerange(3, e + 1):
+                if e % p == 0:
+                    return - b ** (e // p), p
+
+        odd_candidates = {i for i in candidates if i % 2}
+        if not odd_candidates:
+            return False
+
+        pp = perfect_power(-n, odd_candidates, big, factor)
+        if pp:
+            return -pp[0], pp[1]
+
         return False
 
+    # non-integer handling
+    if isinstance(n, Rational) and not isinstance(n, Integer):
+        p, q = n.p, n.q
+
+        if p == 1:
+            qq = perfect_power(q, candidates, big, factor)
+            return (S.One / qq[0], qq[1]) if qq is not None else False
+
+        if not (pp:=perfect_power(p, factor=factor)):
+                return False
+        if not (qq:=perfect_power(q, factor=factor)):
+                return False
+        (num_base, num_exp), (den_base, den_exp) = pp, qq
+
+        def compute_tuple(exponent):
+            """Helper to compute final result given an exponent"""
+            new_num = num_base ** (num_exp // exponent)
+            new_den = den_base ** (den_exp // exponent)
+            return n.func(new_num, new_den), exponent
+
+        if candidates:
+            valid_candidates = [i for i in candidates
+                                if num_exp % i == 0 and den_exp % i == 0]
+            if not valid_candidates:
+                return False
+
+            e = max(valid_candidates) if big else min(valid_candidates)
+            return compute_tuple(e)
+
+        g = math.gcd(num_exp, den_exp)
+        if g == 1:
+            return False
+
+        if big:
+            return compute_tuple(g)
+
+        e = next(p for p in primerange(2, g + 1) if g % p == 0)
+        return compute_tuple(e)
+
+    if candidates is not None:
+        candidates = set(candidates)
+
+    # positive integer handling
+    n = as_int(n)
+
     if candidates is None and big:
         return _perfect_power(n)
 
diff --git a/sympy/ntheory/residue_ntheory.py b/sympy/ntheory/residue_ntheory.py
index d54e0b3bae..b983888d7e 100644
--- a/sympy/ntheory/residue_ntheory.py
+++ b/sympy/ntheory/residue_ntheory.py
@@ -1741,10 +1741,7 @@ def quadratic_congruence(a, b, c, n):
         return sorted((i - b) % n for i in sqrt_mod_iter(b**2 - c, n))
     res = set()
     for i in sqrt_mod_iter(b**2 - 4*a*c, 4*a*n):
-        q, rem = divmod(i - b, 2*a)
-        if rem == 0:
-            res.add(q % n)
-
+        res.update(j % n for j in linear_congruence(2*a, i - b, 4*a*n))
     return sorted(res)
 
 
diff --git a/sympy/printing/c.py b/sympy/printing/c.py
index 34c4b8f021..6fcc017776 100644
--- a/sympy/printing/c.py
+++ b/sympy/printing/c.py
@@ -47,7 +47,6 @@
     "atan2": "atan2",
     "exp": "exp",
     "log": "log",
-    "log10": "log10",
     "sinh": "sinh",
     "cosh": "cosh",
     "tanh": "tanh",
@@ -59,6 +58,7 @@
 known_functions_C99 = dict(known_functions_C89, **{
     'exp2': 'exp2',
     'expm1': 'expm1',
+    'log10': 'log10',
     'log2': 'log2',
     'log1p': 'log1p',
     'Cbrt': 'cbrt',
diff --git a/sympy/printing/printer.py b/sympy/printing/printer.py
index ec4c2eaa99..c6ddad8b83 100644
--- a/sympy/printing/printer.py
+++ b/sympy/printing/printer.py
@@ -342,47 +342,12 @@ def _as_ordered_terms(self, expr, order=None):
         order = order or self.order
 
         if order == 'old':
-            return sorted(Add.make_args(expr), key=cmp_to_key(self._compare_pretty))
+            return sorted(Add.make_args(expr), key=cmp_to_key(Basic._compare_pretty))
         elif order == 'none':
             return list(expr.args)
         else:
             return expr.as_ordered_terms(order=order)
 
-    def _compare_pretty(self, a, b):
-        """return -1, 0, 1 if a is canonically less, equal or
-        greater than b. This is used when 'order=old' is selected
-        for printing. This puts Order last, orders Rationals
-        according to value, puts terms in order wrt the power of
-        the last power appearing in a term. Ties are broken using
-        Basic.compare.
-        """
-        from sympy.core.numbers import Rational
-        from sympy.core.symbol import Wild
-        from sympy.series.order import Order
-        if isinstance(a, Order) and not isinstance(b, Order):
-            return 1
-        if not isinstance(a, Order) and isinstance(b, Order):
-            return -1
-
-        if isinstance(a, Rational) and isinstance(b, Rational):
-            l = a.p * b.q
-            r = b.p * a.q
-            return (l > r) - (l < r)
-        else:
-            p1, p2, p3 = Wild("p1"), Wild("p2"), Wild("p3")
-            r_a = a.match(p1 * p2**p3)
-            if r_a and p3 in r_a:
-                a3 = r_a[p3]
-                r_b = b.match(p1 * p2**p3)
-                if r_b and p3 in r_b:
-                    b3 = r_b[p3]
-                    c = Basic.compare(a3, b3)
-                    if c != 0:
-                        return c
-
-        # break ties
-        return Basic.compare(a, b)
-
 
 class _PrintFunction:
     """


Test patch:
diff --git a/sympy/ntheory/tests/test_factor_.py b/sympy/ntheory/tests/test_factor_.py
index b861994c99..2c934e1890 100644
--- a/sympy/ntheory/tests/test_factor_.py
+++ b/sympy/ntheory/tests/test_factor_.py
@@ -167,6 +167,39 @@ def test_perfect_power():
     # negatives and non-integer rationals
     assert perfect_power(-4) is False
     assert perfect_power(-8) == (-2, 3)
+    assert perfect_power(-S(1)/8) == (-S(1)/2, 3)
+    assert perfect_power(-5**15) == (-5, 15)
+    assert perfect_power(-5**15, big=False) == (-3125, 3)
+    assert perfect_power(-5**15, [15]) == (-5, 15)
+
+    n = -3 ** 60
+    assert perfect_power(n) == (-81, 15)
+    assert perfect_power(n, big=False) == (-3486784401, 3)
+    assert perfect_power(n, [3, 5], big=True) == (-531441, 5)
+    assert perfect_power(n, [3, 5], big=False) == (-3486784401, 3)
+    assert perfect_power(n, [2]) == False
+    assert perfect_power(n, [2, 15]) == (-81, 15)
+    assert perfect_power(n, [2, 13]) == False
+    assert perfect_power(n, [17]) == False
+    assert perfect_power(n, [3]) == (-3486784401, 3)
+    assert perfect_power(n + 1) == False
+
+    r = S(2) ** (2 * 5 * 7) / S(3) ** (2 * 7)
+    assert perfect_power(r) == (S(32) / 3, 14)
+    assert perfect_power(-r) == (-S(1024) / 9, 7)
+    assert perfect_power(r, big=False) == (S(34359738368) / 2187, 2)
+    assert perfect_power(r, [2, 5]) == (S(34359738368) / 2187, 2)
+    assert perfect_power(r, [5, 7]) == (S(1024) / 9, 7)
+    assert perfect_power(r, [5, 7], big=False) == (S(1024) / 9, 7)
+    assert perfect_power(r, [2, 5, 7], big=False) == (S(34359738368) / 2187, 2)
+    assert perfect_power(-r, [5, 7], big=False) == (-S(1024) / 9, 7)
+
+    assert perfect_power(-S(1) / 8) == (-S(1) / 2, 3)
+
+    assert perfect_power((-3)**60) == (3, 60)
+    assert perfect_power((-3)**61) == (-3, 61)
+
+    assert perfect_power(S(2 ** 9) / 3 ** 12) == (S(8)/81, 3)
     assert perfect_power(Rational(1, 2)**3) == (S.Half, 3)
     assert perfect_power(Rational(-3, 2)**3) == (-3*S.Half, 3)
 
diff --git a/sympy/ntheory/tests/test_residue.py b/sympy/ntheory/tests/test_residue.py
index fe92837a23..67ae25e61e 100644
--- a/sympy/ntheory/tests/test_residue.py
+++ b/sympy/ntheory/tests/test_residue.py
@@ -287,7 +287,6 @@ def test_residue():
     assert quadratic_congruence(5, 10, 14, 2) == [0]
     assert quadratic_congruence(10, 17, 19, 2) == [1]
     assert quadratic_congruence(10, 14, 20, 2) == [0, 1]
-    assert quadratic_congruence(2**48-7, 2**48-1, 4, 2**48) == [8249717183797, 31960993774868]
     assert polynomial_congruence(6*x**5 + 10*x**4 + 5*x**3 + x**2 + x + 1,
         972000) == [220999, 242999, 463999, 485999, 706999, 728999, 949999, 971999]
 
diff --git a/sympy/printing/tests/test_c.py b/sympy/printing/tests/test_c.py
index 626e7b6f24..bd76af28cc 100644
--- a/sympy/printing/tests/test_c.py
+++ b/sympy/printing/tests/test_c.py
@@ -625,7 +625,6 @@ def test_C89CodePrinter():
     assert c89printer.standard == 'C89'
     assert 'void' in c89printer.reserved_words
     assert 'template' not in c89printer.reserved_words
-    assert c89printer.doprint(log10(x)) == 'log10(x)'
 
 
 def test_C99CodePrinter():