X Tutup
The Wayback Machine - https://web.archive.org/web/20240624233506/https://github.com/python/cpython/issues/118164
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
antonio-rojas opened this issue Apr 22, 2024 · 22 comments
Closed

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

antonio-rojas opened this issue Apr 22, 2024 · 22 comments
Labels
3.12 bugs and security fixes 3.13 bugs and security fixes type-bug An unexpected behavior, bug, or error

Comments

@antonio-rojas
Copy link

antonio-rojas commented Apr 22, 2024

Bug report

Bug description:

The following code

>>> import sys; sys.set_int_max_str_digits(0); 10**10000

works in 3.11 but hangs in 3.12. Interrupting it gives this backtrace:

  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.12/_pylong.py", line 85, in int_to_decimal_string
    return str(int_to_decimal(n))
               ^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/_pylong.py", line 77, in int_to_decimal
    result = inner(n, n.bit_length())
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/_pylong.py", line 64, in inner
    return inner(lo, w2) + inner(hi, w - w2) * w2pow(w2)
           ^^^^^^^^^^^^^
  File "/usr/lib/python3.12/_pylong.py", line 64, in inner
    return inner(lo, w2) + inner(hi, w - w2) * w2pow(w2)
           ^^^^^^^^^^^^^
  File "/usr/lib/python3.12/_pylong.py", line 64, in inner
    return inner(lo, w2) + inner(hi, w - w2) * w2pow(w2)
           ^^^^^^^^^^^^^
  [Previous line repeated 6 more times]
  File "/usr/lib/python3.12/_pylong.py", line 45, in w2pow
    result = D2**w
             ~~^^~
  File "/usr/lib/python3.12/_pydecimal.py", line 2339, in __pow__
    ans = self._power_exact(other, context.prec + 1)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/_pydecimal.py", line 2187, in _power_exact
    if xc > 10**p:
            ~~^^~
KeyboardInterrupt

CPython versions tested on:

3.11, 3.12

Operating systems tested on:

Linux

Linked PRs

@antonio-rojas antonio-rojas added the type-bug An unexpected behavior, bug, or error label Apr 22, 2024
@JonathanHallstrom
Copy link

I tested it on version 3.12.3 and it worked just fine

@JelleZijlstra
Copy link
Member

I tried on a few versions of 3.12 too (on Mac) and couldn't reproduce a hang.

@pochmann3
Copy link
Contributor

pochmann3 commented Apr 22, 2024

Did you do anything to the decimal module, forcing the use of the Python implementation?

I can reproduce a hang if I prevent the C implementation from getting used, starting with 10**9031 (with 9030 it still only takes a split second).

import sys

sys.modules['_decimal'] = None
sys.set_int_max_str_digits(0)

str(10**9031)

@pochmann3
Copy link
Contributor

starting with 10**9031

That's 30001 bits, where the switch happens:

cpython/Objects/longobject.c

Lines 2055 to 2058 in 1b85b34

#if WITH_PYLONG_MODULE
if (size_a > 1000) {
/* Switch to _pylong.int_to_decimal_string(). */
return pylong_int_to_decimal_string(aa,

@tim-one
Copy link
Member

tim-one commented Apr 23, 2024

To be clear here, I strongly doubt that computing 10**10000 hangs for the OP either. @antonio-rojas, try this:

>>> import sys; sys.set_int_max_str_digits(0); x = 10**10000

I bet it will return near instantly. What can take considerable time is converting the result to a decimal string, which the code you showed implicitly does. Since you didn't bind the result to a name, an interactive shell will try to display the result, which requires converting it to a string first. So, after the above, str(x) may well appear to hang (but is actually just taking a long time to complete).

That "shouldn't" take such a long time either, but before anyone can really address this you'll need to be clear about where the "hang" is occurring (in the computation of the power, or in converting the result to a string - or possibly even that the shell you're using is actually the thing that's hanging when trying to display a string so long - can't guess which from here, because none of this takes noticeable time for me).

EDIT: on second reading, ya, it's clear enough already that the hang is in conversion to string - the power was already computed, and there's not yet a long string for a shell to display.

@hauntsaninja
Copy link
Contributor

It's clear from OP's stacktrace that a) it's getting hung during the str conversion, b) they have frames in _pydecimal so they aren't using the C implementation

@antonio-rojas
Copy link
Author

Turned out to be a packaging issue (missing mpdecimal library), sorry for the noise.

@tim-one
Copy link
Member

tim-one commented Apr 23, 2024

Thanks for following up! But don't be sorry - there's still a mystery here. Even if you don't have mpdecimal, core Python still ships with a pure Python implementation of that code. Did you try @pochmann3's variant?:

