https://github.com/python/cpython/commit/999f0c512281995fb61a0d9eda075fd846e8c505
commit: 999f0c512281995fb61a0d9eda075fd846e8c505
branch: main
author: Tim Peters <[email protected]>
committer: tim-one <[email protected]>
date: 2024-05-04T18:22:33-05:00
summary:

gh-118164: str(10**10000) hangs if the C _decimal module is missing (#118503)

* Initial stab.

* Test the tentative fix. Hangs "forever" without this change.

* Move the new test to a better spot.

* New comment to explain why _convert_to_str allows any poewr of 10.

* Fixed a comment, and fleshed out an existing test that appeared unfinished.

* Added temporary asserts. Or maybe permanent ;-)

* Update Lib/_pydecimal.py

Co-authored-by: Serhiy Storchaka <[email protected]>

* Remove the new _convert_to_str().

Serhiy and I independently concluded that exact powers of 10
aren't possible in these contexts, so just checking the
string length is sufficient.

* At least for now, add the asserts to the other block too.

* 📜🤖 Added by blurb_it.

---------

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2024-05-04-20-22-59.gh-issue-118164.9D02MQ.rst
M Lib/_pydecimal.py
M Lib/test/test_decimal.py

diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py
index de4561a5ee050b..613123ec7b4329 100644
--- a/Lib/_pydecimal.py
+++ b/Lib/_pydecimal.py
@@ -2131,10 +2131,16 @@ def _power_exact(self, other, p):
             else:
                 return None
 
-            if xc >= 10**p:
+            # An exact power of 10 is representable, but can convert to a
+            # string of any length. But an exact power of 10 shouldn't be
+            # possible at this point.
+            assert xc > 1, self
+            assert xc % 10 != 0, self
+            strxc = str(xc)
+            if len(strxc) > p:
                 return None
             xe = -e-xe
-            return _dec_from_triple(0, str(xc), xe)
+            return _dec_from_triple(0, strxc, xe)
 
         # now y is positive; find m and n such that y = m/n
         if ye >= 0:
@@ -2184,13 +2190,18 @@ def _power_exact(self, other, p):
             return None
         xc = xc**m
         xe *= m
-        if xc > 10**p:
+        # An exact power of 10 is representable, but can convert to a string
+        # of any length. But an exact power of 10 shouldn't be possible at
+        # this point.
+        assert xc > 1, self
+        assert xc % 10 != 0, self
+        str_xc = str(xc)
+        if len(str_xc) > p:
             return None
 
         # by this point the result *is* exactly representable
         # adjust the exponent to get as close as possible to the ideal
         # exponent, if necessary
-        str_xc = str(xc)
         if other._isinteger() and other._sign == 0:
             ideal_exponent = self._exp*int(other)
             zeros = min(xe-ideal_exponent, p-len(str_xc))
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 7010c34792e093..e927e24b582a5d 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -4716,9 +4716,33 @@ def test_py_exact_power(self):
 
             c.prec = 1
             x = Decimal("152587890625") ** Decimal('-0.5')
+            self.assertEqual(x, Decimal('3e-6'))
+            c.prec = 2
+            x = Decimal("152587890625") ** Decimal('-0.5')
+            self.assertEqual(x, Decimal('2.6e-6'))
+            c.prec = 3
+            x = Decimal("152587890625") ** Decimal('-0.5')
+            self.assertEqual(x, Decimal('2.56e-6'))
+            c.prec = 28
+            x = Decimal("152587890625") ** Decimal('-0.5')
+            self.assertEqual(x, Decimal('2.56e-6'))
+
             c.prec = 201
             x = Decimal(2**578) ** Decimal("-0.5")
 
+            # See https://github.com/python/cpython/issues/118027
+            # Testing for an exact power could appear to hang, in the Python
+            # version, as it attempted to compute 10**(MAX_EMAX + 1).
+            # Fixed via https://github.com/python/cpython/pull/118503.
+            c.prec = P.MAX_PREC
+            c.Emax = P.MAX_EMAX
+            c.Emin = P.MIN_EMIN
+            c.traps[P.Inexact] = 1
+            D2 = Decimal(2)
+            # If the bug is still present, the next statement won't complete.
+            res = D2 ** 117
+            self.assertEqual(res, 1 << 117)
+
     def test_py_immutability_operations(self):
         # Do operations and check that it didn't change internal objects.
         Decimal = P.Decimal
@@ -5705,7 +5729,6 @@ def test_format_fallback_rounding(self):
         with C.localcontext(rounding=C.ROUND_DOWN):
             self.assertEqual(format(y, '#.1f'), '6.0')
 
-
 @requires_docstrings
 @requires_cdecimal
 class SignatureTest(unittest.TestCase):
diff --git 
a/Misc/NEWS.d/next/Library/2024-05-04-20-22-59.gh-issue-118164.9D02MQ.rst 
b/Misc/NEWS.d/next/Library/2024-05-04-20-22-59.gh-issue-118164.9D02MQ.rst
new file mode 100644
index 00000000000000..80dc868540418f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-05-04-20-22-59.gh-issue-118164.9D02MQ.rst
@@ -0,0 +1 @@
+The Python implementation of the ``decimal`` module could appear to hang in 
relatively small power cases (like ``2**117``) if context precision was set to 
a very high value. A different method to check for exactly representable 
results is used now that doesn't rely on computing ``10**precision`` (which 
could be effectively too large to compute).

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to