Repository navigation
Description
Summary
Converting an int to a decimal string raises ValueError: Exceeds the limit (4300 digits) for integer string conversion for many ints that have exactly as many digits as the limit. CPython raises only when the result would have more digits than the limit. With the default limit of 4300, int() accepts any 4300-digit string, but str() of the result fails for every value from 2**14284 (about 8.17e4299) up, which is about the top 20% of 4300-digit values. So str(int(s)) does not round-trip for those values. With sys.set_int_max_str_digits(5000) the failures start at 2**16609 (about 6.4e4999), about 40% of 5000-digit values.
The same happens in repr(), f-strings, format(x, "d"), format(x, ","), "%d" % x and b"%d" % x, and for negative values. Smaller values with the same number of digits, like 2**14284 - 1, still convert, and so do all values with fewer digits than the limit.
The docs say: "The limit is applied to the number of digit characters in the input or output string ... When an operation would exceed the limit, a ValueError is raised".
Reproducer
import sys
print(sys.get_int_max_str_digits()) # the default limit, 4300
n = int("9" * 4300) # parsing 4300 digits is allowed
for x in (n, 2**14284 - 1, 2**14284):
try:
s = str(x)
print("ok,", len(s), "digits, starts with", s[:4])
except ValueError as e:
print("ValueError:", e)Expected
CPython 3.15.2 (same on 3.13.18 and 3.12.16):
4300
ok, 4300 digits, starts with 9999
ok, 4300 digits, starts with 8174
ok, 4300 digits, starts with 8174
Actual
RustPython main (dd8c343), same on a8bd43e and b66534d:
4300
ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit
ok, 4300 digits, starts with 8174
ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit
Cause
check_int_to_str_digits (int.rs#L831-L853), added in #7688, estimates the digit count from the bit length as bits * 30103 / 100_000 + 1 and raises whenever that estimate is over the limit. The estimate is an upper bound and is one too high for the larger values of each digit count. For example, 2**14284 has 14285 bits, so the estimate is 4301, but the value has 4300 digits. CPython also starts with a quick check, but that check only rejects values that are certainly too long. The real comparison is made against the exact length of the converted string (longobject.c#L2120-L2134, #L2207-L2217).
The value has either digits_upper or digits_upper - 1 digits, so one way to fix it is to raise right away only when digits_upper - 1 > limit. When digits_upper == limit + 1, compare exactly: raise only if abs(value) >= 10**limit. I tried this locally. It matched the exact rule (raise only when len(str(abs(x))) > limit) on 1,280 values around the boundary for limits 640, 1000, 4300, 5000 and 12345, and test_int still passes.
The existing tests do not catch this. test_max_str_digits_edge_cases only converts 10 ** (maxdigits - 1), the smallest value with that many digits, and the snippet in extra_tests/snippets/builtin_int.py uses 10**4001 (4002 digits) with a limit of 4000.
Drafted with help from Claude Code; I reviewed and checked it before posting.
Activity