import sys

sys.modules['_decimal'] = None
sys.set_int_max_str_digits(0)

str(10**9031)

That forces Python to ignore the mpdecimal module. That appears to hang on my box too. I slammed in a print statement before the line in your traceback, and it displayed:

raising 10 to 1000000000000000000

That sure looks like a mistake to me, so I'm reopening this report. In fact, I think the same line in _power_exact was recently the subject of a different report (and if someone remembers which, we should probably link that report to this one, and close this again).

@tim-one tim-one reopened this Apr 23, 2024
@hauntsaninja
Copy link
Contributor

#118027

@serhiy-storchaka
Copy link
Member

There is other problem with using _pydecimal to stringify integers. When it converts a 2n-bit integer to Decimal, it recursively calculates it as D(a) + D(b)*D(2**n), where a and b are only n-bit integers. But multiplication of two large Decimals is implemented in _pydecimal via calculating str(op1.int * op2.int). If both arguments are n-bit, the result of the multiplication is 2n-bit, and we are at square one, trying to stringify a 2n-bit integers. This code falls in infinite recursion.

cc @mdickinson, @tim-one

@cfbolz
Copy link
Contributor

cfbolz commented Apr 23, 2024

yes, now that str(huge_integer) is implemented in pure Python in _pylong.py and uses the decimal module, the C implementation in _decimal is not completely optional any more.

@cfbolz
Copy link
Contributor

cfbolz commented Apr 24, 2024

this was discussed here: https://discuss.python.org/t/int-str-conversions-broken-in-latest-python-bugfix-releases/18889/174

@gpshead wrote:

_pydecimal never actually gets used by anything in CPython so it no longer working in various situations with a limit enabled was considered a non-problem as there is no scenario in which the C _decimal module is not present. _pydecimal only exists as a reference / prototyping implementation within CPython at this point.

@gpshead
Copy link
Member

gpshead commented Apr 24, 2024

Within CPython could we make it an error for _decimal to not exist rather than blindly falling back to _pydecimal at this point. Or at least require _decimal in order for the _pylong code path to be taken?

@gpshead gpshead changed the title 10**10000 hangs str(10**10000) hangs if the C _decimal module is missing Apr 24, 2024
@cfbolz
Copy link
Contributor

cfbolz commented Apr 24, 2024

Or at least require _decimal in order for the _pylong code path to be taken?

yes, at a minimum this seems like a good idea to me.

Whether _pydecimal should be removed maybe needs some wider discussion?

@tim-one
Copy link
Member

tim-one commented May 1, 2024

Another possiblity to address the immediate problem. This is untested, but I know the code well. In _pylong.py, just change

                result = D2**w

to

                result = D(1 << w)

Then the decimal __pow__() won't be called at all. It may even be faster that way under the C decimal implementation too, although not enough so to matter in this context. w in the above is always "small".

@tim-one
Copy link
Member

tim-one commented May 1, 2024

@serhiy-storchaka wrote:

"""
There is other problem with using _pydecimal to stringify integers. When it converts a 2n-bit integer to Decimal, it recursively calculates it as D(a) + D(b)*D(2**n), where a and b are only n-bit integers. But multiplication of two large Decimals is implemented in _pydecimal via calculating str(op1.int * op2.int). If both arguments are n-bit, the result of the multiplication is 2n-bit, and we are at square one, trying to stringify a 2n-bit integers. This code falls in infinite recursion.
"""

That's really cute 😉. If someone cares enough about _pydecimal.py to address this (as noted already, CPython doesn't use it for anything), probably most straightforward for it to implement its own, internal, simple int->str function, and use that instead of calling str(int). It would defer to str(int) for "small" ints, of course.

It requires little code. For "big" ints it will be quadratic-time, but it already was, and users who really care about speed weren't using _pydecimal anyway. I know PyPy uses it, but PyPy should also do a good job on speeding a simple Python-level str->int function.

With PyPy in mind, it would probably be best to convert the trailing N decimal digits remaining per iteration, via divmod(n, 10**N), where 10**N is a precomputed constant, the largest value that fits in a "1 digit" internal PyPy int.

Or it could use much larger powers of 10 in a fancier recursive scheme, relying on CPython's newer sub-quadratic division (which I suspect PyPy already implements internally) for speed. That's substantially trickier to code, though. At a high level, you pick a power P of 10 close to the square root of the int, use divmod(n, P) to break n into high and low pieces with about the same number of decimal digits, recursively convert each piece to a decimal string, then concatenate those strings (careful to pad the "low" string with leading zeros as needed).

