Skip to content

Commit

Permalink
pythongh-68163: Correct conversion of Rational instances to float (py…
Browse files Browse the repository at this point in the history
…thonGH-25619) (pythonGH-96557)

* pythongh-68163: Correct conversion of Rational instances to float

Also document that numerator/denominator properties are instances of Integral.

Co-authored-by: Mark Dickinson <dickinsm@gmail.com>
(cherry picked from commit 8464b75)

Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
  • Loading branch information
miss-islington and skirpichev committed Sep 4, 2022
1 parent 3a56a93 commit 4dea99f
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 4 deletions.
9 changes: 6 additions & 3 deletions Doc/library/numbers.rst
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Lib/numbers.py
Expand Up @@ -288,7 +288,7 @@ def __float__(self):
so that ratios of huge integers convert without overflowing.
"""
return self.numerator / self.denominator
return int(self.numerator) / int(self.denominator)


class Integral(Rational):
Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_numeric_tower.py
Expand Up @@ -14,6 +14,27 @@
_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
Expand Down Expand Up @@ -121,6 +142,13 @@ def test_fractions(self):
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.
#
Expand Down
@@ -0,0 +1 @@
Correct conversion of :class:`numbers.Rational`'s to :class:`float`.

0 comments on commit 4dea99f

Please sign in to comment.