Skip to content

Commit ae819ca

Browse files
miss-islingtonmdickinsonskirpichev
authored
[3.11] gh-68163: Correct conversion of Rational instances to float (GH-25619) (#96556)
Co-authored-by: Mark Dickinson <dickinsm@gmail.com> Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
1 parent e72f469 commit ae819ca

File tree

4 files changed

+36
-4
lines changed

4 files changed

+36
-4
lines changed

Doc/library/numbers.rst

+6-3
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,14 @@ The numeric tower
5858

5959
.. class:: Rational
6060

61-
Subtypes :class:`Real` and adds
62-
:attr:`~Rational.numerator` and :attr:`~Rational.denominator` properties, which
63-
should be in lowest terms. With these, it provides a default for
61+
Subtypes :class:`Real` and adds :attr:`~Rational.numerator` and
62+
:attr:`~Rational.denominator` properties. It also provides a default for
6463
:func:`float`.
6564

65+
The :attr:`~Rational.numerator` and :attr:`~Rational.denominator` values
66+
should be instances of :class:`Integral` and should be in lowest terms with
67+
:attr:`~Rational.denominator` positive.
68+
6669
.. attribute:: numerator
6770

6871
Abstract.

Lib/numbers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def __float__(self):
288288
so that ratios of huge integers convert without overflowing.
289289
290290
"""
291-
return self.numerator / self.denominator
291+
return int(self.numerator) / int(self.denominator)
292292

293293

294294
class Integral(Rational):

Lib/test/test_numeric_tower.py

+28
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@
1414
_PyHASH_MODULUS = sys.hash_info.modulus
1515
_PyHASH_INF = sys.hash_info.inf
1616

17+
18+
class DummyIntegral(int):
19+
"""Dummy Integral class to test conversion of the Rational to float."""
20+
21+
def __mul__(self, other):
22+
return DummyIntegral(super().__mul__(other))
23+
__rmul__ = __mul__
24+
25+
def __truediv__(self, other):
26+
return NotImplemented
27+
__rtruediv__ = __truediv__
28+
29+
@property
30+
def numerator(self):
31+
return DummyIntegral(self)
32+
33+
@property
34+
def denominator(self):
35+
return DummyIntegral(1)
36+
37+
1738
class HashTest(unittest.TestCase):
1839
def check_equal_hash(self, x, y):
1940
# check both that x and y are equal and that their hashes are equal
@@ -121,6 +142,13 @@ def test_fractions(self):
121142
self.assertEqual(hash(F(7*_PyHASH_MODULUS, 1)), 0)
122143
self.assertEqual(hash(F(-_PyHASH_MODULUS, 1)), 0)
123144

145+
# The numbers ABC doesn't enforce that the "true" division
146+
# of integers produces a float. This tests that the
147+
# Rational.__float__() method has required type conversions.
148+
x = F(DummyIntegral(1), DummyIntegral(2), _normalize=False)
149+
self.assertRaises(TypeError, lambda: x.numerator/x.denominator)
150+
self.assertEqual(float(x), 0.5)
151+
124152
def test_hash_normalization(self):
125153
# Test for a bug encountered while changing long_hash.
126154
#
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Correct conversion of :class:`numbers.Rational`'s to :class:`float`.

0 commit comments

Comments
 (0)