Skip to content

Commit f349183

Browse files
Faster fmpz/int conversion using from_bytes/to_bytes
1 parent 955f1df commit f349183

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

src/flint/types/fmpz.pxd

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,44 @@
11
from cpython.long cimport PyLong_Check
22
from flint.flint_base.flint_base cimport flint_scalar
33
from flint.utils.conversion cimport chars_from_str
4-
from flint.flintlib.types.flint cimport slong, pylong_as_slong
4+
from flint.flintlib.types.flint cimport slong, ulong, pylong_as_slong
55
from flint.flintlib.types.flint cimport PyObject
6-
from flint.flintlib.functions.fmpz cimport fmpz_t, fmpz_set_str, fmpz_set_si
6+
from flint.flintlib.functions.fmpz cimport fmpz_t, fmpz_set_si, fmpz_set_signed_ui_array
7+
import sys
78

89
cdef int fmpz_set_any_ref(fmpz_t x, obj)
910
cdef fmpz_get_intlong(fmpz_t x)
1011

12+
cdef int is_big_endian = int(sys.byteorder == "big")
13+
14+
cdef inline ulong ulong_from_little_endian(unsigned char *ptr):
15+
# Read a ulong from little-endian bytes
16+
cdef ulong w = 0
17+
for i in range(sizeof(ulong) // 8):
18+
w = (w << 8) | ptr[i]
19+
return w
20+
1121
cdef inline int fmpz_set_pylong(fmpz_t x, obj):
1222
cdef int overflow
1323
cdef slong longval
24+
cdef slong size
25+
cdef bytes b
26+
cdef ulong w
27+
cdef ulong *words
28+
cdef int i
29+
1430
longval = pylong_as_slong(<PyObject*>obj, &overflow)
1531
if overflow:
16-
s = "%x" % obj
17-
fmpz_set_str(x, chars_from_str(s), 16)
32+
# make sure the sign bit fits
33+
# we need 8 * sizeof(ulong) * size > obj.bit_length()
34+
size = obj.bit_length() // (8 * sizeof(ulong)) + 1
35+
b = obj.to_bytes(sizeof(ulong) * size, "little", signed=True)
36+
# b is a local Python object, we access the internal pointer
37+
words = <ulong*>(<char *>b)
38+
if is_big_endian:
39+
for i in range(size):
40+
words[i] = ulong_from_little_endian(<unsigned char *>(words + i))
41+
fmpz_set_signed_ui_array(x, words, size)
1842
else:
1943
fmpz_set_si(x, longval)
2044

src/flint/types/fmpz.pyx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from flint.flint_base.flint_base cimport flint_scalar
22
from flint.utils.typecheck cimport typecheck
33
from flint.utils.conversion cimport chars_from_str
44
from flint.utils.conversion cimport str_from_chars, _str_trunc
5-
cimport libc.stdlib
5+
from libc.stdlib cimport malloc, free
66

77
from flint.flintlib.types.flint cimport FMPZ_REF, FMPZ_TMP, FMPZ_UNKNOWN, COEFF_IS_MPZ
88
from flint.flintlib.functions.flint cimport flint_free
@@ -14,16 +14,33 @@ from flint.flintlib.functions.partitions cimport *
1414

1515
from flint.utils.flint_exceptions import DomainError
1616

17-
1817
cdef fmpz_get_intlong(fmpz_t x):
1918
"""
2019
Convert fmpz_t to a Python int or long.
2120
"""
22-
cdef char * s
21+
cdef slong size
22+
cdef ulong * words
23+
cdef int i
24+
cdef fmpz_struct xabs[1]
2325
if COEFF_IS_MPZ(x[0]):
24-
s = fmpz_get_str(NULL, 16, x)
25-
v = int(str_from_chars(s), 16)
26-
flint_free(s)
26+
# Python from signed bytes is slow so we convert the absolute value.
27+
# we need 8 * sizeof(ulong) * size >= fmpz_bits(x)
28+
size = fmpz_bits(x) // (8 * sizeof(ulong)) + 1
29+
words = <ulong *>malloc(size * sizeof(ulong))
30+
if fmpz_sgn(x) == -1:
31+
fmpz_init(xabs)
32+
fmpz_abs(xabs, x)
33+
fmpz_get_ui_array(words, size, xabs)
34+
fmpz_clear(xabs)
35+
else:
36+
fmpz_get_ui_array(words, size, x)
37+
if is_big_endian:
38+
for i in range(size):
39+
words[i] = ulong_from_little_endian(<unsigned char *>(words + i))
40+
v = int.from_bytes((<char *>words)[:size * sizeof(ulong)], "little")
41+
if fmpz_sgn(x) == -1:
42+
v = -v
43+
free(words)
2744
return v
2845
else:
2946
return <slong>x[0]

0 commit comments

Comments
 (0)