Skip to content
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

bpo-23975: Correct conversion of Rational’s to float #25619

Merged
merged 10 commits into from
Sep 4, 2022
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 @@ -313,7 +313,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`.