From 9b5e23148badbb0d11fffd05cf9d432f35631a4a Mon Sep 17 00:00:00 2001 From: Facundo Batista Date: Fri, 19 Oct 2007 19:25:57 +0000 Subject: [PATCH] The constructor from tuple was way too permissive: it allowed bad coefficient numbers, floats in the sign, and other details that generated directly the wrong number in the best case, or triggered misfunctionality in the alorithms. Test cases added for these issues. Thanks Mark Dickinson. --- Lib/decimal.py | 46 +++++++++++++++++++++++++++++++--------- Lib/test/test_decimal.py | 27 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/Lib/decimal.py b/Lib/decimal.py index fa41722c240..45ecf88f003 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -562,20 +562,46 @@ class Decimal(object): # tuple/list conversion (possibly from as_tuple()) if isinstance(value, (list,tuple)): if len(value) != 3: - raise ValueError('Invalid arguments') - if value[0] not in (0,1): - raise ValueError('Invalid sign') - for digit in value[1]: - if not isinstance(digit, (int,long)) or digit < 0: - raise ValueError("The second value in the tuple must be " - "composed of non negative integer elements.") + raise ValueError('Invalid tuple size in creation of Decimal ' + 'from list or tuple. The list or tuple ' + 'should have exactly three elements.') + # process sign. The isinstance test rejects floats + if not (isinstance(value[0], (int, long)) and value[0] in (0,1)): + raise ValueError("Invalid sign. The first value in the tuple " + "should be an integer; either 0 for a " + "positive number or 1 for a negative number.") self._sign = value[0] - self._int = tuple(value[1]) - if value[2] in ('F','n','N'): + if value[2] == 'F': + # infinity: value[1] is ignored + self._int = (0,) self._exp = value[2] self._is_special = True else: - self._exp = int(value[2]) + # process and validate the digits in value[1] + digits = [] + for digit in value[1]: + if isinstance(digit, (int, long)) and 0 <= digit <= 9: + # skip leading zeros + if digits or digit != 0: + digits.append(digit) + else: + raise ValueError("The second value in the tuple must " + "be composed of integers in the range " + "0 through 9.") + if value[2] in ('n', 'N'): + # NaN: digits form the diagnostic + self._int = tuple(digits) + self._exp = value[2] + self._is_special = True + elif isinstance(value[2], (int, long)): + # finite number: digits give the coefficient + self._int = tuple(digits or [0]) + self._exp = value[2] + self._is_special = False + else: + raise ValueError("The third value in the tuple must " + "be an integer, or one of the " + "strings 'F', 'n', 'N'.") return self if isinstance(value, float): diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index e4b0ae50ac3..27d1aa618c6 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -452,13 +452,18 @@ class DecimalExplicitConstructionTest(unittest.TestCase): #bad sign self.assertRaises(ValueError, Decimal, (8, (4, 3, 4, 9, 1), 2) ) + self.assertRaises(ValueError, Decimal, (0., (4, 3, 4, 9, 1), 2) ) + self.assertRaises(ValueError, Decimal, (Decimal(1), (4, 3, 4, 9, 1), 2)) #bad exp self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), 'wrong!') ) + self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), 0.) ) + self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), '1') ) #bad coefficients self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, None, 1), 2) ) self.assertRaises(ValueError, Decimal, (1, (4, -3, 4, 9, 1), 2) ) + self.assertRaises(ValueError, Decimal, (1, (4, 10, 4, 9, 1), 2) ) def test_explicit_from_Decimal(self): @@ -1060,6 +1065,28 @@ class DecimalUsabilityTest(unittest.TestCase): d = Decimal("Infinity") self.assertEqual(d.as_tuple(), (0, (0,), 'F') ) + #leading zeros in coefficient should be stripped + d = Decimal( (0, (0, 0, 4, 0, 5, 3, 4), -2) ) + self.assertEqual(d.as_tuple(), (0, (4, 0, 5, 3, 4), -2) ) + d = Decimal( (1, (0, 0, 0), 37) ) + self.assertEqual(d.as_tuple(), (1, (0,), 37)) + d = Decimal( (1, (), 37) ) + self.assertEqual(d.as_tuple(), (1, (0,), 37)) + + #leading zeros in NaN diagnostic info should be stripped + d = Decimal( (0, (0, 0, 4, 0, 5, 3, 4), 'n') ) + self.assertEqual(d.as_tuple(), (0, (4, 0, 5, 3, 4), 'n') ) + d = Decimal( (1, (0, 0, 0), 'N') ) + self.assertEqual(d.as_tuple(), (1, (), 'N') ) + d = Decimal( (1, (), 'n') ) + self.assertEqual(d.as_tuple(), (1, (), 'n') ) + + #coefficient in infinity should be ignored + d = Decimal( (0, (4, 5, 3, 4), 'F') ) + self.assertEqual(d.as_tuple(), (0, (0,), 'F')) + d = Decimal( (1, (0, 2, 7, 1), 'F') ) + self.assertEqual(d.as_tuple(), (1, (0,), 'F')) + def test_immutability_operations(self): # Do operations and check that it didn't change change internal objects.