bpo-43420: Simple optimizations for Fraction's arithmetics (GH-24779) · python/cpython@690aca7 (original) (raw)
`@@ -380,32 +380,139 @@ def reverse(b, a):
`
380
380
``
381
381
`return forward, reverse
`
382
382
``
``
383
`+
Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1.
`
``
384
`+
`
``
385
`+
Assume input fractions a and b are normalized.
`
``
386
`+
`
``
387
`+
1) Consider addition/subtraction.
`
``
388
`+
`
``
389
`+
Let g = gcd(da, db). Then
`
``
390
`+
`
``
391
`+
na nb nadb ± nbda
`
``
392
`+
a ± b == -- ± -- == ------------- ==
`
``
393
`+
da db da*db
`
``
394
`+
`
``
395
`+
na*(db//g) ± nb*(da//g) t
`
``
396
`+
== ----------------------- == -
`
``
397
`+
(da*db)//g d
`
``
398
`+
`
``
399
`+
Now, if g > 1, we're working with smaller integers.
`
``
400
`+
`
``
401
`+
Note, that t, (da//g) and (db//g) are pairwise coprime.
`
``
402
`+
`
``
403
`+
Indeed, (da//g) and (db//g) share no common factors (they were
`
``
404
`+
removed) and da is coprime with na (since input fractions are
`
``
405
`+
normalized), hence (da//g) and na are coprime. By symmetry,
`
``
406
`+
(db//g) and nb are coprime too. Then,
`
``
407
`+
`
``
408
`+
gcd(t, da//g) == gcd(na*(db//g), da//g) == 1
`
``
409
`+
gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1
`
``
410
`+
`
``
411
`+
Above allows us optimize reduction of the result to lowest
`
``
412
`+
terms. Indeed,
`
``
413
`+
`
``
414
`+
g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g)
`
``
415
`+
`
``
416
`+
t//g2 t//g2
`
``
417
`+
a ± b == ----------------------- == ----------------
`
``
418
`+
(da//g)(db//g)(g//g2) (da//g)*(db//g2)
`
``
419
`+
`
``
420
`+
is a normalized fraction. This is useful because the unnormalized
`
``
421
`+
denominator d could be much larger than g.
`
``
422
`+
`
``
423
`+
We should special-case g == 1 (and g2 == 1), since 60.8% of
`
``
424
`+
randomly-chosen integers are coprime:
`
``
425
`+
https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality
`
``
426
`+
Note, that g2 == 1 always for fractions, obtained from floats: here
`
``
427
`+
g is a power of 2 and the unnormalized numerator t is an odd integer.
`
``
428
`+
`
``
429
`+
2) Consider multiplication
`
``
430
`+
`
``
431
`+
Let g1 = gcd(na, db) and g2 = gcd(nb, da), then
`
``
432
`+
`
``
433
`+
nanb nanb (na//g1)*(nb//g2)
`
``
434
`+
a*b == ----- == ----- == -----------------
`
``
435
`+
dadb dbda (db//g1)*(da//g2)
`
``
436
`+
`
``
437
`+
Note, that after divisions we're multiplying smaller integers.
`
``
438
`+
`
``
439
`+
Also, the resulting fraction is normalized, because each of
`
``
440
`+
two factors in the numerator is coprime to each of the two factors
`
``
441
`+
in the denominator.
`
``
442
`+
`
``
443
`+
Indeed, pick (na//g1). It's coprime with (da//g2), because input
`
``
444
`+
fractions are normalized. It's also coprime with (db//g1), because
`
``
445
`+
common factors are removed by g1 == gcd(na, db).
`
``
446
`+
`
``
447
`+
As for addition/subtraction, we should special-case g1 == 1
`
``
448
`+
and g2 == 1 for same reason. That happens also for multiplying
`
``
449
`+
rationals, obtained from floats.
`
``
450
+
383
451
`def _add(a, b):
`
384
452
`"""a + b"""
`
385
``
`-
da, db = a.denominator, b.denominator
`
386
``
`-
return Fraction(a.numerator * db + b.numerator * da,
`
387
``
`-
da * db)
`
``
453
`+
na, da = a.numerator, a.denominator
`
``
454
`+
nb, db = b.numerator, b.denominator
`
``
455
`+
g = math.gcd(da, db)
`
``
456
`+
if g == 1:
`
``
457
`+
return Fraction(na * db + da * nb, da * db, _normalize=False)
`
``
458
`+
s = da // g
`
``
459
`+
t = na * (db // g) + nb * s
`
``
460
`+
g2 = math.gcd(t, g)
`
``
461
`+
if g2 == 1:
`
``
462
`+
return Fraction(t, s * db, _normalize=False)
`
``
463
`+
return Fraction(t // g2, s * (db // g2), _normalize=False)
`
388
464
``
389
465
`add, radd = _operator_fallbacks(_add, operator.add)
`
390
466
``
391
467
`def _sub(a, b):
`
392
468
`"""a - b"""
`
393
``
`-
da, db = a.denominator, b.denominator
`
394
``
`-
return Fraction(a.numerator * db - b.numerator * da,
`
395
``
`-
da * db)
`
``
469
`+
na, da = a.numerator, a.denominator
`
``
470
`+
nb, db = b.numerator, b.denominator
`
``
471
`+
g = math.gcd(da, db)
`
``
472
`+
if g == 1:
`
``
473
`+
return Fraction(na * db - da * nb, da * db, _normalize=False)
`
``
474
`+
s = da // g
`
``
475
`+
t = na * (db // g) - nb * s
`
``
476
`+
g2 = math.gcd(t, g)
`
``
477
`+
if g2 == 1:
`
``
478
`+
return Fraction(t, s * db, _normalize=False)
`
``
479
`+
return Fraction(t // g2, s * (db // g2), _normalize=False)
`
396
480
``
397
481
`sub, rsub = _operator_fallbacks(_sub, operator.sub)
`
398
482
``
399
483
`def _mul(a, b):
`
400
484
`"""a * b"""
`
401
``
`-
return Fraction(a.numerator * b.numerator, a.denominator * b.denominator)
`
``
485
`+
na, da = a.numerator, a.denominator
`
``
486
`+
nb, db = b.numerator, b.denominator
`
``
487
`+
g1 = math.gcd(na, db)
`
``
488
`+
if g1 > 1:
`
``
489
`+
na //= g1
`
``
490
`+
db //= g1
`
``
491
`+
g2 = math.gcd(nb, da)
`
``
492
`+
if g2 > 1:
`
``
493
`+
nb //= g2
`
``
494
`+
da //= g2
`
``
495
`+
return Fraction(na * nb, db * da, _normalize=False)
`
402
496
``
403
497
`mul, rmul = _operator_fallbacks(_mul, operator.mul)
`
404
498
``
405
499
`def _div(a, b):
`
406
500
`"""a / b"""
`
407
``
`-
return Fraction(a.numerator * b.denominator,
`
408
``
`-
a.denominator * b.numerator)
`
``
501
`+
Same as _mul(), with inversed b.
`
``
502
`+
na, da = a.numerator, a.denominator
`
``
503
`+
nb, db = b.numerator, b.denominator
`
``
504
`+
g1 = math.gcd(na, nb)
`
``
505
`+
if g1 > 1:
`
``
506
`+
na //= g1
`
``
507
`+
nb //= g1
`
``
508
`+
g2 = math.gcd(db, da)
`
``
509
`+
if g2 > 1:
`
``
510
`+
da //= g2
`
``
511
`+
db //= g2
`
``
512
`+
n, d = na * db, nb * da
`
``
513
`+
if d < 0:
`
``
514
`+
n, d = -n, -d
`
``
515
`+
return Fraction(n, d, _normalize=False)
`
409
516
``
410
517
`truediv, rtruediv = _operator_fallbacks(_div, operator.truediv)
`
411
518
``