Skip to content

Commit dc7a50d

Browse files
authored
bpo-39350: Fix fractions for int subclasses (GH-18375)
Fix regression in fractions.Fraction if the numerator and/or the denominator is an int subclass. The math.gcd() function is now used to normalize the numerator and denominator. math.gcd() always return a int type. Previously, the GCD type depended on numerator and denominator.
1 parent 60ac6ed commit dc7a50d

File tree

4 files changed

+34
-7
lines changed

4 files changed

+34
-7
lines changed

Doc/library/fractions.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ another rational number, or from a string.
8484
The :class:`Fraction` constructor now accepts :class:`float` and
8585
:class:`decimal.Decimal` instances.
8686

87+
.. versionchanged:: 3.9
88+
The :func:`math.gcd` function is now used to normalize the *numerator*
89+
and *denominator*. :func:`math.gcd` always return a :class:`int` type.
90+
Previously, the GCD type depended on *numerator* and *denominator*.
8791

8892
.. attribute:: numerator
8993

Lib/fractions.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,9 @@ def __new__(cls, numerator=0, denominator=None, *, _normalize=True):
155155
if denominator == 0:
156156
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
157157
if _normalize:
158-
if type(numerator) is int is type(denominator):
159-
# *very* normal case
160-
g = math.gcd(numerator, denominator)
161-
if denominator < 0:
162-
g = -g
163-
else:
164-
g = _gcd(numerator, denominator)
158+
g = math.gcd(numerator, denominator)
159+
if denominator < 0:
160+
g = -g
165161
numerator //= g
166162
denominator //= g
167163
self._numerator = numerator

Lib/test/test_fractions.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,28 @@ def test_slots(self):
703703
r = F(13, 7)
704704
self.assertRaises(AttributeError, setattr, r, 'a', 10)
705705

706+
def test_int_subclass(self):
707+
class myint(int):
708+
def __mul__(self, other):
709+
return type(self)(int(self) * int(other))
710+
def __floordiv__(self, other):
711+
return type(self)(int(self) // int(other))
712+
def __mod__(self, other):
713+
x = type(self)(int(self) % int(other))
714+
return x
715+
@property
716+
def numerator(self):
717+
return type(self)(int(self))
718+
@property
719+
def denominator(self):
720+
return type(self)(1)
721+
722+
f = fractions.Fraction(myint(1 * 3), myint(2 * 3))
723+
self.assertEqual(f.numerator, 1)
724+
self.assertEqual(f.denominator, 2)
725+
self.assertEqual(type(f.numerator), myint)
726+
self.assertEqual(type(f.denominator), myint)
727+
706728

707729
if __name__ == '__main__':
708730
unittest.main()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix regression in :class:`fractions.Fraction` if the numerator and/or the
2+
denominator is an :class:`int` subclass. The :func:`math.gcd` function is now
3+
used to normalize the *numerator* and *denominator*. :func:`math.gcd` always
4+
return a :class:`int` type. Previously, the GCD type depended on *numerator*
5+
and *denominator*.

0 commit comments

Comments
 (0)