From 318826b9105a089f4f59f7d19977fc5263848764 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 20 Jun 2026 10:02:11 +0300 Subject: [PATCH 1/7] Copy normalize/from_man_exp() from mpmath v1.4.1 tree --- pyproject.toml | 2 +- tests/test_functions.py | 8 +-- tests/utils.py | 122 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5806366e..5ffe33cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ Homepage = "https://github.com/diofant/python-gmp" Documentation = "https://python-gmp.readthedocs.io/en/latest/" [project.optional-dependencies] -tests = ["pytest", "hypothesis", "mpmath"] +tests = ["pytest", "hypothesis"] ci = ["python-gmp[tests]", "pytest-xdist"] docs = ["sphinx>=8.2"] develop = ["python-gmp[tests,docs]", "pre-commit", "pyperf"] diff --git a/tests/test_functions.py b/tests/test_functions.py index 41adf211..d89efc3c 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -22,6 +22,8 @@ from hypothesis.strategies import booleans, integers, lists, sampled_from from utils import ( bigints, + mpmath_from_man_exp, + mpmath_normalize, python_gcdext, python_isqrtrem, ) @@ -137,11 +139,10 @@ def test_lcm_nary(xs): @example(1, 6277101735386680763495507056286727952638980837032266301441, 0, 64, "f") def test_mpmath_normalize(sign, man, exp, prec, rnd): - mpmath = pytest.importorskip("mpmath") mman = mpz(man) sign = int(sign) bc = mman.bit_length() - res = mpmath.libmp.libmpf._normalize(sign, man, exp, bc, prec, rnd) + res = mpmath_normalize(sign, man, exp, bc, prec, rnd) assert all(type(_) is int for _ in res) assert _mpmath_normalize(sign, mman, exp, bc, prec, rnd) == res @@ -152,9 +153,8 @@ def test_mpmath_normalize(sign, man, exp, prec, rnd): @example(-6277101735386680763495507056286727952638980837032266301441, 0, 64, "f") def test_mpmath_create(man, exp, prec, rnd): - mpmath = pytest.importorskip("mpmath") mman = mpz(man) - res = mpmath.libmp.from_man_exp(man, exp, prec, rnd) + res = mpmath_from_man_exp(man, exp, prec, rnd) assert all(type(_) is int for _ in res) assert _mpmath_create(mman, exp, prec, rnd) == res assert mman == man diff --git a/tests/utils.py b/tests/utils.py index 9ee2a9c1..6f43aa0e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -182,3 +182,125 @@ def numbers(draw): if draw(booleans()): return draw(floats()) return draw(complex_numbers()) + + +# All supported rounding modes +round_nearest = sys.intern("n") +round_floor = sys.intern("f") +round_ceiling = sys.intern("c") +round_up = sys.intern("u") +round_down = sys.intern("d") +round_fast = round_down + + +# These masks are used to pick out segments of numbers to determine +# which direction to round when rounding to nearest. +class h_mask_big: + def __getitem__(self, n): + return (1<<(n-1))-1 + +h_mask_small = [0]+[((1<<(_-1))-1) for _ in range(1, 300)] +h_mask = [h_mask_big(), h_mask_small] + + +# The >> operator rounds to floor. shifts_down[rnd][sign] +# tells whether this is the right direction to use, or if the +# number should be negated before shifting +shifts_down = {round_floor:(1,0), round_ceiling:(0,1), + round_down:(1,1), round_up:(0,0)} + + +small_trailing = [0] * 256 +for j in range(1, 8): + small_trailing[1<>= 8 + while not n & 0xff: + n >>= 8 + t += 8 + return t + small_trailing[n & 0xff] + +trailtable = [trailing(n) for n in range(256)] + + +def mpmath_normalize(sign, man, exp, bc, prec, rnd): + """ + Create a raw mpf tuple with value (-1)**sign * man * 2**exp and + normalized mantissa. The mantissa is rounded in the specified + direction if its size exceeds the precision. Trailing zero bits + are also stripped from the mantissa to ensure that the + representation is canonical. + """ + if not man: + return 0, 0, 0, 0 + # Cut mantissa down to size if larger than target precision + n = bc - prec + if n > 0: + if rnd == round_nearest: + t = man >> (n-1) + if t & 1 and ((t & 2) or (man & h_mask[n<300][n])): + man = (t>>1)+1 + else: + man = t>>1 + elif shifts_down[rnd][sign]: + man >>= n + else: + man = -((-man)>>n) + exp += n + bc = prec + # Strip trailing bits + if not man & 1: + t = trailtable[man & 255] + if not t: + while not man & 255: + man >>= 8 + exp += 8 + bc -= 8 + t = trailtable[man & 255] + man >>= t + exp += t + bc -= t + # Bit count can be wrong if the input mantissa was 1 less than + # a power of 2 and got rounded up, thereby adding an extra bit. + # With trailing bits removed, all powers of two have mantissa 1, + # so this is easy to check for. + if man == 1: + bc = 1 + return sign, man, exp, bc + + +def mpmath_from_man_exp(man, exp, prec=0, rnd=round_fast): + """Create raw mpf from (man, exp) pair. The mantissa may be signed. + If no precision is specified, the mantissa is stored exactly.""" + sign = 0 + if man < 0: + sign = 1 + man = -man + bc = man.bit_length() + if not prec: + if not man: + return 0, 0, 0, 0 + if not man & 1: + if man & 2: + return sign, man >> 1, exp + 1, bc - 1 + t = trailtable[man & 255] + if not t: + while not man & 255: + man >>= 8 + exp += 8 + bc -= 8 + t = trailtable[man & 255] + man >>= t + exp += t + bc -= t + return sign, man, exp, bc + return mpmath_normalize(sign, man, exp, bc, prec, rnd) From 79c806f53b18b3e177bb9ec02a6f3f6ee57da2e8 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 20 Jun 2026 10:53:33 +0300 Subject: [PATCH 2/7] Simplify mpmath's reference functions --- tests/utils.py | 119 ++++++++++--------------------------------------- 1 file changed, 24 insertions(+), 95 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 6f43aa0e..1b0935e6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -184,54 +184,6 @@ def numbers(draw): return draw(complex_numbers()) -# All supported rounding modes -round_nearest = sys.intern("n") -round_floor = sys.intern("f") -round_ceiling = sys.intern("c") -round_up = sys.intern("u") -round_down = sys.intern("d") -round_fast = round_down - - -# These masks are used to pick out segments of numbers to determine -# which direction to round when rounding to nearest. -class h_mask_big: - def __getitem__(self, n): - return (1<<(n-1))-1 - -h_mask_small = [0]+[((1<<(_-1))-1) for _ in range(1, 300)] -h_mask = [h_mask_big(), h_mask_small] - - -# The >> operator rounds to floor. shifts_down[rnd][sign] -# tells whether this is the right direction to use, or if the -# number should be negated before shifting -shifts_down = {round_floor:(1,0), round_ceiling:(0,1), - round_down:(1,1), round_up:(0,0)} - - -small_trailing = [0] * 256 -for j in range(1, 8): - small_trailing[1<>= 8 - while not n & 0xff: - n >>= 8 - t += 8 - return t + small_trailing[n & 0xff] - -trailtable = [trailing(n) for n in range(256)] - - def mpmath_normalize(sign, man, exp, bc, prec, rnd): """ Create a raw mpf tuple with value (-1)**sign * man * 2**exp and @@ -245,62 +197,39 @@ def mpmath_normalize(sign, man, exp, bc, prec, rnd): # Cut mantissa down to size if larger than target precision n = bc - prec if n > 0: - if rnd == round_nearest: - t = man >> (n-1) - if t & 1 and ((t & 2) or (man & h_mask[n<300][n])): - man = (t>>1)+1 + # The >> operator rounds to floor. shifts_down[rnd][sign] + # tells whether this is the right direction to use, or if the + # number should be negated before shifting + shifts_down = {"f": (1, 0), "c": (0, 1), "d": (1, 1), "u": (0, 0)} + if rnd == "n": + t = man >> (n - 1) + if t & 1 and ((t & 2) or (man & ((1 << (n - 1)) - 1))): + man = (t >> 1) + 1 else: - man = t>>1 + man = t >> 1 elif shifts_down[rnd][sign]: man >>= n else: - man = -((-man)>>n) + man = -((-man) >> n) exp += n - bc = prec # Strip trailing bits - if not man & 1: - t = trailtable[man & 255] - if not t: - while not man & 255: - man >>= 8 - exp += 8 - bc -= 8 - t = trailtable[man & 255] - man >>= t - exp += t - bc -= t - # Bit count can be wrong if the input mantissa was 1 less than - # a power of 2 and got rounded up, thereby adding an extra bit. - # With trailing bits removed, all powers of two have mantissa 1, - # so this is easy to check for. - if man == 1: - bc = 1 - return sign, man, exp, bc - - -def mpmath_from_man_exp(man, exp, prec=0, rnd=round_fast): - """Create raw mpf from (man, exp) pair. The mantissa may be signed. - If no precision is specified, the mantissa is stored exactly.""" - sign = 0 + while man & 1 == 0: + man >>= 1 + exp += 1 + return sign, man, exp, man.bit_length() + + +def mpmath_from_man_exp(man, exp, prec=0, rnd="d"): + """ + Create raw mpf from (man, exp) pair. The mantissa may be signed. + If no precision is specified, the mantissa is stored exactly. + """ if man < 0: sign = 1 man = -man + else: + sign = 0 bc = man.bit_length() if not prec: - if not man: - return 0, 0, 0, 0 - if not man & 1: - if man & 2: - return sign, man >> 1, exp + 1, bc - 1 - t = trailtable[man & 255] - if not t: - while not man & 255: - man >>= 8 - exp += 8 - bc -= 8 - t = trailtable[man & 255] - man >>= t - exp += t - bc -= t - return sign, man, exp, bc + prec = bc return mpmath_normalize(sign, man, exp, bc, prec, rnd) From 35ec3580bb58dde65d0ab9bf9460ac0e87bfe8be Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 20 Jun 2026 10:54:34 +0300 Subject: [PATCH 3/7] Use prek instead of pre-commit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5ffe33cf..09af9472 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ Documentation = "https://python-gmp.readthedocs.io/en/latest/" tests = ["pytest", "hypothesis"] ci = ["python-gmp[tests]", "pytest-xdist"] docs = ["sphinx>=8.2"] -develop = ["python-gmp[tests,docs]", "pre-commit", "pyperf"] +develop = ["python-gmp[tests,docs]", "prek", "pyperf"] [tool.meson-python.args] compile = ["--verbose"] From 478c1cdf10a1fc9363c3df500c5e0d7ea7eb5ea3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 20 Jun 2026 11:16:37 +0300 Subject: [PATCH 4/7] Simplify cibuildwheel settings (delvewheel) --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 09af9472..17de8625 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,10 +79,8 @@ CFLAGS = "-Wall -Wpedantic -Werror -Wconversion" [tool.cibuildwheel.windows] archs = ["auto64"] before-all = "msys2 -c scripts/cibw_before_all.sh" -before-build = "pip install delvewheel" -repair-wheel-command = """delvewheel repair -w {dest_dir} {wheel} \ - --add-path .local/bin""" [tool.cibuildwheel.windows.environment] PYTEST_ADDOPTS = "-n 2" CFLAGS = "" +PATH = ".\\\\.local\\\\bin;$PATH" From 28fd8d3767ecd5b19632171f1e52ee0005615d0d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 20 Jun 2026 11:26:22 +0300 Subject: [PATCH 5/7] Cleanup pyproject.toml --- pyproject.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 17de8625..942c1f18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,12 +49,6 @@ ci = ["python-gmp[tests]", "pytest-xdist"] docs = ["sphinx>=8.2"] develop = ["python-gmp[tests,docs]", "prek", "pyperf"] -[tool.meson-python.args] -compile = ["--verbose"] - -[tool.pytest.ini_options] -xfail_strict = true - [tool.ruff] line-length = 79 From 094e329eec441c4d0bb54b1748102eaf0d16a4dc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 20 Jun 2026 15:46:57 +0300 Subject: [PATCH 6/7] Cleanup .readthedocs.yaml --- .readthedocs.yaml | 2 -- pyproject.toml | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a61a1043..313354df 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -17,8 +17,6 @@ build: python: install: - path: . - extra_requirements: - - docs sphinx: fail_on_warning: true configuration: docs/conf.py diff --git a/pyproject.toml b/pyproject.toml index 942c1f18..4b1fe020 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,7 @@ Documentation = "https://python-gmp.readthedocs.io/en/latest/" [project.optional-dependencies] tests = ["pytest", "hypothesis"] ci = ["python-gmp[tests]", "pytest-xdist"] -docs = ["sphinx>=8.2"] -develop = ["python-gmp[tests,docs]", "prek", "pyperf"] +develop = ["python-gmp[tests]", "prek", "pyperf"] [tool.ruff] line-length = 79 From 37592a7c5e061582ab81b7edfcbf3cb4c111adda Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 21 Jun 2026 10:38:15 +0300 Subject: [PATCH 7/7] Update pypa/cibuildwheel --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 66263a5f..628ae796 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -42,7 +42,7 @@ jobs: - run: echo "PKG_CONFIG_PATH=${{ github.workspace }}/.local/lib/pkgconfig" >> $env:GITHUB_ENV if: ${{ startsWith(matrix.os, 'windows') }} - name: Build wheels - uses: pypa/cibuildwheel@v4.0.0 + uses: pypa/cibuildwheel@v4.1.0 - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.os }}