From b005eb6f60f0e451a17bf4c1e7767c202187b01b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 26 Oct 2026 09:26:49 +0300 Subject: [PATCH 1/8] Cleanup calc_number_widths() and add a comment --- gmp.c | 60 ++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/gmp.c b/gmp.c index 056a2ca6..19e409e7 100644 --- a/gmp.c +++ b/gmp.c @@ -1808,41 +1808,63 @@ calc_number_widths(bool negative, size_t len, const IntFormatSpec *format, spec->sign = '-'; } } + + Py_ssize_t n = spec->n_digits; + Py_ssize_t m = format->group; + Py_ssize_t w = format->width - spec->n_sign - spec->n_prefix; + if (format->group) { + /* Compute number of grouped digits. If no zero-padding is needed, + i.e. n digits being grouped in m digits, then the number of + separators is obviously ⌊(n - 1)/m⌋. + + The tricky part is computing number of leading zeros, if required. + Let now w - requested width of output, without sign and prefix, + and n - number of digits in the number. + Then if x = ⌈(w*m + 1)/(m + 1)⌉ > n - it's the required number + of digits, including zero-padding. + + Indeed, w - x - ⌊(x - 1)/m⌋ = w - ⌊((m + 1)*x - 1)/m⌋. + Now (positive m) use identity ⌈n/m⌉ = ⌊(n - 1)/m⌋ + 1: + = w - ⌊((m+1)*⌊(w*m)/(m + 1)⌋ + m)/m⌋ = + = ⌊(m + 1)/m*{(w*m)/(m + 1)} - 1⌋. That's equal either to + 0 or -1, because -1 ≤ (m + 1)/m*{(w*m)/(m + 1)} - 1 < 1/m < 1. + + In other words, actual width of output will be either w or + one digit bigger, as in examples below: + + >>> format(123, '011_d') + '000_000_123' + >>> format(123, '012_d') + '0_000_000_123' + */ if (format->zeropad) { - spec->n_leading_zeros = Py_MAX(0, - (format->group*(format->width - - spec->n_sign - - spec->n_prefix + 1) - + 1)/(format->group + 1) - - spec->n_digits); - spec->n_digits += spec->n_leading_zeros; + spec->n_leading_zeros = Py_MAX(0, (m*(w + 1) + 1)/(m + 1) - n); + n += spec->n_leading_zeros; + spec->n_digits = n; } - spec->n_grouped_digits = spec->n_digits + (spec->n_digits - - 1)/format->group; + spec->n_grouped_digits = n + (n - 1)/m; } - + w -= spec->n_grouped_digits; + assert(!spec->n_leading_zeros || w == 0 || w == -1); /* Given the desired width and the total of digit and non-digit space we consume, see if we need any padding. format->width can be zero (meaning no padding). */ - Py_ssize_t n_padding = format->width - (spec->n_sign + spec->n_prefix - + spec->n_grouped_digits); - - if (n_padding > 0) { + if (w > 0) { switch (format->align) { case '<': - spec->n_rpadding = n_padding; + spec->n_rpadding = w; break; case '^': - spec->n_lpadding = n_padding / 2; - spec->n_rpadding = n_padding - spec->n_lpadding; + spec->n_lpadding = w / 2; + spec->n_rpadding = w - spec->n_lpadding; break; case '=': - spec->n_spadding = n_padding; + spec->n_spadding = w; break; case '>': default: - spec->n_lpadding = n_padding; + spec->n_lpadding = w; break; } } From 70b47583e6795841d962ca96333fb7117f2d1078 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 27 Oct 2026 02:09:42 +0300 Subject: [PATCH 2/8] Update GraalPy issue reference --- tests/test_mpz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index ae2b1c9e..0812c3e6 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -1005,7 +1005,7 @@ def f(n): @pytest.mark.skipif(platform.python_implementation() == "GraalVM", - reason="oracle/graalpython#593") + reason="oracle/graalpython#998") def test_int_api(): for meth in dir(int): m = getattr(int, meth) From 93d9d80ecc2e51cab190ca0fe6a963a151269388 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 27 Oct 2026 02:15:10 +0300 Subject: [PATCH 3/8] Simplify test example --- tests/test_mpz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index 0812c3e6..6e07a5ec 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -414,7 +414,7 @@ def test_mul_distributivity(x, y, z): @example(-2, 1<<64) @example(2, 1<<64) @example(18446744073709551615<<64, -1<<64) -@example(int("0x"+"f"*32, 0), -1<<64) # XXX: assuming BITS_PER_DIGIT == 64 +@example((1<<128) - 1, -1<<64) # assuming BITS_PER_DIGIT == 64 @example(-68501870735943706700000000000000000001, 10**20) # issue 117 @example(0, 123) @example(0, -1382074480823709287) From 8480c493752aab05d43bd17fc9e749af10c750af Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 27 Oct 2026 02:15:43 +0300 Subject: [PATCH 4/8] Revert ed0ac43 --- tests/test_mpz.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index 6e07a5ec..e582a3b5 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -990,7 +990,6 @@ def test_pickle(protocol, x): assert mx == pickle.loads(pickle.dumps(mx, protocol)) -@pytest.mark.skipif(platform.system() == "Darwin", reason="XXX") @settings(max_examples=100) @given(lists(integers(min_value=2), min_size=3, max_size=20)) def test_mpz_collatz(xs): From b5aab2e6546bc69cc48b4a8ad5c8af9b39fe722c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 27 Oct 2026 02:27:31 +0300 Subject: [PATCH 5/8] Drop some XXX marks --- tests/test_mpz.py | 4 ++-- utils.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index e582a3b5..20e4df77 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -211,7 +211,7 @@ def test_mpz_interface(): assert mpz("१23") == 123 assert mpz("\t123") == 123 if platform.python_implementation() != "GraalVM": - assert mpz("\xa0123") == 123 # see oracle/graalpython#1209 + assert mpz("\xa0123") == 123 # oracle/graalpython#1209 assert mpz("-010") == -10 assert mpz("-10") == -10 assert mpz("0b_10", 0) == 2 @@ -627,7 +627,7 @@ def test_power_mod(x, y, z): assert pow(mx, y, z) == r assert pow(x, my, z) == r if platform.python_implementation() == "PyPy": - return # XXX: pypy/pypy#5207 + return # pypy/pypy#5207 assert pow(x, y, mz) == r diff --git a/utils.h b/utils.h index 78ee1a1d..25227ed5 100644 --- a/utils.h +++ b/utils.h @@ -4,7 +4,7 @@ /* For GraalVM: unicodeobject.h, implicit conversion changes signedness: 'enum PyUnicode_Kind' to 'int' */ #if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic push /* XXX: oracle/graalpython#580 */ +# pragma GCC diagnostic push /* oracle/graalpython#580 */ # pragma GCC diagnostic ignored "-Wsign-conversion" #endif From b7b28f6c55748e7eb563908a12d8c7148974351b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 27 Oct 2026 02:53:16 +0300 Subject: [PATCH 6/8] No bulk-tests for negative shifts --- tests/test_mpz.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index 20e4df77..be0c300e 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -660,7 +660,7 @@ def test_power_errors(): mpz(1) << ((1 << 64) - 1) -@given(bigints(), integers(max_value=12345)) +@given(bigints(), integers(min_value=0, max_value=12345)) @example(18446744073709551618, 64) @example(1, 1<<128) @example(90605555449081991889354259339521952450308780844225461, 64) @@ -676,20 +676,13 @@ def test_lshift(x, y): x << my with pytest.raises(OverflowError): mx << y - except ValueError: - with pytest.raises(ValueError, match="negative shift count"): - mx << my - with pytest.raises(ValueError, match="negative shift count"): - x << my - with pytest.raises(ValueError, match="negative shift count"): - mx << y else: assert mx << my == r assert mx << y == r assert x << my == r -@given(bigints(), bigints()) +@given(bigints(), bigints(min_value=0)) @example(1, 1<<78) @example(-1, 1<<128) @example(-340282366920938463444927863358058659840, 64) @@ -710,19 +703,19 @@ def test_rshift(x, y): x >> my with pytest.raises(OverflowError): mx >> y - except ValueError: - with pytest.raises(ValueError, match="negative shift count"): - mx >> my - with pytest.raises(ValueError, match="negative shift count"): - x >> my - with pytest.raises(ValueError, match="negative shift count"): - mx >> y else: assert mx >> my == r assert mx >> y == r assert x >> my == r +def test_shift_errors(): + with pytest.raises(ValueError, match="negative shift count"): + mpz(123) << mpz(-123) + with pytest.raises(ValueError, match="negative shift count"): + mpz(123) >> mpz(-123) + + @given(bigints()) def test_getters(x): mx = mpz(x) From eecc5872b8580cb96a191f7d08c9ed269fd1d314 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 27 Oct 2026 02:56:52 +0300 Subject: [PATCH 7/8] Revert c97e140 --- tests/test_mpz.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_mpz.py b/tests/test_mpz.py index be0c300e..5c051cce 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -689,9 +689,6 @@ def test_lshift(x, y): @example(-514220174162876888173427869549172032807104958010493707296440352, 206) @example(-6277101735386680763495507056286727952638980837032266301441, 128) def test_rshift(x, y): - # XXX: mp_size_t might be smaller than mp_limb_t - if abs(y) >= 2**32 and platform.system() == "Windows": - return mx = mpz(x) my = mpz(y) try: From e1f710a3ccfe0812076b9f79ed10ba5c737af4c0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 27 Oct 2026 04:42:15 +0300 Subject: [PATCH 8/8] Fix test function names --- tests/test_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_functions.py b/tests/test_functions.py index 9b4f49e7..e167aab7 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -138,7 +138,7 @@ def test_lcm_nary(xs): @example(1, 9727076909039105, -48, 53, "u") @example(1, 6277101735386680763495507056286727952638980837032266301441, 0, 64, "f") -def test_mpmath_normalize(sign, man, exp, prec, rnd): +def test__mpmath_normalize(sign, man, exp, prec, rnd): mman = mpz(man) sign = int(sign) bc = mman.bit_length() @@ -152,7 +152,7 @@ def test_mpmath_normalize(sign, man, exp, prec, rnd): sampled_from(["n", "f", "c", "u", "d"])) @example(-6277101735386680763495507056286727952638980837032266301441, 0, 64, "f") -def test_mpmath_create(man, exp, prec, rnd): +def test__mpmath_create(man, exp, prec, rnd): mman = mpz(man) res = mpmath_from_man_exp(man, exp, prec, rnd) assert all(type(_) is int for _ in res)