Skip to content

[3.11] gh-68163: Correct conversion of Rational instances to float (GH-25619) #96556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Doc/library/numbers.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Correct conversion of :class:`numbers.Rational`'s to :class:`float`.