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.
This commit is contained in:
Victor Stinner 2020-02-07 23:42:51 +01:00 committed by GitHub
parent 60ac6ed557
commit dc7a50d73a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 7 deletions

View File

@ -84,6 +84,10 @@ another rational number, or from a string.
The :class:`Fraction` constructor now accepts :class:`float` and The :class:`Fraction` constructor now accepts :class:`float` and
:class:`decimal.Decimal` instances. :class:`decimal.Decimal` instances.
.. versionchanged:: 3.9
The :func:`math.gcd` function is now used to normalize the *numerator*
and *denominator*. :func:`math.gcd` always return a :class:`int` type.
Previously, the GCD type depended on *numerator* and *denominator*.
.. attribute:: numerator .. attribute:: numerator

View File

@ -155,13 +155,9 @@ class Fraction(numbers.Rational):
if denominator == 0: if denominator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % numerator) raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
if _normalize: if _normalize:
if type(numerator) is int is type(denominator):
# *very* normal case
g = math.gcd(numerator, denominator) g = math.gcd(numerator, denominator)
if denominator < 0: if denominator < 0:
g = -g g = -g
else:
g = _gcd(numerator, denominator)
numerator //= g numerator //= g
denominator //= g denominator //= g
self._numerator = numerator self._numerator = numerator

View File

@ -703,6 +703,28 @@ class FractionTest(unittest.TestCase):
r = F(13, 7) r = F(13, 7)
self.assertRaises(AttributeError, setattr, r, 'a', 10) self.assertRaises(AttributeError, setattr, r, 'a', 10)
def test_int_subclass(self):
class myint(int):
def __mul__(self, other):
return type(self)(int(self) * int(other))
def __floordiv__(self, other):
return type(self)(int(self) // int(other))
def __mod__(self, other):
x = type(self)(int(self) % int(other))
return x
@property
def numerator(self):
return type(self)(int(self))
@property
def denominator(self):
return type(self)(1)
f = fractions.Fraction(myint(1 * 3), myint(2 * 3))
self.assertEqual(f.numerator, 1)
self.assertEqual(f.denominator, 2)
self.assertEqual(type(f.numerator), myint)
self.assertEqual(type(f.denominator), myint)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -0,0 +1,5 @@
Fix regression in :class:`fractions.Fraction` if the numerator and/or the
denominator is an :class:`int` subclass. The :func:`math.gcd` function is now
used to normalize the *numerator* and *denominator*. :func:`math.gcd` always
return a :class:`int` type. Previously, the GCD type depended on *numerator*
and *denominator*.