From 58cc126843e347fc3b9a7f16ce6507e39f47138d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Nov 2026 05:38:29 +0300 Subject: [PATCH 01/17] Drop mpz.h --- gmp.c | 13 ++++++++++++- mpz.h | 18 ------------------ utils.h | 4 +--- 3 files changed, 13 insertions(+), 22 deletions(-) delete mode 100644 mpz.h diff --git a/gmp.c b/gmp.c index 19e409e7..e08295d4 100644 --- a/gmp.c +++ b/gmp.c @@ -1,10 +1,21 @@ -#include "mpz.h" +#include "utils.h" #include "extra.h" #ifdef Py_GIL_DISABLED # include #endif +typedef struct { + PyObject_HEAD + Py_hash_t hash; + zz_t z; +} MPZ_Object; + +extern PyTypeObject MPZ_Type; + +#define MPZ_CheckExact(u) Py_IS_TYPE((u), &MPZ_Type) +#define MPZ_Check(u) PyObject_TypeCheck((u), &MPZ_Type) + #ifdef ON_CPYTHON # define MAX_FREELIST_SIZE 100 # define MAX_FREELIST_SIZEOF 256 diff --git a/mpz.h b/mpz.h deleted file mode 100644 index 91714ce6..00000000 --- a/mpz.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef MPZ_H -#define MPZ_H - -#include "utils.h" -#include "zz/zz.h" - -typedef struct { - PyObject_HEAD - Py_hash_t hash; - zz_t z; -} MPZ_Object; - -extern PyTypeObject MPZ_Type; - -#define MPZ_CheckExact(u) Py_IS_TYPE((u), &MPZ_Type) -#define MPZ_Check(u) PyObject_TypeCheck((u), &MPZ_Type) - -#endif /* MPZ_H */ diff --git a/utils.h b/utils.h index 25227ed5..0be36672 100644 --- a/utils.h +++ b/utils.h @@ -8,10 +8,8 @@ # pragma GCC diagnostic ignored "-Wsign-conversion" #endif -#include "pythoncapi_compat.h" - #define PY_SSIZE_T_CLEAN -#include +#include "pythoncapi_compat.h" #if defined(__GNUC__) || defined(__clang__) # pragma GCC diagnostic pop From 880ad7c53b610de234024ed5b09f1fcaf7a9e7a7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Nov 2026 09:49:51 +0300 Subject: [PATCH 02/17] Cleanup CFLAGS (see meson.build) --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e5cbc908..be82e759 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,7 +5,7 @@ jobs: timeout-minutes: 20 runs-on: ${{ matrix.os }} env: - CFLAGS: -Wall -Werror -std=c17 -Wconversion + CFLAGS: -Wall -Werror -Wconversion strategy: matrix: os: [ubuntu-26.04] From e1f36c52649beed7a4705cdb8fdf32f2aadd1770 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Nov 2026 09:52:39 +0300 Subject: [PATCH 03/17] Unfreeze hypothesis (partial revert of 0fa0926) --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 093d8d09..7953934a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,8 +44,7 @@ Homepage = "https://github.com/diofant/python-gmp" Documentation = "https://python-gmp.readthedocs.io/en/latest/" [project.optional-dependencies] -tests = ["pytest", "hypothesis; implementation_name != 'pypy'", - "hypothesis<=6.150.0; implementation_name == 'pypy'"] +tests = ["pytest", "hypothesis"] ci = ["python-gmp[tests]", "pytest-xdist"] develop = ["python-gmp[tests]", "prek", "pyperf"] From fdc542e87b685cdf700f31421fe610bba0ac3be2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 11 Nov 2026 02:31:21 +0300 Subject: [PATCH 04/17] Amend afad06b --- tests/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index b52c8329..7ea79434 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -15,7 +15,6 @@ BITS_PER_DIGIT = mpz_info.bits_per_digit SIZEOF_DIGIT = mpz_info.sizeof_digit BITCNT_MAX = mpz_info.bitcnt_max -MAX_FACTORIAL_CACHE = 1000 def python_gcdext(a, b): From f3f22e5cf137e42f7bbb2384b03c70ca42715e86 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 11 Nov 2026 08:19:13 +0300 Subject: [PATCH 05/17] Drop unused argument from new_impl() --- gmp.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/gmp.c b/gmp.c index e08295d4..e9c06124 100644 --- a/gmp.c +++ b/gmp.c @@ -385,7 +385,7 @@ MPZ_from_bytes(PyObject *obj, int is_little, int is_signed) } Py_LOCAL(PyObject *) -new_impl(PyTypeObject *Py_UNUSED(type), PyObject *arg, PyObject *base_arg) +new_impl(PyObject *arg, PyObject *base_arg) { int base = 10; @@ -521,14 +521,14 @@ new(PyTypeObject *type, PyObject *args, PyObject *keywds) } if (argc == 1 && !keywds) { arg = PyTuple_GetItem(args, 0); - return new_impl(type, arg, Py_None); + return new_impl(arg, Py_None); } if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O", kwlist, &arg, &base)) { return NULL; } - return new_impl(type, arg, base); + return new_impl(arg, base); } Py_LOCAL(void) @@ -561,7 +561,7 @@ dealloc(PyObject *self) } Py_LOCAL(PyObject *) -vectorcall(PyObject *type, PyObject *const *args, size_t nargsf, +vectorcall(PyObject *Py_UNUSED(type), PyObject *const *args, size_t nargsf, PyObject *kwnames) { Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); @@ -579,11 +579,10 @@ vectorcall(PyObject *type, PyObject *const *args, size_t nargsf, return NULL; } if (argidx[1] >= 0) { - return new_impl((PyTypeObject *)type, args[argidx[0]], - args[argidx[1]]); + return new_impl(args[argidx[0]], args[argidx[1]]); } else if (argidx[0] >= 0) { - return new_impl((PyTypeObject *)type, args[argidx[0]], Py_None); + return new_impl(args[argidx[0]], Py_None); } else { return (PyObject *)MPZ_new(); From e1d765aed25e0b74e1e9e14a53901c30fc62b88d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 11 Nov 2026 08:24:14 +0300 Subject: [PATCH 06/17] Require Python >= 3.12 --- README.rst | 6 +++--- pyproject.toml | 3 +-- tests/test_mpz.py | 5 ++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 8e3a59c4..05573491 100644 --- a/README.rst +++ b/README.rst @@ -10,9 +10,9 @@ integer type (`mpz`_), compatible with Python's `int`_. It also includes functions, compatible with the Python stdlib's submodule `math.integer `_. -This module requires Python 3.11 or later versions and has been tested with -CPython 3.11 through 3.15, with PyPy3.12 8.0.0 and with GraalPy 25.3.4.1. -Free-threading builds of the CPython are supported. +This module requires Python 3.12 or later versions and has been tested with +CPython 3.12 through 3.15, with PyPy3.12 and with GraalPy3.13. Free-threading +builds of the CPython are supported. Releases are available in the Python Package Index (PyPI) at https://pypi.org/project/python-gmp/. diff --git a/pyproject.toml b/pyproject.toml index 7953934a..35583489 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ classifiers = ["Development Status :: 5 - Production/Stable", "Programming Language :: C", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", @@ -35,7 +34,7 @@ classifiers = ["Development Status :: 5 - Production/Stable", "Operating System :: MacOS", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Software Development :: Libraries :: Python Modules"] -requires-python = ">= 3.11" +requires-python = ">= 3.12" [project.urls] Homepage = "https://github.com/diofant/python-gmp" diff --git a/tests/test_mpz.py b/tests/test_mpz.py index 5c051cce..e8600f81 100644 --- a/tests/test_mpz.py +++ b/tests/test_mpz.py @@ -723,9 +723,8 @@ def test_getters(x): @given(bigints()) def test_methods(x): mx = mpz(x) - methods = ["conjugate", "bit_length", "as_integer_ratio", "bit_count"] - if sys.version_info >= (3, 12): - methods.append("is_integer") + methods = ["conjugate", "bit_length", "as_integer_ratio", "bit_count", + "is_integer"] for name in methods: meth = operator.methodcaller(name) assert meth(mx) == meth(x) From daea446b4f61ac4a12756ecfa5b5cff52fdcb257 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 12 Nov 2026 03:18:50 +0300 Subject: [PATCH 07/17] Drop gmpy2 version from example --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index 05573491..1178ff6f 100644 --- a/README.rst +++ b/README.rst @@ -36,8 +36,6 @@ will work if you set address space limit for the Python interpreter (e.g. by .. code:: pycon >>> import gmpy2 - >>> gmpy2.__version__ - '2.2.1' >>> z = gmpy2.mpz(29925959575501) >>> while True: # this loop will crash interpter ... z = z*z From 502f072829d56d360ff3e9624538b707f4f51cc9 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 12 Nov 2026 03:19:39 +0300 Subject: [PATCH 08/17] Update .pre-commit-config.yaml --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 15a85611..8d3d86b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-yaml - id: check-toml @@ -10,13 +10,13 @@ repos: args: [--pytest-test-first] exclude: ^tests/utils.py$ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.3 + rev: v0.17.4 hooks: - id: ruff name: Run ruff args: [--exit-non-zero-on-fix] - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v1.0.0 + rev: v1.0.2 hooks: - id: sphinx-lint name: Run sphinx-lint From 880d23188a3fbdbe7ec70c237b8e4f767243f559 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 12 Nov 2026 04:43:21 +0300 Subject: [PATCH 09/17] Skip gp312* --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 35583489..b7366a21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ select = ["E", "F", "I", "PT", "W", "Q", "SIM", "B", "UP", "RUF"] [tool.cibuildwheel] build-frontend = {name="build", args=["--verbose", "-Csetup-args=--vsenv"]} enable = "pypy cpython-prerelease graalpy" -skip = "gp3*win*amd64* gp312_250-macos*" +skip = "gp3*win*amd64* gp312*" before-all = "sh scripts/cibw_before_all.sh" test-extras = "ci" From b6c72173577ea4831c483296c3ebb0df9406cf45 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 12 Nov 2026 07:16:16 +0300 Subject: [PATCH 10/17] Sync keywords with GH --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7366a21..627a564f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = ["meson-python"] name = "python-gmp" dynamic = ["version"] description = "Safe bindings to the GNU GMP library" -keywords = ["gmp", "multiple-precision", "arbitrary-precision", "bignum"] +keywords = ["gmp", "multiple-precision", "arbitrary-precision", "bigint"] license = "MIT" license-files = ["LICENSE"] readme = "README.rst" From 2cd3f4630a8e13fdd08ac1b8dd564b44f037708a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 12 Nov 2026 07:37:43 +0300 Subject: [PATCH 11/17] Enable cache for actions/setup-python --- .github/workflows/coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index be82e759..90394808 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,6 +18,7 @@ jobs: with: python-version: ${{ matrix.python-version }} allow-prereleases: true + cache: 'pip' - run: sudo apt-get update - run: sudo apt-get install autoconf-archive lcov - name: Setup locales From 077b55833cbe12c68e63dfe09688ec3404ee31cf Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 12 Nov 2026 08:42:34 +0300 Subject: [PATCH 12/17] Enable coverage tests on CPython 3.16 --- .github/workflows/coverage.yml | 2 +- README.rst | 2 +- pyproject.toml | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 90394808..202e2405 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-26.04] - python-version: [pypy3.12, "graalpy-25", "3.x"] + python-version: [pypy3.12, "graalpy-25", "3.x", 3.16] steps: - uses: actions/checkout@v7 with: diff --git a/README.rst b/README.rst index 1178ff6f..bbab3374 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ functions, compatible with the Python stdlib's submodule `math.integer `_. This module requires Python 3.12 or later versions and has been tested with -CPython 3.12 through 3.15, with PyPy3.12 and with GraalPy3.13. Free-threading +CPython 3.12 through 3.16, with PyPy3.12 and with GraalPy3.13. Free-threading builds of the CPython are supported. Releases are available in the Python Package Index (PyPI) at diff --git a/pyproject.toml b/pyproject.toml index 627a564f..7bfb2ca3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = ["Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.15", + "Programming Language :: Python :: 3.16", "Programming Language :: Python :: Free Threading :: 3 - Stable", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", From 1c342a7dabcfca60c9f513cbcc394fceb01f4919 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 13 Nov 2026 03:24:29 +0300 Subject: [PATCH 13/17] Add sphinx to develop deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7bfb2ca3..611815f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ Documentation = "https://python-gmp.readthedocs.io/en/latest/" [project.optional-dependencies] tests = ["pytest", "hypothesis"] ci = ["python-gmp[tests]", "pytest-xdist"] -develop = ["python-gmp[tests]", "prek", "pyperf"] +develop = ["python-gmp[tests]", "prek", "pyperf", "sphinx"] [tool.ruff] line-length = 79 From aba5259e4d6f813a6472aec864a1f1eebd6810ec Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 13 Nov 2026 03:32:53 +0300 Subject: [PATCH 14/17] Note about integer length limitation --- gmp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gmp.c b/gmp.c index e9c06124..1014c995 100644 --- a/gmp.c +++ b/gmp.c @@ -2817,7 +2817,8 @@ static struct PyModuleDef gmp_module = { PyModuleDef_HEAD_INIT, .m_name = "gmp", .m_doc = "Bindings to the GNU GMP for Python, providing int-compatible\n\ -integer type and functions as in the math.integer submodule.", +integer type and functions as in the math.integer submodule.\n\n\ +This module has no integer string conversion length limitation.", .m_size = 0, .m_methods = gmp_functions, .m_slots = gmp_slots, From 399f0930545b5c84c7bda88dc7d2dc6d5d9a92d1 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 13 Nov 2026 04:18:25 +0300 Subject: [PATCH 15/17] More consistent naming for parameters/variables, fix formatting --- extra.c | 42 ++++++++-------- extra.h | 8 +-- gmp.c | 149 ++++++++++++++++++++++++++++---------------------------- 3 files changed, 98 insertions(+), 101 deletions(-) diff --git a/extra.c b/extra.c index 2afb33b5..7cab482f 100644 --- a/extra.c +++ b/extra.c @@ -8,8 +8,7 @@ static const zz_layout bytes_layout = {8, 1, 1, 0}; zz_err -zz_get_bytes(const zz_t *u, size_t length, bool is_signed, - unsigned char **buffer) +zz_get_bytes(const zz_t *u, size_t len, bool is_signed, unsigned char **buf) { zz_t tmp; bool is_negative = zz_isneg(u); @@ -21,7 +20,7 @@ zz_get_bytes(const zz_t *u, size_t length, bool is_signed, if (!is_signed) { return ZZ_BUF; } - if (zz_set(1, &tmp) || zz_mul_2exp(&tmp, 8*length, &tmp) + if (zz_set(1, &tmp) || zz_mul_2exp(&tmp, 8*len, &tmp) || zz_add(&tmp, u, &tmp)) { /* LCOV_EXCL_START */ @@ -34,43 +33,42 @@ zz_get_bytes(const zz_t *u, size_t length, bool is_signed, size_t nbits = zz_bitlen(u); - if (zz_isneg(u) || nbits > 8*length + if (zz_isneg(u) || nbits > 8*len || (is_signed && ((!nbits && is_negative) - || (nbits && (nbits == 8 * length ? !is_negative : is_negative))))) + || (nbits && (nbits == 8 * len ? !is_negative : is_negative))))) { zz_clear(&tmp); return ZZ_BUF; } - size_t gap = length - (nbits + bits_per_digit/8 - 1)/(bits_per_digit/8); + size_t gap = len - (nbits + bits_per_digit/8 - 1)/(bits_per_digit/8); - (void)zz_export(u, bytes_layout, length - gap, *buffer + gap); - memset(*buffer, is_negative ? 0xFF : 0, gap); + (void)zz_export(u, bytes_layout, len - gap, *buf + gap); + memset(*buf, is_negative ? 0xFF : 0, gap); zz_clear(&tmp); return ZZ_OK; } zz_err -zz_set_bytes(const unsigned char *buffer, size_t length, bool is_signed, - zz_t *u) +zz_set_bytes(const unsigned char *buf, size_t len, bool is_signed, zz_t *u) { - if (!length) { + if (!len) { return zz_set(0, u); } - zz_err ret = zz_import(length, buffer, bytes_layout, u); + zz_err ret = zz_import(len, buf, bytes_layout, u); if (ret) { return ret; /* LCOV_EXCL_LINE */ } (void)zz_abs(u, u); - if (is_signed && zz_bitlen(u) == 8*(size_t)length) { + if (is_signed && zz_bitlen(u) == 8*(size_t)len) { zz_t tmp; /* we assume, that bytes were created by zz_get_bytes(), else there could be overflow in zz_mul_2exp() */ if (zz_init(&tmp) || zz_set(1, &tmp) - || zz_mul_2exp(&tmp, 8*length, &tmp) + || zz_mul_2exp(&tmp, 8*len, &tmp) || zz_sub(&tmp, u, u) || zz_neg(u, u)) { /* LCOV_EXCL_START */ @@ -150,13 +148,13 @@ zz_divnear(const zz_t *u, const zz_t *v, zz_t *q, zz_t *r) } zz_err -zz_truediv(const zz_t *u, const zz_t *v, double *res) +zz_truediv(const zz_t *u, const zz_t *v, double *w) { if (zz_iszero(v)) { return ZZ_VAL; } if (zz_iszero(u)) { - *res = zz_isneg(v) ? -0.0 : 0.0; + *w = zz_isneg(v) ? -0.0 : 0.0; return ZZ_OK; } @@ -167,7 +165,7 @@ zz_truediv(const zz_t *u, const zz_t *v, double *res) return ZZ_BUF; } if (ubits < vbits && vbits - ubits > -DBL_MIN_EXP + DBL_MANT_DIG + 1) { - *res = zz_isneg(u) != zz_isneg(v) ? -0.0 : 0.0; + *w = zz_isneg(u) != zz_isneg(v) ? -0.0 : 0.0; return ZZ_OK; } @@ -241,13 +239,13 @@ zz_truediv(const zz_t *u, const zz_t *v, double *res) /* LCOV_EXCL_STOP */ } zz_clear(&b); - (void)zz_get(&a, res); + (void)zz_get(&a, w); zz_clear(&a); - *res = ldexp(*res, -shift); + *w = ldexp(*w, -shift); if (zz_isneg(u) != zz_isneg(v)) { - *res = -*res; + *w = -*w; } - if (isinf(*res)) { + if (isinf(*w)) { return ZZ_BUF; } return ZZ_OK; @@ -285,7 +283,7 @@ zz_rshift(const zz_t *u, const zz_t *v, zz_t *w) zz_err zz_mpmath_normalize(zz_bitcnt_t prec, zz_rnd rnd, bool *negative, - zz_t *man, zz_t *exp, zz_bitcnt_t *bc) + zz_t *man, zz_t *exp, zz_bitcnt_t *bc) { /* If the mantissa is 0, return the normalized representation. */ if (zz_iszero(man)) { diff --git a/extra.h b/extra.h index cbd90457..eba1b89e 100644 --- a/extra.h +++ b/extra.h @@ -5,17 +5,17 @@ extern uint8_t bits_per_digit; -zz_err zz_get_bytes(const zz_t *u, size_t length, bool is_signed, - unsigned char **buffer); -zz_err zz_set_bytes(const unsigned char *buffer, size_t length, +zz_err zz_get_bytes(const zz_t *u, size_t len, bool is_signed, + unsigned char **buf); +zz_err zz_set_bytes(const unsigned char *buf, size_t len, bool is_signed, zz_t *u); #define zz_quo(u, v, w) zz_div((u), (v), (w), NULL) #define zz_rem(u, v, w) zz_div((u), (v), NULL, (w)) zz_err zz_divnear(const zz_t *u, const zz_t *v, zz_t *q, zz_t *r); -zz_err zz_truediv(const zz_t *u, const zz_t *v, double *res); +zz_err zz_truediv(const zz_t *u, const zz_t *v, double *w); zz_err zz_lshift(const zz_t *u, const zz_t *v, zz_t *w); zz_err zz_rshift(const zz_t *u, const zz_t *v, zz_t *w); diff --git a/gmp.c b/gmp.c index 1014c995..fc89b15b 100644 --- a/gmp.c +++ b/gmp.c @@ -282,20 +282,20 @@ revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r) } Py_LOCAL(PyObject *) -MPZ_to_bytes(MPZ_Object *u, Py_ssize_t length, int is_little, int is_signed) +MPZ_to_bytes(MPZ_Object *u, Py_ssize_t len, int is_little, int is_signed) { - PyObject *bytes = PyBytes_FromStringAndSize(NULL, length); + PyObject *bytes = PyBytes_FromStringAndSize(NULL, len); if (!bytes) { return NULL; /* LCOV_EXCL_LINE */ } - unsigned char *buffer = (unsigned char *)PyBytes_AsString(bytes); - zz_err ret = zz_get_bytes(&u->z, (size_t)length, is_signed, &buffer); + unsigned char *buf = (unsigned char *)PyBytes_AsString(bytes); + zz_err ret = zz_get_bytes(&u->z, (size_t)len, is_signed, &buf); if (ret == ZZ_OK) { - if (is_little && length) { - revstr(buffer, 0, length - 1); + if (is_little && len) { + revstr(buf, 0, len - 1); } return bytes; } @@ -307,7 +307,7 @@ MPZ_to_bytes(MPZ_Object *u, Py_ssize_t length, int is_little, int is_signed) else { #if (PY_VERSION_HEX < 0x030D08F0 || (PY_VERSION_HEX >= 0x030E0000 \ && PY_VERSION_HEX < 0x030E00C3)) - if (!length && zz_cmp(&u->z, -1) == ZZ_EQ) { + if (!len && zz_cmp(&u->z, -1) == ZZ_EQ) { return bytes; } #endif @@ -326,20 +326,20 @@ Py_LOCAL(MPZ_Object *) MPZ_from_bytes(PyObject *obj, int is_little, int is_signed) { PyObject *bytes = PyObject_Bytes(obj); - unsigned char *buffer; - Py_ssize_t length; + unsigned char *buf; + Py_ssize_t len; if (bytes == NULL) { return NULL; } - if (PyBytes_AsStringAndSize(bytes, (char **)&buffer, &length) == -1) { + if (PyBytes_AsStringAndSize(bytes, (char **)&buf, &len) == -1) { return NULL; /* LCOV_EXCL_LINE */ } - if (!length) { + if (!len) { is_little = 0; } if (is_little) { - unsigned char *tmp = malloc((size_t)length); + unsigned char *tmp = malloc((size_t)len); if (!tmp) { /* LCOV_EXCL_START */ @@ -347,9 +347,9 @@ MPZ_from_bytes(PyObject *obj, int is_little, int is_signed) return (MPZ_Object *)PyErr_NoMemory(); /* LCOV_EXCL_STOP */ } - memcpy(tmp, buffer, (size_t)length); - revstr(tmp, 0, length - 1); - buffer = tmp; + memcpy(tmp, buf, (size_t)len); + revstr(tmp, 0, len - 1); + buf = tmp; } MPZ_Object *res = MPZ_new(); @@ -358,17 +358,17 @@ MPZ_from_bytes(PyObject *obj, int is_little, int is_signed) /* LCOV_EXCL_START */ Py_DECREF(bytes); if (is_little) { - free(buffer); + free(buf); } return NULL; /* LCOV_EXCL_STOP */ } - zz_err ret = zz_set_bytes(buffer, (size_t)length, is_signed, &res->z); + zz_err ret = zz_set_bytes(buf, (size_t)len, is_signed, &res->z); Py_DECREF(bytes); if (is_little) { - free(buffer); + free(buf); } if (ret) { /* LCOV_EXCL_START */ @@ -1076,14 +1076,14 @@ BINOP_INT(lshift) BINOP_INT(rshift) Py_LOCAL(PyObject *) -power(PyObject *self, PyObject *other, PyObject *module) +power(PyObject *self, PyObject *other, PyObject *modulus) { MPZ_Object *res = NULL; MPZ_Object *u = NULL, *v = NULL; CHECK_OP(u, self); CHECK_OP(v, other); - if (Py_IsNone(module)) { + if (Py_IsNone(modulus)) { if (zz_isneg(&v->z)) { PyObject *uf, *vf, *resf; @@ -1138,7 +1138,7 @@ power(PyObject *self, PyObject *other, PyObject *module) else { MPZ_Object *w = NULL; - CHECK_OP_INT(w, module); + CHECK_OP_INT(w, modulus); zz_err ret = ZZ_OK; @@ -1223,13 +1223,13 @@ static PyNumberMethods as_number = { }; Py_LOCAL_INLINE(PyObject *) -get_copy(PyObject *self, void *Py_UNUSED(closure)) +get_copy(PyObject *self, void *Py_UNUSED(args)) { return Py_NewRef(self); } Py_LOCAL(PyObject *) -get_one(PyObject *Py_UNUSED(self), void *Py_UNUSED(closure)) +get_one(PyObject *Py_UNUSED(self), void *Py_UNUSED(args)) { MPZ_Object *res = MPZ_new(); @@ -1240,7 +1240,7 @@ get_one(PyObject *Py_UNUSED(self), void *Py_UNUSED(closure)) } Py_LOCAL_INLINE(PyObject *) -get_zero(PyObject *Py_UNUSED(self), void *Py_UNUSED(closure)) +get_zero(PyObject *Py_UNUSED(self), void *Py_UNUSED(args)) { return (PyObject *)MPZ_new(); } @@ -1347,9 +1347,9 @@ to_bytes(PyObject *self, PyObject *const *args, Py_ssize_t nargs, } Py_LOCAL(PyObject *) -_from_bytes(PyObject *Py_UNUSED(type), PyObject *arg) +_from_bytes(PyObject *Py_UNUSED(type), PyObject *bytes) { - return (PyObject *)MPZ_from_bytes(arg, 0, 1); + return (PyObject *)MPZ_from_bytes(bytes, 0, 1); } Py_LOCAL(PyObject *) @@ -1512,7 +1512,7 @@ __round__(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } Py_LOCAL(PyObject *) -__reduce_ex__(PyObject *self, PyObject *Py_UNUSED(args)) +__reduce_ex__(PyObject *self, PyObject *Py_UNUSED(protocol)) { MPZ_Object *u = (MPZ_Object *)self; zz_bitcnt_t len = zz_bitlen(&u->z); @@ -1523,7 +1523,7 @@ __reduce_ex__(PyObject *self, PyObject *Py_UNUSED(args)) } Py_LOCAL(PyObject *) -__sizeof__(PyObject *self, PyObject *Py_UNUSED(ignored)) +__sizeof__(PyObject *self, PyObject *Py_UNUSED(args)) { MPZ_Object *u = (MPZ_Object *)self; @@ -2092,12 +2092,12 @@ The signed argument indicates whether two’s complement is used."}, ("digits($self, base=10)\n--\n\n" "Return string representing self in the given base.\n\n" "Values for base can range between 2 to 36.")}, - {"_from_bytes", _from_bytes, METH_O | METH_CLASS, NULL}, + {"_from_bytes", _from_bytes, METH_O | METH_CLASS, + "_from_bytes($type, bytes, /)\n--\n\nReconstruct an integer from bytes."}, {NULL} /* sentinel */ }; -PyDoc_STRVAR(mpz_doc, - "mpz(number=0, /)\nmpz(string, /, base=10)\n\n\ +PyDoc_STRVAR(mpz_doc, "mpz(number=0, /)\nmpz(string, /, base=10)\n\n\ Convert a number or a string to an integer. If numeric argument is not\n\ an int subclass, return same as mpz(int(number)).\n\n\ If argument is not a number or if base is given, then it must be a string,\n\ @@ -2156,14 +2156,13 @@ gmp_gcd(PyObject *Py_UNUSED(module), PyObject *const *args, Py_ssize_t nargs) } Py_LOCAL(PyObject *) -gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args, - Py_ssize_t nargs) +gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args, Py_ssize_t nargs) { if (nargs != 2) { PyErr_SetString(PyExc_TypeError, "gcdext() expects two arguments"); return NULL; } - MPZ_Object *x = NULL, *y = NULL; + MPZ_Object *u = NULL, *v = NULL; MPZ_Object *g = MPZ_new(), *s = MPZ_new(), *t = MPZ_new(); if (!g || !s || !t) { @@ -2174,13 +2173,13 @@ gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args, return PyErr_NoMemory(); /* LCOV_EXCL_STOP */ } - CHECK_OP_INT(x, args[0]); - CHECK_OP_INT(y, args[1]); + CHECK_OP_INT(u, args[0]); + CHECK_OP_INT(v, args[1]); - zz_err ret = zz_gcdext(&x->z, &y->z, &g->z, &s->z, &t->z); + zz_err ret = zz_gcdext(&u->z, &v->z, &g->z, &s->z, &t->z); - Py_DECREF(x); - Py_DECREF(y); + Py_DECREF(u); + Py_DECREF(v); if (ret == ZZ_MEM) { return PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } @@ -2194,8 +2193,8 @@ gmp_gcdext(PyObject *Py_UNUSED(module), PyObject *const *args, Py_DECREF(g); Py_DECREF(s); Py_DECREF(t); - Py_XDECREF((PyObject *)x); - Py_XDECREF((PyObject *)y); + Py_XDECREF((PyObject *)u); + Py_XDECREF((PyObject *)v); return NULL; } @@ -2239,18 +2238,18 @@ gmp_lcm(PyObject *Py_UNUSED(module), PyObject *const *args, Py_ssize_t nargs) } Py_LOCAL(PyObject *) -gmp_isqrt(PyObject *Py_UNUSED(module), PyObject *arg) +gmp_isqrt(PyObject *Py_UNUSED(module), PyObject *n) { - MPZ_Object *x, *root = MPZ_new(); + MPZ_Object *u, *root = MPZ_new(); if (!root) { return NULL; /* LCOV_EXCL_LINE */ } - CHECK_OP_INT(x, arg); + CHECK_OP_INT(u, n); - zz_err ret = zz_sqrtrem(&x->z, &root->z, NULL); + zz_err ret = zz_sqrtrem(&u->z, &root->z, NULL); - Py_DECREF(x); + Py_DECREF(u); if (ret == ZZ_OK) { return (PyObject *)root; } @@ -2267,9 +2266,9 @@ gmp_isqrt(PyObject *Py_UNUSED(module), PyObject *arg) } Py_LOCAL(PyObject *) -gmp_isqrt_rem(PyObject *Py_UNUSED(module), PyObject *arg) +gmp_isqrt_rem(PyObject *Py_UNUSED(module), PyObject *n) { - MPZ_Object *x, *root = MPZ_new(), *rem = MPZ_new(); + MPZ_Object *u, *root = MPZ_new(), *rem = MPZ_new(); PyObject *tup = NULL; if (!root || !rem) { @@ -2279,11 +2278,11 @@ gmp_isqrt_rem(PyObject *Py_UNUSED(module), PyObject *arg) return NULL; /* LCOV_EXCL_STOP */ } - CHECK_OP_INT(x, arg); + CHECK_OP_INT(u, n); - zz_err ret = zz_sqrtrem(&x->z, &root->z, &rem->z); + zz_err ret = zz_sqrtrem(&u->z, &root->z, &rem->z); - Py_DECREF(x); + Py_DECREF(u); if (ret == ZZ_OK) { tup = PyTuple_Pack(2, root, rem); } @@ -2301,32 +2300,32 @@ gmp_isqrt_rem(PyObject *Py_UNUSED(module), PyObject *arg) } Py_LOCAL(PyObject *) -gmp_fac(PyObject *Py_UNUSED(module), PyObject *arg) +gmp_fac(PyObject *Py_UNUSED(module), PyObject *n) { - MPZ_Object *x, *res = MPZ_new(); + MPZ_Object *u, *res = MPZ_new(); if (!res) { return NULL; /* LCOV_EXCL_LINE */ } - CHECK_OP_INT(x, arg); - if (zz_isneg(&x->z)) { + CHECK_OP_INT(u, n); + if (zz_isneg(&u->z)) { PyErr_SetString(PyExc_ValueError, "fac() not defined for negative values"); goto err; } - uint64_t n; + uint64_t x; - if (zz_get(&x->z, &n)) { + if (zz_get(&u->z, &x)) { overflow: PyErr_Format(PyExc_OverflowError, "fac() argument should not exceed %ld", ULONG_MAX); goto err; } - Py_DECREF((PyObject *)x); + Py_DECREF((PyObject *)u); - zz_err ret = zz_fac((zz_digit_t)n, &res->z); + zz_err ret = zz_fac((zz_digit_t)x, &res->z); if (ret) { /* LCOV_EXCL_START */ @@ -2352,14 +2351,14 @@ gmp_comb(PyObject *Py_UNUSED(module), PyObject *const *args, Py_ssize_t nargs) return NULL; } - MPZ_Object *x, *y, *res = MPZ_new(); + MPZ_Object *u, *v, *res = MPZ_new(); if (!res) { return NULL; /* LCOV_EXCL_LINE */ } - CHECK_OP_INT(x, args[0]); - CHECK_OP_INT(y, args[1]); - if (zz_isneg(&x->z) || zz_isneg(&y->z)) { + CHECK_OP_INT(u, args[0]); + CHECK_OP_INT(v, args[1]); + if (zz_isneg(&u->z) || zz_isneg(&v->z)) { PyErr_SetString(PyExc_ValueError, "comb() not defined for negative values"); goto err; @@ -2367,15 +2366,15 @@ gmp_comb(PyObject *Py_UNUSED(module), PyObject *const *args, Py_ssize_t nargs) uint64_t n, k; - if (zz_get(&x->z, &n) || zz_get(&y->z, &k)) { + if (zz_get(&u->z, &n) || zz_get(&v->z, &k)) { overflow: PyErr_Format(PyExc_OverflowError, "comb() arguments should not exceed %ld", ULONG_MAX); goto err; } - Py_DECREF((PyObject *)x); - Py_DECREF((PyObject *)y); + Py_DECREF((PyObject *)u); + Py_DECREF((PyObject *)v); zz_err ret = zz_bin(n, k, &res->z); @@ -2406,14 +2405,14 @@ gmp_perm(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return gmp_fac(module, args[0]); } - MPZ_Object *x, *y, *res = MPZ_new(); + MPZ_Object *u, *v, *res = MPZ_new(); if (!res) { return NULL; /* LCOV_EXCL_LINE */ } - CHECK_OP_INT(x, args[0]); - CHECK_OP_INT(y, args[1]); - if (zz_isneg(&x->z) || zz_isneg(&y->z)) { + CHECK_OP_INT(u, args[0]); + CHECK_OP_INT(v, args[1]); + if (zz_isneg(&u->z) || zz_isneg(&v->z)) { PyErr_SetString(PyExc_ValueError, "perm() not defined for negative values"); goto err; @@ -2421,15 +2420,15 @@ gmp_perm(PyObject *module, PyObject *const *args, Py_ssize_t nargs) uint64_t n, k; - if (zz_get(&x->z, &n) || zz_get(&y->z, &k)) { + if (zz_get(&u->z, &n) || zz_get(&v->z, &k)) { overflow: PyErr_Format(PyExc_OverflowError, "perm() arguments should not exceed %ld", ULONG_MAX); goto err; } - Py_DECREF((PyObject *)x); - Py_DECREF((PyObject *)y); + Py_DECREF((PyObject *)u); + Py_DECREF((PyObject *)v); if (k > n) { return (PyObject *)res; } @@ -2445,7 +2444,7 @@ gmp_perm(PyObject *module, PyObject *const *args, Py_ssize_t nargs) zz_err ret = zz_fac((zz_digit_t)n, &res->z); - if (ret || zz_fac((zz_digit_t)(n-k), &den->z) + if (ret || zz_fac((zz_digit_t)(n - k), &den->z) || zz_quo(&res->z, &den->z, &res->z)) { /* LCOV_EXCL_START */ @@ -2654,7 +2653,7 @@ static PyMethodDef gmp_functions[] = { ("gcd($module, /, *integers)\n--\n\n" "Greatest Common Divisor.")}, {"gcdext", (PyCFunction)gmp_gcdext, METH_FASTCALL, - ("gcdext($module, x, y, /)\n--\n\n" + ("gcdext($module, n, k, /)\n--\n\n" "Compute extended GCD.")}, {"lcm", (PyCFunction)gmp_lcm, METH_FASTCALL, ("lcm($module, /, *integers)\n--\n\n" From 19c9b91469b07ba75b339b2842163ae13ea05c0a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 13 Nov 2026 06:05:05 +0300 Subject: [PATCH 16/17] Use CHECK_OPv2 in richcompare() --- gmp.c | 115 ++++++++++++++++++++++++---------------------------------- 1 file changed, 48 insertions(+), 67 deletions(-) diff --git a/gmp.c b/gmp.c index fc89b15b..d44a466a 100644 --- a/gmp.c +++ b/gmp.c @@ -601,25 +601,6 @@ str(PyObject *self) return MPZ_to_str((MPZ_Object *)self, 10, false); } -#define Number_Check(op) (PyFloat_Check((op)) || PyComplex_Check((op))) - -#define CHECK_OP(u, a) \ - if (MPZ_Check(a)) { \ - u = (MPZ_Object *)Py_NewRef(a); \ - } \ - else if (PyLong_Check(a)) { \ - u = MPZ_from_int(a); \ - if (!u) { \ - goto end; \ - } \ - } \ - else if (Number_Check(a)) { \ - goto numbers; \ - } \ - else { \ - goto fallback; \ - } - Py_LOCAL(PyObject *) to_float(PyObject *self) { @@ -650,37 +631,60 @@ PyLong_AsSdigit_t(PyObject *obj, int *error) return (int64_t)value; } +#define Number_Check(op) (PyFloat_Check((op)) || PyComplex_Check((op))) + +#define CHECK_OP(u, a) \ + if (MPZ_Check(a)) { \ + u = (MPZ_Object *)Py_NewRef(a); \ + } \ + else if (PyLong_Check(a)) { \ + u = MPZ_from_int(a); \ + if (!u) { \ + goto end; \ + } \ + } \ + else if (Number_Check(a)) { \ + goto numbers; \ + } \ + else { \ + goto fallback; \ + } + +#define CHECK_OPv2(u, a) \ + if (MPZ_Check(a)) { \ + u = (MPZ_Object *)Py_NewRef(a); \ + } \ + else if (PyLong_Check(a)) { \ + int error; \ + \ + ival = PyLong_AsSdigit_t(a, &error); \ + if (error) { \ + u = MPZ_from_int(a); \ + if (!u) { \ + goto end; \ + } \ + } \ + } \ + else if (Number_Check(a)) { \ + goto numbers; \ + } \ + else { \ + goto fallback; \ + } + Py_LOCAL(PyObject *) richcompare(PyObject *self, PyObject *other, int op) { - MPZ_Object *u = (MPZ_Object *)self; + MPZ_Object *u = (MPZ_Object *)self, *v = NULL; zz_ord r; + int64_t ival = 0; - if (MPZ_Check(other)) { - r = zz_cmp(&u->z, &((MPZ_Object *)other)->z); - } - else if (PyLong_Check(other)) { - int error; - int64_t temp = PyLong_AsSdigit_t(other, &error); - - if (!error) { - r = zz_cmp(&u->z, temp); - } - else { - MPZ_Object *v = MPZ_from_int(other); - - if (!v) { - goto end; /* LCOV_EXCL_LINE */ - } - r = zz_cmp(&u->z, &v->z); - Py_DECREF(v); - } - } - else if (Number_Check(other)) { - goto numbers; + CHECK_OPv2(v, other); + if (v) { + r = zz_cmp(&u->z, &v->z); } else { - goto fallback; + r = zz_cmp(&u->z, ival); } switch (op) { case Py_LT: @@ -797,28 +801,6 @@ to_bool(PyObject *self) return !zz_iszero(&((MPZ_Object *)self)->z); } -#define CHECK_OPv2(u, a) \ - if (MPZ_Check(a)) { \ - u = (MPZ_Object *)Py_NewRef(a); \ - } \ - else if (PyLong_Check(a)) { \ - int error; \ - \ - ival = PyLong_AsSdigit_t(a, &error); \ - if (error) { \ - u = MPZ_from_int(a); \ - if (!u) { \ - goto end; \ - } \ - } \ - } \ - else if (Number_Check(a)) { \ - goto numbers; \ - } \ - else { \ - goto fallback; \ - } - #define BINOP(suff, slot) \ Py_LOCAL(PyObject *) \ nb_##suff(PyObject *self, PyObject *other) \ @@ -828,7 +810,6 @@ to_bool(PyObject *self) \ CHECK_OPv2(u, self); \ CHECK_OPv2(v, other); \ - \ res = MPZ_new(); \ if (!res) { \ goto end; \ @@ -1043,8 +1024,8 @@ nb_truediv(PyObject *self, PyObject *other) \ CHECK_OP_INT(u, self); \ CHECK_OP_INT(v, other); \ - \ res = MPZ_new(); \ + \ zz_err ret = ZZ_OK; \ \ if (res && (ret = zz_##suff(&u->z, &v->z, &res->z))) { \ From 9684e1ef6b2a283edd1923ce92aca6aa99fe21cc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 13 Nov 2026 06:15:57 +0300 Subject: [PATCH 17/17] PyLong_AsSdigit_t -> PyLong_AsInt64AndOverflow --- gmp.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/gmp.c b/gmp.c index d44a466a..57b24bbb 100644 --- a/gmp.c +++ b/gmp.c @@ -617,7 +617,7 @@ to_float(PyObject *self) } Py_LOCAL_INLINE(int64_t) -PyLong_AsSdigit_t(PyObject *obj, int *error) +PyLong_AsInt64AndOverflow(PyObject *obj, int *error) { #if SIZEOF_LONG == 8 long value = PyLong_AsLongAndOverflow(obj, error); @@ -650,26 +650,26 @@ PyLong_AsSdigit_t(PyObject *obj, int *error) goto fallback; \ } -#define CHECK_OPv2(u, a) \ - if (MPZ_Check(a)) { \ - u = (MPZ_Object *)Py_NewRef(a); \ - } \ - else if (PyLong_Check(a)) { \ - int error; \ - \ - ival = PyLong_AsSdigit_t(a, &error); \ - if (error) { \ - u = MPZ_from_int(a); \ - if (!u) { \ - goto end; \ - } \ - } \ - } \ - else if (Number_Check(a)) { \ - goto numbers; \ - } \ - else { \ - goto fallback; \ +#define CHECK_OPv2(u, a) \ + if (MPZ_Check(a)) { \ + u = (MPZ_Object *)Py_NewRef(a); \ + } \ + else if (PyLong_Check(a)) { \ + int error; \ + \ + ival = PyLong_AsInt64AndOverflow(a, &error); \ + if (error) { \ + u = MPZ_from_int(a); \ + if (!u) { \ + goto end; \ + } \ + } \ + } \ + else if (Number_Check(a)) { \ + goto numbers; \ + } \ + else { \ + goto fallback; \ } Py_LOCAL(PyObject *)