Commit 345adc43 authored by Mark Dickinson's avatar Mark Dickinson

Issue #6595: Allow Decimal constructor to accept non-European decimal digits,...

Issue #6595: Allow Decimal constructor to accept non-European decimal digits, as recommended by the specification.
parent 642d96a6
...@@ -324,6 +324,11 @@ Decimal objects ...@@ -324,6 +324,11 @@ Decimal objects
numeric-value ::= decimal-part [exponent-part] | infinity numeric-value ::= decimal-part [exponent-part] | infinity
numeric-string ::= [sign] numeric-value | [sign] nan numeric-string ::= [sign] numeric-value | [sign] nan
Other Unicode decimal digits are also permitted where ``digit``
appears above. These include decimal digits from various other
alphabets (for example, Arabic-Indic and Devanāgarī digits) along
with the fullwidth digits ``'\uff10'`` through ``'\uff19'``.
If *value* is a :class:`tuple`, it should have three components, a sign If *value* is a :class:`tuple`, it should have three components, a sign
(:const:`0` for positive or :const:`1` for negative), a :class:`tuple` of (:const:`0` for positive or :const:`1` for negative), a :class:`tuple` of
digits, and an integer exponent. For example, ``Decimal((0, (1, 4, 1, 4), -3))`` digits, and an integer exponent. For example, ``Decimal((0, (1, 4, 1, 4), -3))``
......
...@@ -554,20 +554,16 @@ class Decimal(object): ...@@ -554,20 +554,16 @@ class Decimal(object):
intpart = m.group('int') intpart = m.group('int')
if intpart is not None: if intpart is not None:
# finite number # finite number
fracpart = m.group('frac') fracpart = m.group('frac') or ''
exp = int(m.group('exp') or '0') exp = int(m.group('exp') or '0')
if fracpart is not None: self._int = str(int(intpart+fracpart))
self._int = (intpart+fracpart).lstrip('0') or '0'
self._exp = exp - len(fracpart) self._exp = exp - len(fracpart)
else:
self._int = intpart.lstrip('0') or '0'
self._exp = exp
self._is_special = False self._is_special = False
else: else:
diag = m.group('diag') diag = m.group('diag')
if diag is not None: if diag is not None:
# NaN # NaN
self._int = diag.lstrip('0') self._int = str(int(diag or '0')).lstrip('0')
if m.group('signal'): if m.group('signal'):
self._exp = 'N' self._exp = 'N'
else: else:
...@@ -5482,26 +5478,23 @@ ExtendedContext = Context( ...@@ -5482,26 +5478,23 @@ ExtendedContext = Context(
# 2. For finite numbers (not infinities and NaNs) the body of the # 2. For finite numbers (not infinities and NaNs) the body of the
# number between the optional sign and the optional exponent must have # number between the optional sign and the optional exponent must have
# at least one decimal digit, possibly after the decimal point. The # at least one decimal digit, possibly after the decimal point. The
# lookahead expression '(?=[0-9]|\.[0-9])' checks this. # lookahead expression '(?=\d|\.\d)' checks this.
#
# As the flag UNICODE is not enabled here, we're explicitly avoiding any
# other meaning for \d than the numbers [0-9].
import re import re
_parser = re.compile(r""" # A numeric string consists of: _parser = re.compile(r""" # A numeric string consists of:
# \s* # \s*
(?P<sign>[-+])? # an optional sign, followed by either... (?P<sign>[-+])? # an optional sign, followed by either...
( (
(?=[0-9]|\.[0-9]) # ...a number (with at least one digit) (?=\d|\.\d) # ...a number (with at least one digit)
(?P<int>[0-9]*) # having a (possibly empty) integer part (?P<int>\d*) # having a (possibly empty) integer part
(\.(?P<frac>[0-9]*))? # followed by an optional fractional part (\.(?P<frac>\d*))? # followed by an optional fractional part
(E(?P<exp>[-+]?[0-9]+))? # followed by an optional exponent, or... (E(?P<exp>[-+]?\d+))? # followed by an optional exponent, or...
| |
Inf(inity)? # ...an infinity, or... Inf(inity)? # ...an infinity, or...
| |
(?P<signal>s)? # ...an (optionally signaling) (?P<signal>s)? # ...an (optionally signaling)
NaN # NaN NaN # NaN
(?P<diag>[0-9]*) # with (possibly empty) diagnostic info. (?P<diag>\d*) # with (possibly empty) diagnostic info.
) )
# \s* # \s*
\Z \Z
......
...@@ -425,9 +425,6 @@ class DecimalExplicitConstructionTest(unittest.TestCase): ...@@ -425,9 +425,6 @@ class DecimalExplicitConstructionTest(unittest.TestCase):
self.assertEqual(str(Decimal('1.3E4 \n')), '1.3E+4') self.assertEqual(str(Decimal('1.3E4 \n')), '1.3E+4')
self.assertEqual(str(Decimal(' -7.89')), '-7.89') self.assertEqual(str(Decimal(' -7.89')), '-7.89')
#but alternate unicode digits should not
self.assertEqual(str(Decimal('\uff11')), 'NaN')
def test_explicit_from_tuples(self): def test_explicit_from_tuples(self):
#zero #zero
...@@ -534,6 +531,15 @@ class DecimalExplicitConstructionTest(unittest.TestCase): ...@@ -534,6 +531,15 @@ class DecimalExplicitConstructionTest(unittest.TestCase):
d = nc.create_decimal(prevdec) d = nc.create_decimal(prevdec)
self.assertEqual(str(d), '5.00E+8') self.assertEqual(str(d), '5.00E+8')
def test_unicode_digits(self):
test_values = {
'\uff11': '1',
'\u0660.\u0660\u0663\u0667\u0662e-\u0663' : '0.0000372',
'-nan\u0c68\u0c6a\u0c66\u0c66' : '-NaN2400',
}
for input, expected in test_values.items():
self.assertEqual(str(Decimal(input)), expected)
class DecimalImplicitConstructionTest(unittest.TestCase): class DecimalImplicitConstructionTest(unittest.TestCase):
'''Unit tests for Implicit Construction cases of Decimal.''' '''Unit tests for Implicit Construction cases of Decimal.'''
......
...@@ -63,6 +63,10 @@ C-API ...@@ -63,6 +63,10 @@ C-API
Library Library
------- -------
- Issue #6595: The Decimal constructor now allows arbitrary Unicode
decimal digits in input, as recommended by the standard. Previously
it was restricted to accepting [0-9].
- Issue #6106: telnetlib.Telnet.process_rawq doesn't handle default WILL/WONT - Issue #6106: telnetlib.Telnet.process_rawq doesn't handle default WILL/WONT
DO/DONT correctly. DO/DONT correctly.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment