Skip to content

Commit 52d8d47

Browse files
committed
Replace current arithmetic with Henrici variants that are algorithmically faster for large denominators.
See https://bugs.python.org/issue43420
1 parent ed7738f commit 52d8d47

File tree

3 files changed

+143
-29
lines changed

3 files changed

+143
-29
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ ChangeLog
77
* Faster and more space friendly pickling and unpickling.
88
https://bugs.python.org/issue44154
99

10+
* Algorithmically faster arithmetic for large denominators, although slower for
11+
small fraction components.
12+
https://bugs.python.org/issue43420
13+
Original patch for CPython by Sergey B. Kirpichev and Raymond Hettinger.
14+
1015
* Make sure ``bool(Fraction)`` always returns a ``bool``.
1116
https://bugs.python.org/issue39274
1217

src/quicktions.pyx

Lines changed: 136 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,142 @@ cdef _as_float(numerator, denominator):
832832
return numerator / denominator
833833

834834

835+
# Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1.
836+
#
837+
# Assume input fractions a and b are normalized.
838+
#
839+
# 1) Consider addition/subtraction.
840+
#
841+
# Let g = gcd(da, db). Then
842+
#
843+
# na nb na*db ± nb*da
844+
# a ± b == -- ± -- == ------------- ==
845+
# da db da*db
846+
#
847+
# na*(db//g) ± nb*(da//g) t
848+
# == ----------------------- == -
849+
# (da*db)//g d
850+
#
851+
# Now, if g > 1, we're working with smaller integers.
852+
#
853+
# Note, that t, (da//g) and (db//g) are pairwise coprime.
854+
#
855+
# Indeed, (da//g) and (db//g) share no common factors (they were
856+
# removed) and da is coprime with na (since input fractions are
857+
# normalized), hence (da//g) and na are coprime. By symmetry,
858+
# (db//g) and nb are coprime too. Then,
859+
#
860+
# gcd(t, da//g) == gcd(na*(db//g), da//g) == 1
861+
# gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1
862+
#
863+
# Above allows us optimize reduction of the result to lowest
864+
# terms. Indeed,
865+
#
866+
# g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g)
867+
#
868+
# t//g2 t//g2
869+
# a ± b == ----------------------- == ----------------
870+
# (da//g)*(db//g)*(g//g2) (da//g)*(db//g2)
871+
#
872+
# is a normalized fraction. This is useful because the unnormalized
873+
# denominator d could be much larger than g.
874+
#
875+
# We should special-case g == 1 (and g2 == 1), since 60.8% of
876+
# randomly-chosen integers are coprime:
877+
# https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality
878+
# Note, that g2 == 1 always for fractions, obtained from floats: here
879+
# g is a power of 2 and the unnormalized numerator t is an odd integer.
880+
#
881+
# 2) Consider multiplication
882+
#
883+
# Let g1 = gcd(na, db) and g2 = gcd(nb, da), then
884+
#
885+
# na*nb na*nb (na//g1)*(nb//g2)
886+
# a*b == ----- == ----- == -----------------
887+
# da*db db*da (db//g1)*(da//g2)
888+
#
889+
# Note, that after divisions we're multiplying smaller integers.
890+
#
891+
# Also, the resulting fraction is normalized, because each of
892+
# two factors in the numerator is coprime to each of the two factors
893+
# in the denominator.
894+
#
895+
# Indeed, pick (na//g1). It's coprime with (da//g2), because input
896+
# fractions are normalized. It's also coprime with (db//g1), because
897+
# common factors are removed by g1 == gcd(na, db).
898+
#
899+
# As for addition/subtraction, we should special-case g1 == 1
900+
# and g2 == 1 for same reason. That happens also for multiplying
901+
# rationals, obtained from floats.
902+
903+
cdef _add(na, da, nb, db):
904+
"""a + b"""
905+
# return Fraction(na * db + nb * da, da * db)
906+
g = _gcd(da, db)
907+
if g == 1:
908+
return Fraction(na * db + da * nb, da * db, _normalize=False)
909+
s = da // g
910+
t = na * (db // g) + nb * s
911+
g2 = _gcd(t, g)
912+
if g2 == 1:
913+
return Fraction(t, s * db, _normalize=False)
914+
return Fraction(t // g2, s * (db // g2), _normalize=False)
915+
916+
cdef _sub(na, da, nb, db):
917+
"""a - b"""
918+
# return Fraction(na * db - nb * da, da * db)
919+
g = _gcd(da, db)
920+
if g == 1:
921+
return Fraction(na * db - da * nb, da * db, _normalize=False)
922+
s = da // g
923+
t = na * (db // g) - nb * s
924+
g2 = _gcd(t, g)
925+
if g2 == 1:
926+
return Fraction(t, s * db, _normalize=False)
927+
return Fraction(t // g2, s * (db // g2), _normalize=False)
928+
929+
cdef _mul(na, da, nb, db):
930+
"""a * b"""
931+
# return Fraction(na * nb, da * db)
932+
g1 = _gcd(na, db)
933+
if g1 > 1:
934+
na //= g1
935+
db //= g1
936+
g2 = _gcd(nb, da)
937+
if g2 > 1:
938+
nb //= g2
939+
da //= g2
940+
return Fraction(na * nb, db * da, _normalize=False)
941+
942+
cdef _div(na, da, nb, db):
943+
"""a / b"""
944+
# return Fraction(na * db, da * nb)
945+
# Same as _mul(), with inversed b.
946+
g1 = _gcd(na, nb)
947+
if g1 > 1:
948+
na //= g1
949+
nb //= g1
950+
g2 = _gcd(db, da)
951+
if g2 > 1:
952+
da //= g2
953+
db //= g2
954+
n, d = na * db, nb * da
955+
if d < 0:
956+
n, d = -n, -d
957+
return Fraction(n, d, _normalize=False)
958+
959+
cdef _floordiv(an, ad, bn, bd):
960+
"""a // b -> int"""
961+
return (an * bd) // (bn * ad)
962+
963+
cdef _divmod(an, ad, bn, bd):
964+
div, n_mod = divmod(an * bd, ad * bn)
965+
return div, Fraction(n_mod, ad * bd)
966+
967+
cdef _mod(an, ad, bn, bd):
968+
return Fraction((an * bd) % (bn * ad), ad * bd)
969+
970+
835971
"""
836972
In general, we want to implement the arithmetic operations so
837973
that mixed-mode operations either call an implementation whose
@@ -906,35 +1042,6 @@ uses similar boilerplate code:
9061042
will get a TypeError.
9071043
"""
9081044

909-
910-
cdef _add(an, ad, bn, bd):
911-
"""a + b"""
912-
return Fraction(an * bd + bn * ad, ad * bd)
913-
914-
cdef _sub(an, ad, bn, bd):
915-
"""a - b"""
916-
return Fraction(an * bd - bn * ad, ad * bd)
917-
918-
cdef _mul(an, ad, bn, bd):
919-
"""a * b"""
920-
return Fraction(an * bn, ad * bd)
921-
922-
cdef _div(an, ad, bn, bd):
923-
"""a / b"""
924-
return Fraction(an * bd, ad * bn)
925-
926-
cdef _floordiv(an, ad, bn, bd):
927-
"""a // b -> int"""
928-
return (an * bd) // (bn * ad)
929-
930-
cdef _divmod(an, ad, bn, bd):
931-
div, n_mod = divmod(an * bd, ad * bn)
932-
return div, Fraction(n_mod, ad * bd)
933-
934-
cdef _mod(an, ad, bn, bd):
935-
return Fraction((an * bd) % (bn * ad), ad * bd)
936-
937-
9381045
cdef:
9391046
_math_op_add = operator.add
9401047
_math_op_sub = operator.sub

src/test_fractions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,9 @@ def testArithmetic(self):
475475
self.assertEqual(F(1, 2), F(1, 10) + F(2, 5))
476476
self.assertEqual(F(-3, 10), F(1, 10) - F(2, 5))
477477
self.assertEqual(F(1, 25), F(1, 10) * F(2, 5))
478+
self.assertEqual(F(5, 6), F(2, 3) * F(5, 4))
478479
self.assertEqual(F(1, 4), F(1, 10) / F(2, 5))
480+
self.assertEqual(F(-15, 8), F(3, 4) / F(-2, 5))
479481
self.assertTypedEquals(2, F(9, 10) // F(2, 5))
480482
self.assertTypedEquals(10**23, F(10**23, 1) // F(1))
481483
self.assertEqual(F(5, 6), F(7, 3) % F(3, 2))

0 commit comments

Comments
 (0)