But fancy schemes generally don't pay off unless the int is "very" big.

@serhiy-storchaka
Copy link
Member

Yes, I am not an expert in fancier schemes, but this idea is pretty straightforward. I thought about it before, but it did not work while division was quadratic.

Here is a simple implementation:

def int_to_decimal_string(n):
    DECLIM = 39
    def inner(n, w):
        if w <= DECLIM:
            return str(n)
        w2 = w >> 1
        d = 10**w2
        hi, lo = divmod(n, d)
        return inner(hi, w - w2) + inner(lo, w2).zfill(w2)
    w = int(n.bit_length() * 0.3010299956639812)
    return inner(n, w)

It is even faster than the current implementation for 10**10000 (740 usec vs 1.59 msec) and 10**100000 (20.5 msec vs 39.1 msec), but slower for 10**1000000 (721 msec vs 567 msec). They are equal for 10**530000 (266 msec).

The code above used the same threshold as the current code (translated to decimals). If increase DECLIM to 1000, this improves results (694 msec for 10**1000000), and this value looks like the optimum. Increasing BITLIM in the current code also slightly improves results.

@tim-one
Copy link
Member

tim-one commented May 1, 2024

Ya! That's in the right direction. It has bugs, though. Primarily, what's "high" at one level may be "low" to its caller. Zero-padding is always needed.

But then the number of digits needs to be known exactly in advance, so that a useless '0' isn't tacked on to the front of the very leftmost piece, and there is no dirt-cheap way to compute that.

More subtly, repeated catenation introduces an O(d log d) cost, where d is the number of digits. One way to eliminate that is shown below. Another is to extend a result bytearray in-place instead.

Testing hint: set DECLIM to 1 and compare the result to str(i) for all i in, say, range(1000000).

For fine-tuning (not done here), the same powers of 10 are reused often, so it can pay some to use a pow10 wrapper that caches the results for reuse. Which powers get reused depends on the input, though, so it's best to use a fresh cache on each call (lest the cache grow without bound - on a single call it won't hold more than O(log(n)) entries).

import math
LOG10OF2 = math.log10(2)
DECLIM = 39

def int_to_decimal_string(n):
    assert n >= 0  # precondition for this specific code
    if n < 1000000: # arbitrary - really want to get rid of n==0
        return str(n)
    pieces = []
    def inner(n, w):
        if w <= DECLIM:
            pieces.append(str(n).zfill(w))
            return
        w2 = w >> 1
        hi, lo = divmod(n, 10**w2)
        inner(hi, w - w2)
        inner(lo, w2)
    # The number of decimal digits is the smallest w such that
    # n < 10**w. There is no O(1) way to compute this.
    w = int(n.bit_length() * LOG10OF2) + 1 # upper bound
    assert n <= 10**w
    while n < 10**(w-1): # usually goes around no more than once
        w -= 1
    inner(n, w)
    assert pieces and pieces[0][0] != '0'
    result = "".join(pieces)
    assert len(result) == w
    return result

We could, e.g., instead settle for the cheap upper-bound guess at the number of digits needed, and then, at the end, strip off any leading zeroes in pieces[0] (which can happen if the guess was too large).

@tim-one
Copy link
Member

tim-one commented May 2, 2024

We could, e.g., instead settle for the cheap upper-bound guess ...

Or not worry about "upper bound" at all - just get "close". Floating-point vagaries make it too annoying to try to out-think "upper bound" precisely.

and click to see the code
import math
LOG10OF2 = math.log10(2)
DECLIM = 39

def int_to_decimal_string(n):
    assert n >= 0  # precondition for this specific code
    if n < 1000000: # arbitrary - really want to get rid of n==0
        return str(n)
    pieces = []
    def inner(n, w):
        if w <= DECLIM:
            pieces.append(str(n).zfill(w))
            return
        w2 = w >> 1
        hi, lo = divmod(n, 10**w2)
        inner(hi, w - w2)
        inner(lo, w2)
    # The number of drcimal digits is the smallest w such that n <
    # 10**w. Viewed another way, w = floor(log10(n)) + 1 if we used
    # infinite precision. There is no O(1) way to compute this.
    #
    # We don't need to be exact, though -"close" is good enough.
    # Splitting may not be optimal, but that doesn't much matter.
    # If we guess too small, no harm to the result. If we guess too
    # large, there may be leading 0's that need to be stripped.
    #
    # Where k = n.bit_length(), n < 2**k = 10**(log10(2) * k), so
    # log10(n) < log10(2) * k. Due to floating-point rounding
    # errors, though, the computed int(log10(2) * k) may be 1 too
    # small if a true result >= some int rounds to something a
    # little smaller than that int.  But it's close, and more likely
    # too large than too small (since n may be only half of 2**k).
    w = int(n.bit_length() * LOG10OF2) + 1
    inner(n, w)
    assert pieces
    result = "".join(pieces)
    return result.lstrip("0")

