Skip to content

Commit 95df675

Browse files
committed
Avoid calling _PyLong_GCD() in Py3.13 and call math.gcd() instead (which is hopefully almost as fast).
1 parent 20bd947 commit 95df675

1 file changed

Lines changed: 16 additions & 40 deletions

File tree

src/quicktions.pyx

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ import sys
5050

5151
cdef bint _decimal_supports_integer_ratio = hasattr(Decimal, "as_integer_ratio") # Py3.6+
5252
cdef object _operator_index = operator.index
53+
cdef object math_gcd
54+
try:
55+
math_gcd = math.gcd
56+
except AttributeError:
57+
pass
5358

5459

5560
# Cache widely used 10**x int objects.
@@ -89,25 +94,24 @@ cdef pow10(long long i):
8994

9095
cdef extern from *:
9196
"""
92-
#if PY_VERSION_HEX < 0x030500F0 || !CYTHON_COMPILING_IN_CPYTHON
97+
#if PY_VERSION_HEX < 0x030500F0 || PY_VERSION_HEX >= 0x030d0000 || !CYTHON_COMPILING_IN_CPYTHON
9398
#define _PyLong_GCD(a, b) (NULL)
9499
#endif
95100
"""
96-
# CPython 3.5+ has a fast PyLong GCD implementation that we can use.
101+
# CPython 3.5-3.12 has a fast PyLong GCD implementation that we can use.
102+
# In CPython 3.13, math.gcd() is fast enough to call it directly.
97103
int PY_VERSION_HEX
98-
int IS_CPYTHON "CYTHON_COMPILING_IN_CPYTHON"
104+
int HAS_PYLONG_GCD "(CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX < 0x030d0000)"
99105
_PyLong_GCD(a, b)
100106

101107

102108
cpdef _gcd(a, b):
103109
"""Calculate the Greatest Common Divisor of a and b as a non-negative number.
104110
"""
105-
if PY_VERSION_HEX < 0x030500F0 or not IS_CPYTHON:
111+
if PY_VERSION_HEX >= 0x030d0000:
112+
return math_gcd(a, b)
113+
if PY_VERSION_HEX < 0x030500F0 or not HAS_PYLONG_GCD:
106114
return _gcd_fallback(a, b)
107-
if not isinstance(a, int):
108-
a = int(a)
109-
if not isinstance(b, int):
110-
b = int(b)
111115
return _PyLong_GCD(a, b)
112116

113117

@@ -134,38 +138,6 @@ cdef cunumber _igcd(cunumber a, cunumber b):
134138
return a
135139

136140

137-
cdef cunumber _ibgcd(cunumber a, cunumber b):
138-
"""Binary GCD algorithm.
139-
See https://en.wikipedia.org/wiki/Binary_GCD_algorithm
140-
"""
141-
cdef uint shift = 0
142-
if not a:
143-
return b
144-
if not b:
145-
return a
146-
147-
# Find common pow2 factors.
148-
while not (a|b) & 1:
149-
a >>= 1
150-
b >>= 1
151-
shift += 1
152-
153-
# Exclude factor 2.
154-
while not a & 1:
155-
a >>= 1
156-
157-
# a is always odd from here on.
158-
while b:
159-
while not b & 1:
160-
b >>= 1
161-
if a > b:
162-
a, b = b, a
163-
b -= a
164-
165-
# Restore original pow2 factor.
166-
return a << shift
167-
168-
169141
cdef _py_gcd(ullong a, ullong b):
170142
if a <= <ullong>INT_MAX and b <= <ullong>INT_MAX:
171143
return <int> _igcd[uint](<uint> a, <uint> b)
@@ -455,6 +427,10 @@ cdef class Fraction:
455427
if denominator == 0:
456428
raise ZeroDivisionError(f'Fraction({numerator}, 0)')
457429
if _normalize:
430+
if not isinstance(numerator, int):
431+
numerator = int(numerator)
432+
if not isinstance(denominator, int):
433+
denominator = int(denominator)
458434
g = _gcd(numerator, denominator)
459435
# NOTE: 'is' tests on integers are generally a bad idea, but
460436
# they are fast and if they fail here, it'll still be correct

0 commit comments

Comments
 (0)