@@ -381,10 +381,79 @@ def reverse(b, a):
381
381
382
382
return forward , reverse
383
383
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
+
384
446
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 )
388
457
389
458
_add = functools .partial (_add_sub_ )
390
459
_add .__doc__ = 'a + b'
@@ -396,14 +465,26 @@ def _add_sub_(a, b, pm=int.__add__):
396
465
397
466
def _mul (a , b ):
398
467
"""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 )
400
474
401
475
__mul__ , __rmul__ = _operator_fallbacks (_mul , operator .mul )
402
476
403
477
def _div (a , b ):
404
478
"""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 )
407
488
408
489
__truediv__ , __rtruediv__ = _operator_fallbacks (_div , operator .truediv )
409
490
0 commit comments