From 4dea99f18eeef15a6d69236146793c801d12c329 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 4 Sep 2022 05:40:24 -0700 Subject: gh-68163: Correct conversion of Rational instances to float (GH-25619) (GH-96557) * gh-68163: Correct conversion of Rational instances to float Also document that numerator/denominator properties are instances of Integral. Co-authored-by: Mark Dickinson (cherry picked from commit 8464b754c4168586b99e2135ccd2567e025625a9) Co-authored-by: Sergey B Kirpichev --- Doc/library/numbers.rst | 9 ++++--- Lib/numbers.py | 2 +- Lib/test/test_numeric_tower.py | 28 ++++++++++++++++++++++ .../2022-09-04-12-32-52.gh-issue-68163.h6TJCc.rst | 1 + 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-09-04-12-32-52.gh-issue-68163.h6TJCc.rst diff --git a/Doc/library/numbers.rst b/Doc/library/numbers.rst index b77845e..db06b08 100644 --- a/Doc/library/numbers.rst +++ b/Doc/library/numbers.rst @@ -58,11 +58,14 @@ The numeric tower .. class:: Rational - Subtypes :class:`Real` and adds - :attr:`~Rational.numerator` and :attr:`~Rational.denominator` properties, which - should be in lowest terms. With these, it provides a default for + Subtypes :class:`Real` and adds :attr:`~Rational.numerator` and + :attr:`~Rational.denominator` properties. It also provides a default for :func:`float`. + The :attr:`~Rational.numerator` and :attr:`~Rational.denominator` values + should be instances of :class:`Integral` and should be in lowest terms with + :attr:`~Rational.denominator` positive. + .. attribute:: numerator Abstract. diff --git a/Lib/numbers.py b/Lib/numbers.py index 5b98e64..0985dd8 100644 --- a/Lib/numbers.py +++ b/Lib/numbers.py @@ -288,7 +288,7 @@ class Rational(Real): so that ratios of huge integers convert without overflowing. """ - return self.numerator / self.denominator + return int(self.numerator) / int(self.denominator) class Integral(Rational): diff --git a/Lib/test/test_numeric_tower.py b/Lib/test/test_numeric_tower.py index c54dedb..9cd85e1 100644 --- a/Lib/test/test_numeric_tower.py +++ b/Lib/test/test_numeric_tower.py @@ -14,6 +14,27 @@ from fractions import Fraction as F _PyHASH_MODULUS = sys.hash_info.modulus _PyHASH_INF = sys.hash_info.inf + +class DummyIntegral(int): + """Dummy Integral class to test conversion of the Rational to float.""" + + def __mul__(self, other): + return DummyIntegral(super().__mul__(other)) + __rmul__ = __mul__ + + def __truediv__(self, other): + return NotImplemented + __rtruediv__ = __truediv__ + + @property + def numerator(self): + return DummyIntegral(self) + + @property + def denominator(self): + return DummyIntegral(1) + + class HashTest(unittest.TestCase): def check_equal_hash(self, x, y): # check both that x and y are equal and that their hashes are equal @@ -121,6 +142,13 @@ class HashTest(unittest.TestCase): self.assertEqual(hash(F(7*_PyHASH_MODULUS, 1)), 0) self.assertEqual(hash(F(-_PyHASH_MODULUS, 1)), 0) + # The numbers ABC doesn't enforce that the "true" division + # of integers produces a float. This tests that the + # Rational.__float__() method has required type conversions. + x = F(DummyIntegral(1), DummyIntegral(2), _normalize=False) + self.assertRaises(TypeError, lambda: x.numerator/x.denominator) + self.assertEqual(float(x), 0.5) + def test_hash_normalization(self): # Test for a bug encountered while changing long_hash. # diff --git a/Misc/NEWS.d/next/Library/2022-09-04-12-32-52.gh-issue-68163.h6TJCc.rst b/Misc/NEWS.d/next/Library/2022-09-04-12-32-52.gh-issue-68163.h6TJCc.rst new file mode 100644 index 0000000..756f6c9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-09-04-12-32-52.gh-issue-68163.h6TJCc.rst @@ -0,0 +1 @@ +Correct conversion of :class:`numbers.Rational`'s to :class:`float`. -- cgit v0.12