@@ -381,10 +381,79 @@ def reverse(b, a):
381381
382382 return forward , reverse
383383
384+ # Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1.
385+ #
386+ # Assume input fractions a and b are normalized.
387+ #
388+ # 1) Consider addition/substraction.
389+ #
390+ # Let g = gcd(da, db). Then
391+ #
392+ # na nb na*db ± nb*da
393+ # a ± b == -- ± -- == ------------- ==
394+ # da db da*db
395+ #
396+ # na*(db//g) ± nb*(da//g) t
397+ # == ----------------------- == -
398+ # (da*db)//g d
399+ #
400+ # Now, if g > 1, we're working with smaller integers.
401+ #
402+ # Note, that t, (da//g) and (db//g) are pairwise coprime.
403+ #
404+ # Indeed, (da//g) and (db//g) share no common factors (they were
405+ # removed) and da is coprime with na (since input fractions are
406+ # normalized), hence (da//g) and na are coprime. By symmetry,
407+ # (db//g) and nb are coprime too. Then,
408+ #
409+ # gcd(t, da//g) == gcd(na*(db//g), da//g) == 1
410+ # gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1
411+ #
412+ # Above allows us optimize reduction of the result to lowest
413+ # terms. Indeed,
414+ #
415+ # g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g)
416+ #
417+ # t//g2 t//g2
418+ # a ± b == ----------------------- == ----------------
419+ # (da//g)*(db//g)*(g//g2) (da//g)*(db//g2)
420+ #
421+ # is a normalized fraction. This is useful because the unnormalized
422+ # denominator d could be much larger than g.
423+ #
424+ # We should special-case g == 1, since 60.8% of randomly-chosen
425+ # integers are coprime:
426+ # https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality
427+ #
428+ # 2) Consider multiplication
429+ #
430+ # Let g1 = gcd(na, db) and g2 = gcd(nb, da), then
431+ #
432+ # na*nb na*nb (na//g1)*(nb//g2)
433+ # a*b == ----- == ----- == -----------------
434+ # da*db db*da (db//g1)*(da//g2)
435+ #
436+ # Note, that after divisions we're multiplying smaller integers.
437+ #
438+ # Also, the resulting fraction is normalized, because each of
439+ # two factors in the numerator is coprime to each of the two factors
440+ # in the denominator.
441+ #
442+ # Indeed, pick (na//g1). It's coprime with (da//g2), because input
443+ # fractions are normalized. It's also coprime with (db//g1), because
444+ # common factors are removed by g1 == gcd(na, db).
445+
384446 def _add_sub_ (a , b , pm = int .__add__ ):
385- da , db = a .denominator , b .denominator
386- return Fraction (pm (a .numerator * db , b .numerator * da ),
387- da * db )
447+ na , da = a .numerator , a .denominator
448+ nb , db = b .numerator , b .denominator
449+ g = math .gcd (da , db )
450+ if g == 1 :
451+ return Fraction (pm (na * db , da * nb ), da * db , _normalize = False )
452+ else :
453+ s = da // g
454+ t = pm (na * (db // g ), nb * s )
455+ g2 = math .gcd (t , g )
456+ return Fraction (t // g2 , s * (db // g2 ), _normalize = False )
388457
389458 _add = functools .partial (_add_sub_ )
390459 _add .__doc__ = 'a + b'
@@ -396,14 +465,26 @@ def _add_sub_(a, b, pm=int.__add__):
396465
397466 def _mul (a , b ):
398467 """a * b"""
399- return Fraction (a .numerator * b .numerator , a .denominator * b .denominator )
468+ na , da = a .numerator , a .denominator
469+ nb , db = b .numerator , b .denominator
470+ g1 = math .gcd (na , db )
471+ g2 = math .gcd (nb , da )
472+ return Fraction ((na // g1 ) * (nb // g2 ),
473+ (db // g1 ) * (da // g2 ), _normalize = False )
400474
401475 __mul__ , __rmul__ = _operator_fallbacks (_mul , operator .mul )
402476
403477 def _div (a , b ):
404478 """a / b"""
405- return Fraction (a .numerator * b .denominator ,
406- a .denominator * b .numerator )
479+ # Same as _mul(), with inversed b.
480+ na , da = a .numerator , a .denominator
481+ nb , db = b .numerator , b .denominator
482+ g1 = math .gcd (na , nb )
483+ g2 = math .gcd (db , da )
484+ n , d = (na // g1 ) * (db // g2 ), (nb // g1 ) * (da // g2 )
485+ if nb < 0 :
486+ n , d = - n , - d
487+ return Fraction (n , d , _normalize = False )
407488
408489 __truediv__ , __rtruediv__ = _operator_fallbacks (_div , operator .truediv )
409490
0 commit comments