Skip to content

Commit cf63f2f

Browse files
committed
Issue #5812: Make Fraction('1e6') valid. The Fraction constructor now
accepts all strings accepted by the float and Decimal constructors, with the exception of strings representing NaNs or infinities.
1 parent 937491d commit cf63f2f

File tree

4 files changed

+47
-33
lines changed

4 files changed

+47
-33
lines changed

Doc/library/fractions.rst

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,18 @@ another rational number, or from a string.
2525
:exc:`ZeroDivisionError`. The second version requires that
2626
*other_fraction* is an instance of :class:`numbers.Rational` and
2727
returns an :class:`Fraction` instance with the same value. The
28-
last version of the constructor expects a string
29-
instance in one of two possible forms. The first form is::
28+
last version of the constructor expects a string instance. The
29+
usual form for this string is::
3030

3131
[sign] numerator ['/' denominator]
3232

3333
where the optional ``sign`` may be either '+' or '-' and
3434
``numerator`` and ``denominator`` (if present) are strings of
35-
decimal digits. The second permitted form is that of a number
36-
containing a decimal point::
37-
38-
[sign] integer '.' [fraction] | [sign] '.' fraction
39-
40-
where ``integer`` and ``fraction`` are strings of digits. In
41-
either form the input string may also have leading and/or trailing
42-
whitespace. Here are some examples::
35+
decimal digits. In addition, any string that represents a finite
36+
value and is accepted by the :class:`float` constructor is also
37+
accepted by the :class:`Fraction` constructor. In either form the
38+
input string may also have leading and/or trailing whitespace.
39+
Here are some examples::
4340

4441
>>> from fractions import Fraction
4542
>>> Fraction(16, -10)
@@ -57,6 +54,8 @@ another rational number, or from a string.
5754
Fraction(1414213, 1000000)
5855
>>> Fraction('-.125')
5956
Fraction(-1, 8)
57+
>>> Fraction('7e-6')
58+
Fraction(7, 1000000)
6059

6160

6261
The :class:`Fraction` class inherits from the abstract base class

Lib/fractions.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ def gcd(a, b):
2828
(?P<sign>[-+]?) # an optional sign, then
2929
(?=\d|\.\d) # lookahead for digit or .digit
3030
(?P<num>\d*) # numerator (possibly empty)
31-
(?: # followed by an optional
32-
/(?P<denom>\d+) # / and denominator
31+
(?: # followed by
32+
(?:/(?P<denom>\d+))? # an optional denominator
3333
| # or
34-
\.(?P<decimal>\d*) # decimal point and fractional part
35-
)?
34+
(?:\.(?P<decimal>\d*))? # an optional fractional part
35+
(?:E(?P<exp>[-+]?\d+))? # and optional exponent
36+
)
3637
\s*\Z # and optional whitespace to finish
37-
""", re.VERBOSE)
38+
""", re.VERBOSE | re.IGNORECASE)
3839

3940

4041
class Fraction(numbers.Rational):
@@ -65,22 +66,28 @@ def __new__(cls, numerator=0, denominator=1):
6566
if not isinstance(numerator, int) and denominator == 1:
6667
if isinstance(numerator, str):
6768
# Handle construction from strings.
68-
input = numerator
69-
m = _RATIONAL_FORMAT.match(input)
69+
m = _RATIONAL_FORMAT.match(numerator)
7070
if m is None:
71-
raise ValueError('Invalid literal for Fraction: %r' % input)
72-
numerator = m.group('num')
73-
decimal = m.group('decimal')
74-
if decimal:
75-
# The literal is a decimal number.
76-
numerator = int(numerator + decimal)
77-
denominator = 10**len(decimal)
71+
raise ValueError('Invalid literal for Fraction: %r' %
72+
numerator)
73+
numerator = int(m.group('num') or '0')
74+
denom = m.group('denom')
75+
if denom:
76+
denominator = int(denom)
7877
else:
79-
# The literal is an integer or fraction.
80-
numerator = int(numerator)
81-
# Default denominator to 1.
82-
denominator = int(m.group('denom') or 1)
83-
78+
denominator = 1
79+
decimal = m.group('decimal')
80+
if decimal:
81+
scale = 10**len(decimal)
82+
numerator = numerator * scale + int(decimal)
83+
denominator *= scale
84+
exp = m.group('exp')
85+
if exp:
86+
exp = int(exp)
87+
if exp >= 0:
88+
numerator *= 10**exp
89+
else:
90+
denominator *= 10**-exp
8491
if m.group('sign') == '-':
8592
numerator = -numerator
8693

Lib/test/test_fractions.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,21 @@ def testFromString(self):
7878
self.assertEquals((-16, 5), _components(F(" -3.2 ")))
7979
self.assertEquals((-3, 1), _components(F(" -3. ")))
8080
self.assertEquals((3, 5), _components(F(" .6 ")))
81+
self.assertEquals((1, 3125), _components(F("32.e-5")))
82+
self.assertEquals((1000000, 1), _components(F("1E+06")))
83+
self.assertEquals((-12300, 1), _components(F("-1.23e4")))
84+
self.assertEquals((0, 1), _components(F(" .0e+0\t")))
85+
self.assertEquals((0, 1), _components(F("-0.000e0")))
8186

8287
self.assertRaisesMessage(
8388
ZeroDivisionError, "Fraction(3, 0)",
8489
F, "3/0")
8590
self.assertRaisesMessage(
8691
ValueError, "Invalid literal for Fraction: '3/'",
8792
F, "3/")
93+
self.assertRaisesMessage(
94+
ValueError, "Invalid literal for Fraction: '/2'",
95+
F, "/2")
8896
self.assertRaisesMessage(
8997
ValueError, "Invalid literal for Fraction: '3 /2'",
9098
F, "3 /2")
@@ -100,10 +108,6 @@ def testFromString(self):
100108
# Avoid treating '.' as a regex special character.
101109
ValueError, "Invalid literal for Fraction: '3a2'",
102110
F, "3a2")
103-
self.assertRaisesMessage(
104-
# Only parse ordinary decimals, not scientific form.
105-
ValueError, "Invalid literal for Fraction: '3.2e4'",
106-
F, "3.2e4")
107111
self.assertRaisesMessage(
108112
# Don't accept combinations of decimals and rationals.
109113
ValueError, "Invalid literal for Fraction: '3/7.2'",

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ Core and Builtins
7575
Library
7676
-------
7777

78+
- Issue #5812: Fraction('1e6') is valid: more generally, any string
79+
that's valid for float() is now valid for Fraction(), with the
80+
exception of strings representing NaNs and infinities.
81+
7882
- Issue #5734: BufferedRWPair was poorly tested and had several glaring
7983
bugs. Patch by Brian Quinlan.
8084

0 commit comments

Comments
 (0)