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:
parent
60ac6ed557
commit
dc7a50d73a
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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*.
|
Loading…
Reference in New Issue