For those following, I explained on the linked pull request that this has significantly worse asymptotics than the code in _pylong.py, and that can't be "repaired". CPython's sub-quadratic division is limited by the asymptotics of CPython's Karatsuba multiplication, which in turn has much worse O() behavior than libmpdec's fanciest multiplication.

It's nevertheless a massive improvement over CPython's previous quadratic-time int->str.

@serhiy-storchaka
Copy link
Member

I do not think that it has bugs. Note that zeroes padding is only added for the low part.

The only bug in my initial implementation is that it worked incorrectly for negative integers. I was not aware that it is called with negative arguments.

It turned out that values that end with many zeroes like 10**100_000 are not good for testing, because the low parts are degraded to 0. 10**100_000-1 is closer to real word data. I now use the "all nines" examples for fine tuning. It lowers the upper limit where the discussed implementation is faster than the current one to 900_000 bits.

Using cache for pow10 is a good idea. I tried several variants, and the simplest one turned out to be the best (or not worst from more sophisticated code). It makes the code ~5-10% faster in the tested range.

Using the list of chunks does not affect the performance at all. BTW, your latest variant is equal to my code before adding the pow10 cache for 10**270_000-1 and 15% slower for 10**30_000-1 (the lower limit for calling it from the C code). Your initial variant is ~2 times slower.

I think that our goal is to provide an alternate implementation that does not use Decimal to break the loop between _pydecimal and _pylong. Preferably with better than quadratic complexity. It should be used in _pydecimal, and, since it turned out to be faster than the current implementation in the middle range, it could also be used in _pylong directly, even if _decimal is present. Since it is so simple, it could even be implemented in C, this can add few tens of percent to the speed and may made some more optimizations (like using the buffer for concatenation) more viable.

@tim-one
Copy link
Member

tim-one commented May 2, 2024

I do not think that it has bugs.

My apologies! You're right. I was suffering an illusion there.

For the rest, please read my comments on your PR. I don't believe there's any need to change _pydecimal after all. At import time, _pylong can pick the new code if the C version of decimal isn't installed, else use the current code.

Please don't complicate this more than necessary to fix the problem. For example, it was very deliberate that we left these simple functions coded in Python. The inputs you're testing on are relatively tiny compared to what we actually cared about - inputs so large that people are routinely tricked into thinking CPython is stuck in an infinite loop when converting a large int. There is no end to ways various ranges could be sped up. But that's not our job. People who want peak speed should be using gmpy2 instead. We're aiming here at inputs with dozens of millions of bits.

