From 598b3c96307618abc71bfe984b1460aca3f51ecc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 11 Mar 2021 10:38:24 +0300 Subject: [PATCH 01/12] Merge Fraction._add/sub() to a common helper _add_sub_() --- Lib/fractions.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index de3e23b759227c..a9fa62dba7b438 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -4,6 +4,7 @@ """Fraction, infinite-precision, real numbers.""" from decimal import Decimal +import functools import math import numbers import operator @@ -380,20 +381,17 @@ def reverse(b, a): return forward, reverse - def _add(a, b): - """a + b""" + def _add_sub_(a, b, pm=int.__add__): da, db = a.denominator, b.denominator - return Fraction(a.numerator * db + b.numerator * da, + return Fraction(pm(a.numerator * db, b.numerator * da), da * db) + _add = functools.partial(_add_sub_) + _add.__doc__ = 'a + b' __add__, __radd__ = _operator_fallbacks(_add, operator.add) - def _sub(a, b): - """a - b""" - da, db = a.denominator, b.denominator - return Fraction(a.numerator * db - b.numerator * da, - da * db) - + _sub = functools.partial(_add_sub_, pm=int.__sub__) + _sub.__doc__ = 'a - b' __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) def _mul(a, b): From 430e4764cb554ab4c3fef16990dc55186a18b771 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 14 Mar 2021 06:39:21 +0300 Subject: [PATCH 02/12] bpo-43420: Simple optimizations for Fraction's arithmetics making Fraction class more usable for large arguments (>~ 10**6), with cost 10-20% for small components case. Before: $ ./python -m timeit -s 'from fractions import Fraction as F' \ -s 'a=[F(1, _**3) for _ in range(1, 1000)]' 'sum(a)' 5 loops, best of 5: 81.2 msec per loop After: $ ./python -m timeit -s 'from fractions import Fraction as F' \ -s 'a=[F(1, _**3) for _ in range(1, 1000)]' 'sum(a)' 10 loops, best of 5: 23 msec per loop References: Knuth, TAOCP, Volume 2, 4.5.1, https://www.eecis.udel.edu/~saunders/courses/822/98f/collins-notes/rnarith.ps, https://gmplib.org/ (e.g. https://gmplib.org/repo/gmp/file/tip/mpq/aors.c) --- Lib/fractions.py | 93 +++++++++++++++++-- .../2021-03-07-08-03-31.bpo-43420.cee_X5.rst | 2 + 2 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst diff --git a/Lib/fractions.py b/Lib/fractions.py index a9fa62dba7b438..9937b960ba8461 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -381,10 +381,79 @@ def reverse(b, a): return forward, reverse + # Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1. + # + # Assume input fractions a and b are normalized. + # + # 1) Consider addition/substraction. + # + # Let g = gcd(da, db). Then + # + # na nb na*db ± nb*da + # a ± b == -- ± -- == ------------- == + # da db da*db + # + # na*(db//g) ± nb*(da//g) t + # == ----------------------- == - + # (da*db)//g d + # + # Now, if g > 1, we're working with smaller integers. + # + # Note, that t, (da//g) and (db//g) are pairwise coprime. + # + # Indeed, (da//g) and (db//g) share no common factors (they were + # removed) and da is coprime with na (since input fractions are + # normalized), hence (da//g) and na are coprime. By symmetry, + # (db//g) and nb are coprime too. Then, + # + # gcd(t, da//g) == gcd(na*(db//g), da//g) == 1 + # gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1 + # + # Above allows us optimize reduction of the result to lowest + # terms. Indeed, + # + # g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g) + # + # t//g2 t//g2 + # a ± b == ----------------------- == ---------------- + # (da//g)*(db//g)*(g//g2) (da//g)*(db//g2) + # + # is a normalized fraction. This is useful because the unnormalized + # denominator d could be much larger than g. + # + # We should special-case g == 1, since 60.8% of randomly-chosen + # integers are coprime: + # https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality + # + # 2) Consider multiplication + # + # Let g1 = gcd(na, db) and g2 = gcd(nb, da), then + # + # na*nb na*nb (na//g1)*(nb//g2) + # a*b == ----- == ----- == ----------------- + # da*db db*da (db//g1)*(da//g2) + # + # Note, that after divisions we're multiplying smaller integers. + # + # Also, the resulting fraction is normalized, because each of + # two factors in the numerator is coprime to each of the two factors + # in the denominator. + # + # Indeed, pick (na//g1). It's coprime with (da//g2), because input + # fractions are normalized. It's also coprime with (db//g1), because + # common factors are removed by g1 == gcd(na, db). + def _add_sub_(a, b, pm=int.__add__): - da, db = a.denominator, b.denominator - return Fraction(pm(a.numerator * db, b.numerator * da), - da * db) + na, da = a.numerator, a.denominator + nb, db = b.numerator, b.denominator + g = math.gcd(da, db) + if g == 1: + return Fraction(pm(na * db, da * nb), da * db, _normalize=False) + else: + s = da // g + t = pm(na * (db // g), nb * s) + g2 = math.gcd(t, g) + return Fraction(t // g2, s * (db // g2), _normalize=False) _add = functools.partial(_add_sub_) _add.__doc__ = 'a + b' @@ -396,14 +465,26 @@ def _add_sub_(a, b, pm=int.__add__): def _mul(a, b): """a * b""" - return Fraction(a.numerator * b.numerator, a.denominator * b.denominator) + na, da = a.numerator, a.denominator + nb, db = b.numerator, b.denominator + g1 = math.gcd(na, db) + g2 = math.gcd(nb, da) + return Fraction((na // g1) * (nb // g2), + (db // g1) * (da // g2), _normalize=False) __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) def _div(a, b): """a / b""" - return Fraction(a.numerator * b.denominator, - a.denominator * b.numerator) + # Same as _mul(), with inversed b. + na, da = a.numerator, a.denominator + nb, db = b.numerator, b.denominator + g1 = math.gcd(na, nb) + g2 = math.gcd(db, da) + n, d = (na // g1) * (db // g2), (nb // g1) * (da // g2) + if nb < 0: + n, d = -n, -d + return Fraction(n, d, _normalize=False) __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) diff --git a/Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst b/Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst new file mode 100644 index 00000000000000..02025813a45713 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst @@ -0,0 +1,2 @@ +Improve performance of class:`fractions.Fraction` arithmetics for large +components. From 046c84e8f970306b668d62cfacbd58f65b8c6e66 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 14 Mar 2021 07:43:36 +0300 Subject: [PATCH 03/12] Use Fraction's private attributes in arithmetic methods That's slightly faster for small components. Before: $ ./python -m timeit -r11 -s 'from fractions import Fraction as F' \ -s 'a = F(10, 3)' -s 'b = F(6, 5)' 'a + b' 20000 loops, best of 11: 12.1 usec per loop $ ./python -m timeit -r11 -s 'from fractions import Fraction as F' \ -s 'a = F(10, 3)' -s 'b = 7' 'a + b' 20000 loops, best of 11: 11.4 usec per loop After: $ ./python -m timeit -r11 -s 'from fractions import Fraction as F' \ -s 'a = F(10, 3)' -s 'b = F(6, 5)' 'a + b' 50000 loops, best of 11: 9.74 usec per loop $ ./python -m timeit -r11 -s 'from fractions import Fraction as F' \ -s 'a = F(10, 3)' -s 'b = 7' 'a + b' 20000 loops, best of 11: 16.5 usec per loop On the master: $ ./python -m timeit -r11 -s 'from fractions import Fraction as F' \ -s 'a = F(10, 3)' -s 'b = F(6, 5)' 'a + b' 50000 loops, best of 11: 9.61 usec per loop $ ./python -m timeit -r11 -s 'from fractions import Fraction as F' \ -s 'a = F(10, 3)' -s 'b = 7' 'a + b' 50000 loops, best of 11: 9.11 usec per loop --- Lib/fractions.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 9937b960ba8461..0e0673c5e3f2f1 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -355,8 +355,10 @@ class doesn't subclass a concrete type, there's no """ def forward(a, b): - if isinstance(b, (int, Fraction)): + if isinstance(b, Fraction): return monomorphic_operator(a, b) + elif isinstance(b, int): + return monomorphic_operator(a, Fraction(b)) elif isinstance(b, float): return fallback_operator(float(a), b) elif isinstance(b, complex): @@ -367,9 +369,10 @@ def forward(a, b): forward.__doc__ = monomorphic_operator.__doc__ def reverse(b, a): - if isinstance(a, numbers.Rational): - # Includes ints. + if isinstance(a, Fraction): return monomorphic_operator(a, b) + elif isinstance(a, numbers.Integral): + return monomorphic_operator(Fraction(a), b) elif isinstance(a, numbers.Real): return fallback_operator(float(a), float(b)) elif isinstance(a, numbers.Complex): @@ -444,8 +447,8 @@ def reverse(b, a): # common factors are removed by g1 == gcd(na, db). def _add_sub_(a, b, pm=int.__add__): - na, da = a.numerator, a.denominator - nb, db = b.numerator, b.denominator + na, da = a._numerator, a._denominator + nb, db = b._numerator, b._denominator g = math.gcd(da, db) if g == 1: return Fraction(pm(na * db, da * nb), da * db, _normalize=False) @@ -465,8 +468,8 @@ def _add_sub_(a, b, pm=int.__add__): def _mul(a, b): """a * b""" - na, da = a.numerator, a.denominator - nb, db = b.numerator, b.denominator + na, da = a._numerator, a._denominator + nb, db = b._numerator, b._denominator g1 = math.gcd(na, db) g2 = math.gcd(nb, da) return Fraction((na // g1) * (nb // g2), @@ -477,8 +480,8 @@ def _mul(a, b): def _div(a, b): """a / b""" # Same as _mul(), with inversed b. - na, da = a.numerator, a.denominator - nb, db = b.numerator, b.denominator + na, da = a._numerator, a._denominator + nb, db = b._numerator, b._denominator g1 = math.gcd(na, nb) g2 = math.gcd(db, da) n, d = (na // g1) * (db // g2), (nb // g1) * (da // g2) From 772bec66301464de7f2a99febacf08c953a0c3ae Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 15 Mar 2021 06:59:03 +0300 Subject: [PATCH 04/12] Rational addition/substraction: special-case g2 == 1 Co-authored-by: Tim Peters --- Lib/fractions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 0e0673c5e3f2f1..7a6045e7a23b42 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -424,8 +424,8 @@ def reverse(b, a): # is a normalized fraction. This is useful because the unnormalized # denominator d could be much larger than g. # - # We should special-case g == 1, since 60.8% of randomly-chosen - # integers are coprime: + # We should special-case g == 1 (and g2 == 1), since 60.8% of + # randomly-chosen integers are coprime: # https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality # # 2) Consider multiplication @@ -456,7 +456,10 @@ def _add_sub_(a, b, pm=int.__add__): s = da // g t = pm(na * (db // g), nb * s) g2 = math.gcd(t, g) - return Fraction(t // g2, s * (db // g2), _normalize=False) + if g2 == 1: + return Fraction(t, s * db, _normalize=False) + else: + return Fraction(t // g2, s * (db // g2), _normalize=False) _add = functools.partial(_add_sub_) _add.__doc__ = 'a + b' From 3d5d321918cab8a05f51630dcec407c27955db13 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 16 Mar 2021 08:03:16 +0300 Subject: [PATCH 05/12] Special-case for trivial gcd in Fraction._mul()/_div() as well --- Lib/fractions.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 7a6045e7a23b42..52aa56bc2dfe34 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -445,6 +445,10 @@ def reverse(b, a): # Indeed, pick (na//g1). It's coprime with (da//g2), because input # fractions are normalized. It's also coprime with (db//g1), because # common factors are removed by g1 == gcd(na, db). + # + # As for addition/substraction, we should special-case g1 == 1 + # and g2 == 1 for same reason. That happens also for multiplying + # rationals, obtained from floats. def _add_sub_(a, b, pm=int.__add__): na, da = a._numerator, a._denominator @@ -474,9 +478,14 @@ def _mul(a, b): na, da = a._numerator, a._denominator nb, db = b._numerator, b._denominator g1 = math.gcd(na, db) + if g1 > 1: + na //= g1 + db //= g1 g2 = math.gcd(nb, da) - return Fraction((na // g1) * (nb // g2), - (db // g1) * (da // g2), _normalize=False) + if g2 > 1: + nb //= g2 + da //= g2 + return Fraction(na * nb, db * da, _normalize=False) __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) @@ -486,8 +495,14 @@ def _div(a, b): na, da = a._numerator, a._denominator nb, db = b._numerator, b._denominator g1 = math.gcd(na, nb) + if g1 > 1: + na //= g1 + nb //= g1 g2 = math.gcd(db, da) - n, d = (na // g1) * (db // g2), (nb // g1) * (da // g2) + if g2 > 1: + da //= g2 + db //= g2 + n, d = na * db, nb * da if nb < 0: n, d = -n, -d return Fraction(n, d, _normalize=False) From 32778abfa591a373a17f69b1d06f4f6c2a3dae8f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 16 Mar 2021 08:06:36 +0300 Subject: [PATCH 06/12] Amend _add_sub_() comment for fractions, obtained from floats --- Lib/fractions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/fractions.py b/Lib/fractions.py index 52aa56bc2dfe34..f000596a308db9 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -427,6 +427,8 @@ def reverse(b, a): # We should special-case g == 1 (and g2 == 1), since 60.8% of # randomly-chosen integers are coprime: # https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality + # Note, that g2 == 1 always for fractions, obtained from floats: here + # g is a power of 2 and the unnormalized numerator t is an odd integer. # # 2) Consider multiplication # From ea38398072df51037ce65cb2079ad5d45fec790f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 17 Mar 2021 11:33:11 +0300 Subject: [PATCH 07/12] Revert "Use Fraction's private attributes in arithmetic methods" This reverts commit 046c84e8f970306b668d62cfacbd58f65b8c6e66. --- Lib/fractions.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index f000596a308db9..57c14d6750d9d1 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -355,10 +355,8 @@ class doesn't subclass a concrete type, there's no """ def forward(a, b): - if isinstance(b, Fraction): + if isinstance(b, (int, Fraction)): return monomorphic_operator(a, b) - elif isinstance(b, int): - return monomorphic_operator(a, Fraction(b)) elif isinstance(b, float): return fallback_operator(float(a), b) elif isinstance(b, complex): @@ -369,10 +367,9 @@ def forward(a, b): forward.__doc__ = monomorphic_operator.__doc__ def reverse(b, a): - if isinstance(a, Fraction): + if isinstance(a, numbers.Rational): + # Includes ints. return monomorphic_operator(a, b) - elif isinstance(a, numbers.Integral): - return monomorphic_operator(Fraction(a), b) elif isinstance(a, numbers.Real): return fallback_operator(float(a), float(b)) elif isinstance(a, numbers.Complex): @@ -453,8 +450,8 @@ def reverse(b, a): # rationals, obtained from floats. def _add_sub_(a, b, pm=int.__add__): - na, da = a._numerator, a._denominator - nb, db = b._numerator, b._denominator + na, da = a.numerator, a.denominator + nb, db = b.numerator, b.denominator g = math.gcd(da, db) if g == 1: return Fraction(pm(na * db, da * nb), da * db, _normalize=False) @@ -477,8 +474,8 @@ def _add_sub_(a, b, pm=int.__add__): def _mul(a, b): """a * b""" - na, da = a._numerator, a._denominator - nb, db = b._numerator, b._denominator + na, da = a.numerator, a.denominator + nb, db = b.numerator, b.denominator g1 = math.gcd(na, db) if g1 > 1: na //= g1 @@ -494,8 +491,8 @@ def _mul(a, b): def _div(a, b): """a / b""" # Same as _mul(), with inversed b. - na, da = a._numerator, a._denominator - nb, db = b._numerator, b._denominator + na, da = a.numerator, a.denominator + nb, db = b.numerator, b.denominator g1 = math.gcd(na, nb) if g1 > 1: na //= g1 From f4b151a0da21a5e6e49d3bb43156454ea717e1bc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 17 Mar 2021 11:38:35 +0300 Subject: [PATCH 08/12] Revert 598b3c9630 & make _add()/_sub() less nested --- Lib/fractions.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 57c14d6750d9d1..3fb31e69ca8c5f 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -4,7 +4,6 @@ """Fraction, infinite-precision, real numbers.""" from decimal import Decimal -import functools import math import numbers import operator @@ -449,27 +448,36 @@ def reverse(b, a): # and g2 == 1 for same reason. That happens also for multiplying # rationals, obtained from floats. - def _add_sub_(a, b, pm=int.__add__): + def _add(a, b): + """a + b""" na, da = a.numerator, a.denominator nb, db = b.numerator, b.denominator g = math.gcd(da, db) if g == 1: - return Fraction(pm(na * db, da * nb), da * db, _normalize=False) - else: - s = da // g - t = pm(na * (db // g), nb * s) - g2 = math.gcd(t, g) - if g2 == 1: - return Fraction(t, s * db, _normalize=False) - else: - return Fraction(t // g2, s * (db // g2), _normalize=False) + return Fraction(na * db + da * nb, da * db, _normalize=False) + s = da // g + t = na * (db // g) + nb * s + g2 = math.gcd(t, g) + if g2 == 1: + return Fraction(t, s * db, _normalize=False) + return Fraction(t // g2, s * (db // g2), _normalize=False) - _add = functools.partial(_add_sub_) - _add.__doc__ = 'a + b' __add__, __radd__ = _operator_fallbacks(_add, operator.add) - _sub = functools.partial(_add_sub_, pm=int.__sub__) - _sub.__doc__ = 'a - b' + def _sub(a, b): + """a - b""" + na, da = a.numerator, a.denominator + nb, db = b.numerator, b.denominator + g = math.gcd(da, db) + if g == 1: + return Fraction(na * db - da * nb, da * db, _normalize=False) + s = da // g + t = na * (db // g) - nb * s + g2 = math.gcd(t, g) + if g2 == 1: + return Fraction(t, s * db, _normalize=False) + return Fraction(t // g2, s * (db // g2), _normalize=False) + __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) def _mul(a, b): From 40401afc7650b7d9a520dc5011199832b03a8a29 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev <2155800+skirpichev@users.noreply.github.com> Date: Thu, 18 Mar 2021 05:02:08 +0300 Subject: [PATCH 09/12] Apply spelling fixes Co-authored-by: Mark Dickinson --- Lib/fractions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 3fb31e69ca8c5f..b3ebc6b5d80297 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -384,7 +384,7 @@ def reverse(b, a): # # Assume input fractions a and b are normalized. # - # 1) Consider addition/substraction. + # 1) Consider addition/subtraction. # # Let g = gcd(da, db). Then # @@ -444,7 +444,7 @@ def reverse(b, a): # fractions are normalized. It's also coprime with (db//g1), because # common factors are removed by g1 == gcd(na, db). # - # As for addition/substraction, we should special-case g1 == 1 + # As for addition/subtraction, we should special-case g1 == 1 # and g2 == 1 for same reason. That happens also for multiplying # rationals, obtained from floats. From 020037af48e34a3d8f4e62deb802b0fbe0c1af61 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 18 Mar 2021 05:28:32 +0300 Subject: [PATCH 10/12] Restore back initial d test in _div() per Mark suggestion --- Lib/fractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index b3ebc6b5d80297..96047beb4546a5 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -510,7 +510,7 @@ def _div(a, b): da //= g2 db //= g2 n, d = na * db, nb * da - if nb < 0: + if d < 0: n, d = -n, -d return Fraction(n, d, _normalize=False) From bde52d149ebe973785e6b1714f1058522f70783b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 18 Mar 2021 05:45:05 +0300 Subject: [PATCH 11/12] Add coverage tests (missing in test_fractions.py) --- Lib/test/test_fractions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 0845f7921c39ec..b92552531d6bb2 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -369,7 +369,9 @@ def testArithmetic(self): self.assertEqual(F(1, 2), F(1, 10) + F(2, 5)) self.assertEqual(F(-3, 10), F(1, 10) - F(2, 5)) self.assertEqual(F(1, 25), F(1, 10) * F(2, 5)) + self.assertEqual(F(5, 6), F(2, 3) * F(5, 4)) self.assertEqual(F(1, 4), F(1, 10) / F(2, 5)) + self.assertEqual(F(-15, 8), F(3, 4) / F(-2, 5)) self.assertTypedEquals(2, F(9, 10) // F(2, 5)) self.assertTypedEquals(10**23, F(10**23, 1) // F(1)) self.assertEqual(F(5, 6), F(7, 3) % F(3, 2)) From 2789350bb77f58e05b85f3031a03fcaa715cc59b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 20 Mar 2021 21:17:35 +0300 Subject: [PATCH 12/12] Update Misc/ACKS --- Misc/ACKS | 1 + .../next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/ACKS b/Misc/ACKS index d1cee7d02b7869..5d3f75a4165b11 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -902,6 +902,7 @@ James King W. Trevor King Jeffrey Kintscher Paul Kippes +Sergey B Kirpichev Steve Kirsch Sebastian Kirsche Kamil Kisiel diff --git a/Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst b/Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst index 02025813a45713..f9b3228772c121 100644 --- a/Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst +++ b/Misc/NEWS.d/next/Library/2021-03-07-08-03-31.bpo-43420.cee_X5.rst @@ -1,2 +1,2 @@ Improve performance of class:`fractions.Fraction` arithmetics for large -components. +components. Contributed by Sergey B. Kirpichev.