From 4326ad8f72053140aa658a0392a509c9da382670 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 2 Aug 2009 10:59:36 +0000 Subject: Issue #6595: Allow Decimal constructor to accept non-European decimal digits, as recommended by the specification. (Backport of r74279 from py3k.) --- Doc/library/decimal.rst | 6 ++++++ Lib/decimal.py | 27 ++++++++++----------------- Lib/test/test_decimal.py | 12 +++++++++--- Misc/NEWS | 4 ++++ 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 53caff8..ee87023 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -330,6 +330,12 @@ Decimal objects numeric-value ::= decimal-part [exponent-part] | infinity numeric-string ::= [sign] numeric-value | [sign] nan + If *value* is a unicode string then 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 + ``u'\uff10'`` through ``u'\uff19'``. + 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 digits, and an integer exponent. For example, ``Decimal((0, (1, 4, 1, 4), -3))`` diff --git a/Lib/decimal.py b/Lib/decimal.py index cc2c5a5..f36e846 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -554,20 +554,16 @@ class Decimal(object): intpart = m.group('int') if intpart is not None: # finite number - fracpart = m.group('frac') + fracpart = m.group('frac') or '' exp = int(m.group('exp') or '0') - if fracpart is not None: - self._int = str((intpart+fracpart).lstrip('0') or '0') - self._exp = exp - len(fracpart) - else: - self._int = str(intpart.lstrip('0') or '0') - self._exp = exp + self._int = str(int(intpart+fracpart)) + self._exp = exp - len(fracpart) self._is_special = False else: diag = m.group('diag') if diag is not None: # NaN - self._int = str(diag.lstrip('0')) + self._int = str(int(diag or '0')).lstrip('0') if m.group('signal'): self._exp = 'N' else: @@ -5402,29 +5398,26 @@ ExtendedContext = Context( # number between the optional sign and the optional exponent must have # at least one decimal digit, possibly after the decimal point. The # 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 _parser = re.compile(r""" # A numeric string consists of: # \s* (?P[-+])? # an optional sign, followed by either... ( - (?=[0-9]|\.[0-9]) # ...a number (with at least one digit) - (?P[0-9]*) # having a (possibly empty) integer part - (\.(?P[0-9]*))? # followed by an optional fractional part - (E(?P[-+]?[0-9]+))? # followed by an optional exponent, or... + (?=\d|\.\d) # ...a number (with at least one digit) + (?P\d*) # having a (possibly empty) integer part + (\.(?P\d*))? # followed by an optional fractional part + (E(?P[-+]?\d+))? # followed by an optional exponent, or... | Inf(inity)? # ...an infinity, or... | (?Ps)? # ...an (optionally signaling) NaN # NaN - (?P[0-9]*) # with (possibly empty) diagnostic info. + (?P\d*) # with (possibly empty) diagnostic info. ) # \s* \Z -""", re.VERBOSE | re.IGNORECASE).match +""", re.VERBOSE | re.IGNORECASE | re.UNICODE).match _all_zeros = re.compile('0*$').match _exact_half = re.compile('50*$').match diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 1ce65ec..0be901a 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -431,9 +431,6 @@ class DecimalExplicitConstructionTest(unittest.TestCase): self.assertEqual(str(Decimal(u'-Inf')), '-Infinity') self.assertEqual(str(Decimal(u'NaN123')), 'NaN123') - #but alternate unicode digits should not - self.assertEqual(str(Decimal(u'\uff11')), 'NaN') - def test_explicit_from_tuples(self): #zero @@ -540,6 +537,15 @@ class DecimalExplicitConstructionTest(unittest.TestCase): d = nc.create_decimal(prevdec) self.assertEqual(str(d), '5.00E+8') + def test_unicode_digits(self): + test_values = { + u'\uff11': '1', + u'\u0660.\u0660\u0663\u0667\u0662e-\u0663' : '0.0000372', + u'-nan\u0c68\u0c6a\u0c66\u0c66' : '-NaN2400', + } + for input, expected in test_values.items(): + self.assertEqual(str(Decimal(input)), expected) + class DecimalImplicitConstructionTest(unittest.TestCase): '''Unit tests for Implicit Construction cases of Decimal.''' diff --git a/Misc/NEWS b/Misc/NEWS index ad998b4..2423262 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -354,6 +354,10 @@ Core and Builtins 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 #6511: ZipFile now raises BadZipfile (instead of an IOError) when opening an empty or very small file. -- cgit v0.12