The goals here were better asymptotics and minimal maintenance burden. Recoding in C is right out. For truly large inputs, almost all the time is spent multiplying (true even of the newer code). The Python interpreter overhead is insignificant in these cases. The new version inherits O(n**1.585) asymptotics from CPython's Karatsuba multiplication. That's an enormous improvement over quadratic time, but the current code using libmpdec is more like O(n log n) (no, it's not that good, but far better than Karatsuba).

And it's again the case that the advantage of pasting pieces together only once isn't apparent except for very large inputs - the log(d) in O(d log d) increasingly matters the larger d becomes.

To generate cases for testing, I use this when I want an input with bits bits:

hibit = 1 << (bits - 1)
lo = randrange(hibit)
n = lo + hibit
assert n.bit_length() == bits

If it's any consolation, everyone gets tricked by initially testing only on powers of 10 😉. "All 9s" can trick them in a different way: there are no zero digits at all.

tim-one added a commit that referenced this issue May 4, 2024
…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 <storchaka@gmail.com>

* 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 <storchaka@gmail.com>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
serhiy-storchaka added a commit that referenced this issue May 5, 2024
…nt to str conversion (GH-118483)

For converting large ints to strings, CPython invokes a function in _pylong.py,
which uses the decimal module to implement an asymptotically waaaaay
sub-quadratic algorithm. But if the C decimal module isn't available, CPython
uses _pydecimal.py instead. Which in turn frequently does str(int). If the int
is very large, _pylong ends up doing the work, which in turn asks decimal to do
"big" arithmetic, which in turn calls str(big_int), which in turn ... it can
become infinite mutual recursion.

This change introduces a different int->str function that doesn't use decimal.
It's asymptotically worse, "Karatsuba time" instead of quadratic time, so
still a huge improvement. _pylong switches to that when the C decimal isn't
available. It is also used for not too large integers (less than 450_000 bits),
where it is faster (up to 2 times for 30_000 bits) than the asymptotically
better implementation that uses the C decimal.

Co-authored-by: Tim Peters <tim.peters@gmail.com>
@tim-one
Copy link
Member

tim-one commented May 5, 2024

Closing this. The main branch now uses a new function in _pylong.py if the C version of decimal isn't available.

#118483

@tim-one tim-one closed this as completed May 5, 2024
miss-islington pushed a commit to miss-islington/cpython that referenced this issue May 5, 2024
…sing (pythonGH-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 <storchaka@gmail.com>

* 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.

---------

(cherry picked from commit 999f0c5)

Co-authored-by: Tim Peters <tim.peters@gmail.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
serhiy-storchaka added a commit that referenced this issue May 5, 2024
…ssing (GH-118503) (GH-118584)

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

(cherry picked from commit 999f0c5)

Co-authored-by: Tim Peters <tim.peters@gmail.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue May 5, 2024
…mize int to str conversion (pythonGH-118483)

For converting large ints to strings, CPython invokes a function in _pylong.py,
which uses the decimal module to implement an asymptotically waaaaay
sub-quadratic algorithm. But if the C decimal module isn't available, CPython
uses _pydecimal.py instead. Which in turn frequently does str(int). If the int
is very large, _pylong ends up doing the work, which in turn asks decimal to do
"big" arithmetic, which in turn calls str(big_int), which in turn ... it can
become infinite mutual recursion.

This change introduces a different int->str function that doesn't use decimal.
It's asymptotically worse, "Karatsuba time" instead of quadratic time, so
still a huge improvement. _pylong switches to that when the C decimal isn't
available. It is also used for not too large integers (less than 450_000 bits),
where it is faster (up to 2 times for 30_000 bits) than the asymptotically
better implementation that uses the C decimal.

(cherry picked from commit 711c80b)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Tim Peters <tim.peters@gmail.com>
serhiy-storchaka added a commit that referenced this issue May 6, 2024
…imize int to str conversion (GH-118483) (GH-118590)

For converting large ints to strings, CPython invokes a function in _pylong.py,
which uses the decimal module to implement an asymptotically waaaaay
sub-quadratic algorithm. But if the C decimal module isn't available, CPython
uses _pydecimal.py instead. Which in turn frequently does str(int). If the int
is very large, _pylong ends up doing the work, which in turn asks decimal to do
"big" arithmetic, which in turn calls str(big_int), which in turn ... it can
become infinite mutual recursion.

This change introduces a different int->str function that doesn't use decimal.
It's asymptotically worse, "Karatsuba time" instead of quadratic time, so
still a huge improvement. _pylong switches to that when the C decimal isn't
available. It is also used for not too large integers (less than 450_000 bits),
where it is faster (up to 2 times for 30_000 bits) than the asymptotically
better implementation that uses the C decimal.

(cherry picked from commit 711c80b)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Tim Peters <tim.peters@gmail.com>
@serhiy-storchaka serhiy-storchaka added 3.12 bugs and security fixes 3.13 bugs and security fixes labels May 6, 2024
SonicField pushed a commit to SonicField/cpython that referenced this issue May 8, 2024
…sing (python#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 <storchaka@gmail.com>

* 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 <storchaka@gmail.com>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
SonicField pushed a commit to SonicField/cpython that referenced this issue May 8, 2024
…mize int to str conversion (pythonGH-118483)

For converting large ints to strings, CPython invokes a function in _pylong.py,
which uses the decimal module to implement an asymptotically waaaaay
sub-quadratic algorithm. But if the C decimal module isn't available, CPython
uses _pydecimal.py instead. Which in turn frequently does str(int). If the int
is very large, _pylong ends up doing the work, which in turn asks decimal to do
"big" arithmetic, which in turn calls str(big_int), which in turn ... it can
become infinite mutual recursion.

This change introduces a different int->str function that doesn't use decimal.
It's asymptotically worse, "Karatsuba time" instead of quadratic time, so
still a huge improvement. _pylong switches to that when the C decimal isn't
available. It is also used for not too large integers (less than 450_000 bits),
where it is faster (up to 2 times for 30_000 bits) than the asymptotically
better implementation that uses the C decimal.

Co-authored-by: Tim Peters <tim.peters@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.12 bugs and security fixes 3.13 bugs and security fixes type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

9 participants
X Tutup