Skip to content

Commit aee14f7

Browse files
authored
Merge pull request #119 from skirpichev/v0.2.1
v0.2.1
2 parents b67a302 + c0d1531 commit aee14f7

File tree

9 files changed

+47
-100
lines changed

9 files changed

+47
-100
lines changed

.github/workflows/docs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
sudo apt-get install libgmp-dev
1717
sudo apt-get install texlive texlive-latex-extra latexmk
1818
- run: pip install --upgrade pip
19+
- run: pip install -U git+https://github.com/picnixz/sphinx.git@fix/13188-classmethod-c
1920
- run: pip --verbose install --editable .[docs]
2021
- run: |
2122
alias sphinx-build='sphinx-build --color -W --keep-going'

.readthedocs.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ build:
1111
python: "3"
1212
python:
1313
install:
14+
- requirements: docs/requirements.txt
1415
- method: pip
1516
path: .
16-
extra_requirements:
17-
- docs
17+
# extra_requirements:
18+
# - docs
1819
sphinx:
1920
fail_on_warning: true
2021
configuration: docs/conf.py

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2024 Sergey B Kirpichev
3+
Copyright (c) 2024-2025 Sergey B Kirpichev
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ The CPython (and most other Python implementations, like PyPy) is optimized to
1717
work with small integers. Algorithms used here for "big enough" integers
1818
usually aren't best known in the field. Fortunately, it's possible to use
1919
bindings (for example, the `gmpy2 <https://pypi.org/project/gmpy2/>`_ package)
20-
to the GNU Multiple Precision Arithmetic Library (GMP), which aims to be
21-
faster than any other bignum library for all operand sizes.
20+
to the GNU Multiple Precision Arithmetic Library (GMP), which aims to be faster
21+
than any other bignum library for all operand sizes.
2222

2323
But such extension modules usually rely on default GMP's memory allocation
24-
functions and can't recover from errors such as out of memory. So, e.g. it's
25-
easy to crash the Python interpreter during the interactive session. Above
24+
functions and can't recover from errors such as out of memory. So, it's easy
25+
to crash the Python interpreter during the interactive session. Following
2626
example with the gmpy2 should work on most Unix systems:
2727

2828
.. code:: pycon
@@ -42,6 +42,8 @@ The gmp module handles such errors correctly:
4242

4343
.. code:: pycon
4444
45+
>>> import gmp, resource
46+
>>> resource.setrlimit(resource.RLIMIT_AS, (1024*32*1024, -1))
4547
>>> z = gmp.mpz(29925959575501)
4648
>>> while True:
4749
... z = z*z

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
# General information about the project.
3636
project = gmp.__package__
37-
copyright = "2024, Sergey B Kirpichev"
37+
copyright = "2024-2025, Sergey B Kirpichev"
3838

3939
gmp_version = packaging.version.parse(gmp.__version__)
4040

docs/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sphinx @ git+https://github.com/picnixz/sphinx.git@fix/13188-classmethod-c

main.c

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,8 @@ MPZ_abs(MPZ_Object *u)
150150
return res;
151151
}
152152

153-
/* Maps 1-byte integer to digit character for bases up to 36 and
154-
from 37 up to 62, respectively. */
155-
static const char *num_to_text36 = "0123456789abcdefghijklmnopqrstuvwxyz";
156-
static const char *num_to_text62 = ("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
157-
"abcdefghijklmnopqrstuvwxyz");
153+
/* Maps 1-byte integer to digit character for bases up to 36. */
154+
static const char *num_to_text = "0123456789abcdefghijklmnopqrstuvwxyz";
158155

