diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 19978118c80dba..7e055e8ba59e59 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1694,5 +1694,13 @@ class MyInt(int): # GH-117195 -- This shouldn't crash object.__sizeof__(1) + def test_long_add_overallocate(self): + # see gh-100688 + x = (MASK//2) * (MASK+1) + x2 = (MASK//2 + 1) * (MASK+1) + z = x + x2 + self.assertEqual(x + x2, MASK * (MASK + 1)) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-01-02-14-54-12.gh-issue-100688.yenIag.rst b/Misc/NEWS.d/next/Core and Builtins/2023-01-02-14-54-12.gh-issue-100688.yenIag.rst new file mode 100644 index 00000000000000..06c11e28c61308 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-01-02-14-54-12.gh-issue-100688.yenIag.rst @@ -0,0 +1,2 @@ +Reduce frequency of overallocation in some cases of multidigit integer +additions and subtractions. Thanks Pieter Eendebak for contributions. diff --git a/Objects/longobject.c b/Objects/longobject.c index d34c8b6d71ab3f..a85790cf46af3e 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3682,6 +3682,10 @@ x_add(PyLongObject *a, PyLongObject *b) Py_ssize_t i; digit carry = 0; + /* There are fast paths for cases where a and b both have at most a single + digit, so if we end up here then at least one of them is multi-digit. */ + assert(size_a >= 2 || size_b >= 2); + /* Ensure a is the larger of the two: */ if (size_a < size_b) { { PyLongObject *temp = a; a = b; b = temp; } @@ -3689,7 +3693,15 @@ x_add(PyLongObject *a, PyLongObject *b) size_a = size_b; size_b = size_temp; } } - z = _PyLong_New(size_a+1); + + /* Allocate sufficient space for the result. In the majority of cases + we allocate exactly the right number of digits, but in the (relatively + rare) case where the sum of the topmost digits is exactly PyLong_MASK, + we'll sometimes end up overallocating by a single digit. */ + digit top_sum = a->long_value.ob_digit[size_a - 1] + + (size_b == size_a ? b->long_value.ob_digit[size_b - 1] : (digit)0); + int extra_digit = top_sum >= PyLong_MASK; + z = _PyLong_New(size_a + extra_digit); if (z == NULL) return NULL; for (i = 0; i < size_b; ++i) { @@ -3702,7 +3714,10 @@ x_add(PyLongObject *a, PyLongObject *b) z->long_value.ob_digit[i] = carry & PyLong_MASK; carry >>= PyLong_SHIFT; } - z->long_value.ob_digit[i] = carry; + assert(carry == 0 || extra_digit); + if (extra_digit) { + z->long_value.ob_digit[i] = carry; + } return long_normalize(z); }