Skip to content

Commit 9cc62f7

Browse files
committed
gh-101773: Optimize creation of Fraction's in private methods
1 parent 448c7d1 commit 9cc62f7

File tree

4 files changed

+42
-32
lines changed

4 files changed

+42
-32
lines changed

Lib/fractions.py

+39-31
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ class Fraction(numbers.Rational):
183183
__slots__ = ('_numerator', '_denominator')
184184

185185
# We're immutable, so use __new__ not __init__
186-
def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
186+
def __new__(cls, numerator=0, denominator=None):
187187
"""Constructs a Rational.
188188
189189
Takes a string like '3/2' or '1.5', another Rational instance, a
@@ -279,12 +279,11 @@ def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
279279

280280
if denominator == 0:
281281
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
282-
if _normalize:
283-
g = math.gcd(numerator, denominator)
284-
if denominator < 0:
285-
g = -g
286-
numerator //= g
287-
denominator //= g
282+
g = math.gcd(numerator, denominator)
283+
if denominator < 0:
284+
g = -g
285+
numerator //= g
286+
denominator //= g
288287
self._numerator = numerator
289288
self._denominator = denominator
290289
return self
@@ -315,6 +314,13 @@ def from_decimal(cls, dec):
315314
(cls.__name__, dec, type(dec).__name__))
316315
return cls(*dec.as_integer_ratio())
317316

317+
@classmethod
318+
def _from_pair(cls, num, den):
319+
obj = super(Fraction, cls).__new__(cls)
320+
obj._numerator = num
321+
obj._denominator = den
322+
return obj
323+
318324
def is_integer(self):
319325
"""Return True if the Fraction is an integer."""
320326
return self._denominator == 1
@@ -380,9 +386,9 @@ def limit_denominator(self, max_denominator=1000000):
380386
# the distance from p1/q1 to self is d/(q1*self._denominator). So we
381387
# need to compare 2*(q0+k*q1) with self._denominator/d.
382388
if 2*d*(q0+k*q1) <= self._denominator:
383-
return Fraction(p1, q1, _normalize=False)
389+
return Fraction._from_pair(p1, q1)
384390
else:
385-
return Fraction(p0+k*p1, q0+k*q1, _normalize=False)
391+
return Fraction._from_pair(p0+k*p1, q0+k*q1)
386392

387393
@property
388394
def numerator(a):
@@ -703,13 +709,13 @@ def _add(a, b):
703709
nb, db = b._numerator, b._denominator
704710
g = math.gcd(da, db)
705711
if g == 1:
706-
return Fraction(na * db + da * nb, da * db, _normalize=False)
712+
return Fraction._from_pair(na * db + da * nb, da * db)
707713
s = da // g
708714
t = na * (db // g) + nb * s
709715
g2 = math.gcd(t, g)
710716
if g2 == 1:
711-
return Fraction(t, s * db, _normalize=False)
712-
return Fraction(t // g2, s * (db // g2), _normalize=False)
717+
return Fraction._from_pair(t, s * db)
718+
return Fraction._from_pair(t // g2, s * (db // g2))
713719

714720
__add__, __radd__ = _operator_fallbacks(_add, operator.add)
715721

@@ -719,13 +725,13 @@ def _sub(a, b):
719725
nb, db = b._numerator, b._denominator
720726
g = math.gcd(da, db)
721727
if g == 1:
722-
return Fraction(na * db - da * nb, da * db, _normalize=False)
728+
return Fraction._from_pair(na * db - da * nb, da * db)
723729
s = da // g
724730
t = na * (db // g) - nb * s
725731
g2 = math.gcd(t, g)
726732
if g2 == 1:
727-
return Fraction(t, s * db, _normalize=False)
728-
return Fraction(t // g2, s * (db // g2), _normalize=False)
733+
return Fraction._from_pair(t, s * db)
734+
return Fraction._from_pair(t // g2, s * (db // g2))
729735

730736
__sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub)
731737

@@ -741,15 +747,17 @@ def _mul(a, b):
741747
if g2 > 1:
742748
nb //= g2
743749
da //= g2
744-
return Fraction(na * nb, db * da, _normalize=False)
750+
return Fraction._from_pair(na * nb, db * da)
745751

746752
__mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul)
747753

748754
def _div(a, b):
749755
"""a / b"""
750756
# Same as _mul(), with inversed b.
751-
na, da = a._numerator, a._denominator
752757
nb, db = b._numerator, b._denominator
758+
if nb == 0:
759+
raise ZeroDivisionError('Fraction(%s, 0)' % db)
760+
na, da = a._numerator, a._denominator
753761
g1 = math.gcd(na, nb)
754762
if g1 > 1:
755763
na //= g1
@@ -761,7 +769,7 @@ def _div(a, b):
761769
n, d = na * db, nb * da
762770
if d < 0:
763771
n, d = -n, -d
764-
return Fraction(n, d, _normalize=False)
772+
return Fraction._from_pair(n, d)
765773

766774
__truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv)
767775

@@ -798,17 +806,17 @@ def __pow__(a, b):
798806
if b.denominator == 1:
799807
power = b.numerator
800808
if power >= 0:
801-
return Fraction(a._numerator ** power,
802-
a._denominator ** power,
803-
_normalize=False)
804-
elif a._numerator >= 0:
805-
return Fraction(a._denominator ** -power,
806-
a._numerator ** -power,
807-
_normalize=False)
809+
return Fraction._from_pair(a._numerator ** power,
810+
a._denominator ** power)
811+
elif a._numerator > 0:
812+
return Fraction._from_pair(a._denominator ** -power,
813+
a._numerator ** -power)
814+
elif a._numerator == 0:
815+
raise ZeroDivisionError('Fraction(%s, 0)' %
816+
a._denominator ** -power)
808817
else:
809-
return Fraction((-a._denominator) ** -power,
810-
(-a._numerator) ** -power,
811-
_normalize=False)
818+
return Fraction._from_pair((-a._denominator) ** -power,
819+
(-a._numerator) ** -power)
812820
else:
813821
# A fractional power will generally produce an
814822
# irrational number.
@@ -832,15 +840,15 @@ def __rpow__(b, a):
832840

833841
def __pos__(a):
834842
"""+a: Coerces a subclass instance to Fraction"""
835-
return Fraction(a._numerator, a._denominator, _normalize=False)
843+
return Fraction._from_pair(a._numerator, a._denominator)
836844

837845
def __neg__(a):
838846
"""-a"""
839-
return Fraction(-a._numerator, a._denominator, _normalize=False)
847+
return Fraction._from_pair(-a._numerator, a._denominator)
840848

841849
def __abs__(a):
842850
"""abs(a)"""
843-
return Fraction(abs(a._numerator), a._denominator, _normalize=False)
851+
return Fraction._from_pair(abs(a._numerator), a._denominator)
844852

845853
def __int__(a, _index=operator.index):
846854
"""int(a)"""

Lib/test/test_fractions.py

+1
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ def testArithmetic(self):
488488
self.assertEqual(F(5, 6), F(2, 3) * F(5, 4))
489489
self.assertEqual(F(1, 4), F(1, 10) / F(2, 5))
490490
self.assertEqual(F(-15, 8), F(3, 4) / F(-2, 5))
491+
self.assertRaises(ZeroDivisionError, operator.truediv, F(1), F(0))
491492
self.assertTypedEquals(2, F(9, 10) // F(2, 5))
492493
self.assertTypedEquals(10**23, F(10**23, 1) // F(1))
493494
self.assertEqual(F(5, 6), F(7, 3) % F(3, 2))

Lib/test/test_numeric_tower.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def test_fractions(self):
145145
# The numbers ABC doesn't enforce that the "true" division
146146
# of integers produces a float. This tests that the
147147
# Rational.__float__() method has required type conversions.
148-
x = F(DummyIntegral(1), DummyIntegral(2), _normalize=False)
148+
x = F._from_pair(DummyIntegral(1), DummyIntegral(2))
149149
self.assertRaises(TypeError, lambda: x.numerator/x.denominator)
150150
self.assertEqual(float(x), 0.5)
151151

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Optimize :class:`fractions.Fraction` for small components.

0 commit comments

Comments
 (0)