159156
static const char *mpz_tag = "mpz(";
160157
static int OPT_TAG = 0x1;
@@ -163,9 +160,8 @@ static int OPT_PREFIX = 0x2;
163160
static PyObject *
164161
MPZ_to_str(MPZ_Object *u, int base, int options)
165162
{
166-
if (base < 2 || base > 62) {
167-
PyErr_SetString(PyExc_ValueError,
168-
"base must be in the interval [2, 62]");
163+
if (base < 2 || base > 36) {
164+
PyErr_SetString(PyExc_ValueError, "mpz base must be >= 2 and <= 36");
169165
return NULL;
170166
}
171167

@@ -209,9 +205,6 @@ MPZ_to_str(MPZ_Object *u, int base, int options)
209205
return PyErr_NoMemory();
210206
/* LCOV_EXCL_STOP */
211207
}
212-
213-
const char *num_to_text = base > 36 ? num_to_text62 : num_to_text36;
214-
215208
for (size_t i = 0; i < len; i++) {
216209
*p = num_to_text[*p];
217210
p++;
@@ -266,9 +259,9 @@ const unsigned char gmp_digit_value_tab[] =
266259
static MPZ_Object *
267260
MPZ_from_str(PyObject *obj, int base)
268261
{
269-
if (base != 0 && (base < 2 || base > 62)) {
262+
if (base != 0 && (base < 2 || base > 36)) {
270263
PyErr_SetString(PyExc_ValueError,
271-
"base must be 0 or in the interval [2, 62]");
264+
"mpz base must be >= 2 and <= 36, or 0");
272265
return NULL;
273266
}
274267

@@ -339,11 +332,6 @@ MPZ_from_str(PyObject *obj, int base)
339332
}
340333

341334
const unsigned char *digit_value = gmp_digit_value_tab;
342-
343-
if (base > 36) {
344-
digit_value += 208;
345-
}
346-
347335
Py_ssize_t new_len = len;
348336

349337
for (Py_ssize_t i = 0; i < len; i++) {
@@ -2136,7 +2124,7 @@ gmp_parse_pyargs(const gmp_pyargs *fnargs, int argidx[], PyObject *const *args,
21362124
}
21372125

21382126
static PyObject *
2139-
vectorcall(PyObject *type, PyObject * const*args, size_t nargsf,
2127+
vectorcall(PyObject *type, PyObject *const *args, size_t nargsf,
21402128
PyObject *kwnames)
21412129
{
21422130
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
@@ -2177,28 +2165,28 @@ str(PyObject *self)
21772165
return MPZ_to_str((MPZ_Object *)self, 10, 0);
21782166
}
21792167

2180-
#define Number_Check(op) (PyFloat_Check((op)) \
2181-
|| PyComplex_Check((op)))
2168+
#define Number_Check(op) (PyFloat_Check((op)) || PyComplex_Check((op)))
21822169

2183-
#define CHECK_OP(u, a) \
2184-
if (MPZ_Check(a)) { \
2185-
u = (MPZ_Object *)a; \
2186-
Py_INCREF(u); \
2187-
} \
2188-
else if (PyLong_Check(a)) { \
2189-
u = MPZ_from_int(a); \
2190-
if (!u) { \
2191-
goto end; \
2192-
} \
2193-
} \
2194-
else if (Number_Check(a)) { \
2195-
goto numbers; \
2196-
} \
2197-
else { \
2198-
goto fallback; \
2170+
#define CHECK_OP(u, a) \
2171+
if (MPZ_Check(a)) { \
2172+
u = (MPZ_Object *)a; \
2173+
Py_INCREF(u); \
2174+
} \
2175+
else if (PyLong_Check(a)) { \
2176+
u = MPZ_from_int(a); \
2177+
if (!u) { \
2178+
goto end; \
2179+
} \
2180+
} \
2181+
else if (Number_Check(a)) { \
2182+
goto numbers; \
2183+
} \
2184+
else { \
2185+
goto fallback; \
21992186
}
22002187

2201-
static PyObject * to_float(PyObject *self);
2188+
static PyObject *
2189+
to_float(PyObject *self);
22022190

22032191
static PyObject *
22042192
richcompare(PyObject *self, PyObject *other, int op)
@@ -3362,7 +3350,7 @@ static struct PyModuleDef gmp_module = {
33623350
};
33633351

33643352
PyDoc_STRVAR(gmp_info__doc__,
3365-
"gmp.gmplib_info\n\
3353+
"gmp.gmplib_info\n\
33663354
\n\
33673355
A named tuple that holds information about GNU GMP\n\
33683356
and it's internal representation of integers.\n\

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ version = {attr = "setuptools_scm.get_version"}
5757
[project.optional-dependencies]
5858
tests = ["pytest", "hypothesis"]
5959
docs = ["sphinx"]
60-
develop = ["python-gmp[tests,docs]", "pre-commit"]
60+
develop = ["python-gmp[tests,docs]", "pre-commit", "pyperf",
61+
"gmpy2>=2.2; platform_python_implementation!='PyPy'",
62+
"python-flint>=0.7.0a5"]
6163

6264
[tool.pytest.ini_options]
6365
addopts = "--durations=7"

tests/test_mpz.py

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -685,11 +685,9 @@ def test___sizeof__():
685685
def to_digits(n, base):
686686
if n == 0:
687687
return "0"
688-
if base < 2 or base > 62:
689-
raise ValueError("base must be in the interval [2, 62]")
688+
if base < 2 or base > 36:
689+
raise ValueError("mpz base must be >= 2 and <= 36")
690690
num_to_text = string.digits + string.ascii_lowercase
691-
if base > 36:
692-
num_to_text = num_to_text.upper() + string.ascii_lowercase
693691
digits = []
694692
if n < 0:
695693
sign = "-"
@@ -704,37 +702,7 @@ def to_digits(n, base):
704702
return sign + "".join(digits[::-1])
705703

706704

707-
def from_digits(s, base):
708-
if s == "0":
709-
return 0
710-
if base < 2 or base > 62:
711-
raise ValueError("base must be in the interval [2, 62]")
712-
if base <= 36:
713-
return int(s, base)
714-
if s[0] == "-":
715-
negative = True
716-
s = s[1:]
717-
else:
718-
negative = False
719-
n = 0
720-
b = 1
721-
for c in reversed(s):
722-
v = ord(c)
723-
if ord("0") <= v <= ord("9"):
724-
v -= ord("0")
725-
elif ord("A") <= v <= ord("Z"):
726-
v -= ord("A") - 10
727-
else:
728-
if base <= 36:
729-
v -= ord("a") - 10
730-
else:
731-
v -= ord("a") - 36
732-
n += v*b
733-
b *= base
734-
return -n if negative else n
735-
736-
737-
@given(integers(), integers(min_value=2, max_value=62))
705+
@given(integers(), integers(min_value=2, max_value=36))
738706
def test_digits(x, base):
739707
mx = mpz(x)
740708
res = to_digits(x, base)
@@ -761,7 +729,7 @@ def test_digits_interface():
761729

762730

763731
@given(integers(), integers(min_value=2, max_value=36))
764-
def test_digits_frombase_low(x, base):
732+
def test_digits_frombase(x, base):
765733
mx = mpz(x)
766734
smx = mx.digits(base)
767735
assert mpz(smx, base) == mx
@@ -777,22 +745,6 @@ def test_digits_frombase_low(x, base):
777745
assert mpz(smx, smaller_base) == i
778746

779747

780-
@given(integers(), integers(min_value=37, max_value=62))
781-
def test_digits_frombase_high(x, base):
782-
mx = mpz(x)
783-
smx = mx.digits(base)
784-
assert mpz(smx, base) == mx
785-
assert from_digits(smx, base) == mx
786-
smaller_base = (base + 2)//2 + 1
787-
try:
788-
g = from_digits(smx, smaller_base)
789-
except ValueError:
790-
with pytest.raises(ValueError):
791-
mpz(smx, smaller_base)
792-
else:
793-
assert mpz(smx, smaller_base) == g
794-
795-
796748
@given(integers())
797749
def test_frombase_auto(x):
798750
mx = mpz(x)

0 commit comments

Comments
 (0)