-
-
Notifications
You must be signed in to change notification settings - Fork 29.3k
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
Comments
|
I tested it on version 3.12.3 and it worked just fine |
|
I tried on a few versions of 3.12 too (on Mac) and couldn't reproduce a hang. |
|
Did you do anything to the I can reproduce a hang if I prevent the C implementation from getting used, starting with import sys
sys.modules['_decimal'] = None
sys.set_int_max_str_digits(0)
str(10**9031) |
That's 30001 bits, where the switch happens: Lines 2055 to 2058 in 1b85b34
|
|
To be clear here, I strongly doubt that computing >>> import sys; sys.set_int_max_str_digits(0); x = 10**10000I 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, 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. |
|
It's clear from OP's stacktrace that a) it's getting hung during the str conversion, b) they have frames in |
|
Turned out to be a packaging issue (missing mpdecimal library), sorry for the noise. |
|
Thanks for following up! But don't be sorry - there's still a mystery here. Even if you don't have import sys
sys.modules['_decimal'] = None
sys.set_int_max_str_digits(0)
str(10**9031)That forces Python to ignore the
That sure looks like a mistake to me, so I'm reopening this report. In fact, I think the same line in |
|
There is other problem with using cc @mdickinson, @tim-one |
|
yes, now that |
|
this was discussed here: https://discuss.python.org/t/int-str-conversions-broken-in-latest-python-bugfix-releases/18889/174 @gpshead wrote:
|
|
Within CPython could we make it an error for |
_decimal module is missing
yes, at a minimum this seems like a good idea to me. Whether |
|
Another possiblity to address the immediate problem. This is untested, but I know the code well. In result = D2**wto result = D(1 << w)Then the decimal |
|
@serhiy-storchaka wrote: """ That's really cute 😉. If someone cares enough about 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 With PyPy in mind, it would probably be best to convert the trailing 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 But fancy schemes generally don't pay off unless the int is "very" big. |
|
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 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 |
|
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 Testing hint: set For fine-tuning (not done here), the same powers of 10 are reused often, so it can pay some to use a 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 resultWe 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 |
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 codeimport 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 It's nevertheless a massive improvement over CPython's previous quadratic-time int->str. |
|
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 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 I think that our goal is to provide an alternate implementation that does not use Decimal to break the loop between |
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 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 The goals here were better asymptotics and minimal maintenance burden. Recoding in And it's again the case that the advantage of pasting pieces together only once isn't apparent except for very large inputs - the To generate cases for testing, I use this when I want an input with hibit = 1 << (bits - 1)
lo = randrange(hibit)
n = lo + hibit
assert n.bit_length() == bitsIf 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. |
…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>
…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>
|
Closing this. The |
…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>
…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>
…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>
…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>
…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>
…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>

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.

Bug report
Bug description:
The following code
works in 3.11 but hangs in 3.12. Interrupting it gives this backtrace:
CPython versions tested on:
3.11, 3.12
Operating systems tested on:
Linux
Linked PRs
The text was updated successfully, but these errors were encountered: