diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -78,14 +78,32 @@ d = getattr(context, attr) cls.assertTrue(all(d[s] if s in expected else not d[s] for s in d)) -RoundingModes = { - C: (C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR, - C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN, - C.ROUND_05UP) if C else None, - P: (P.ROUND_UP, P.ROUND_DOWN, P.ROUND_CEILING, P.ROUND_FLOOR, - P.ROUND_HALF_UP, P.ROUND_HALF_DOWN, P.ROUND_HALF_EVEN, - P.ROUND_05UP) -} +# The C version has an optimization that tests rounding mode strings for +# pointer equality, but it must also handle strings with different ids. +if C: + ROUND_UP = random.choice([C.ROUND_UP, P.ROUND_UP]) + ROUND_DOWN = random.choice([C.ROUND_DOWN, P.ROUND_DOWN]) + ROUND_CEILING = random.choice([C.ROUND_CEILING, P.ROUND_CEILING]) + ROUND_FLOOR = random.choice([C.ROUND_FLOOR, P.ROUND_FLOOR]) + ROUND_HALF_UP = random.choice([C.ROUND_HALF_UP, P.ROUND_HALF_UP]) + ROUND_HALF_DOWN = random.choice([C.ROUND_HALF_DOWN, P.ROUND_HALF_DOWN]) + ROUND_HALF_EVEN = random.choice([C.ROUND_HALF_EVEN, P.ROUND_HALF_EVEN]) + ROUND_05UP = random.choice([C.ROUND_05UP, P.ROUND_05UP]) +else: + ROUND_UP = P.ROUND_UP + ROUND_DOWN = P.ROUND_DOWN + ROUND_CEILING = P.ROUND_CEILING + ROUND_FLOOR = P.ROUND_FLOOR + ROUND_HALF_UP = P.ROUND_HALF_UP + ROUND_HALF_DOWN = P.ROUND_HALF_DOWN + ROUND_HALF_EVEN = P.ROUND_HALF_EVEN + ROUND_05UP = P.ROUND_05UP + +RoundingModes = [ + ROUND_UP, ROUND_DOWN, ROUND_CEILING, ROUND_FLOOR, + ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_HALF_EVEN, + ROUND_05UP +] # Tests are built around these assumed context defaults. # test_main() restores the original context. @@ -96,7 +114,7 @@ def init(m): if not m: return DefaultTestContext = m.Context( - prec=9, rounding=m.ROUND_HALF_EVEN, traps=dict.fromkeys(Signals[m], 0) + prec=9, rounding=ROUND_HALF_EVEN, traps=dict.fromkeys(Signals[m], 0) ) m.setcontext(DefaultTestContext) @@ -229,14 +247,14 @@ 'xor':'logical_xor'} # Map test-case names to roundings. - self.RoundingDict = {'ceiling' : self.decimal.ROUND_CEILING, - 'down' : self.decimal.ROUND_DOWN, - 'floor' : self.decimal.ROUND_FLOOR, - 'half_down' : self.decimal.ROUND_HALF_DOWN, - 'half_even' : self.decimal.ROUND_HALF_EVEN, - 'half_up' : self.decimal.ROUND_HALF_UP, - 'up' : self.decimal.ROUND_UP, - '05up' : self.decimal.ROUND_05UP} + self.RoundingDict = {'ceiling' : ROUND_CEILING, + 'down' : ROUND_DOWN, + 'floor' : ROUND_FLOOR, + 'half_down' : ROUND_HALF_DOWN, + 'half_even' : ROUND_HALF_EVEN, + 'half_up' : ROUND_HALF_UP, + 'up' : ROUND_UP, + '05up' : ROUND_05UP} # Map the test cases' error names to the actual errors. self.ErrorNames = {'clamped' : self.decimal.Clamped, @@ -2101,9 +2119,6 @@ Inexact = self.decimal.Inexact Rounded = self.decimal.Rounded Clamped = self.decimal.Clamped - ROUND_HALF_EVEN = self.decimal.ROUND_HALF_EVEN - ROUND_DOWN = self.decimal.ROUND_DOWN - ROUND_UP = self.decimal.ROUND_UP with localcontext(Context()) as c: c.prec = 7 @@ -2430,7 +2445,6 @@ def test_int(self): Decimal = self.decimal.Decimal - ROUND_DOWN = self.decimal.ROUND_DOWN for x in range(-250, 250): s = '%0.2f' % (x / 100.0) @@ -2448,7 +2462,6 @@ def test_trunc(self): Decimal = self.decimal.Decimal - ROUND_DOWN = self.decimal.ROUND_DOWN for x in range(-250, 250): s = '%0.2f' % (x / 100.0) @@ -2491,8 +2504,6 @@ def test_create_decimal_from_float(self): Decimal = self.decimal.Decimal Context = self.decimal.Context - ROUND_DOWN = self.decimal.ROUND_DOWN - ROUND_UP = self.decimal.ROUND_UP Inexact = self.decimal.Inexact context = Context(prec=5, rounding=ROUND_DOWN) @@ -2522,7 +2533,6 @@ Decimal = self.decimal.Decimal Context = self.decimal.Context InvalidOperation = self.decimal.InvalidOperation - ROUND_DOWN = self.decimal.ROUND_DOWN c = Context(Emax=99999, Emin=-99999) self.assertEqual( @@ -2723,7 +2733,6 @@ InvalidOperation = self.decimal.InvalidOperation DivisionByZero = self.decimal.DivisionByZero Overflow = self.decimal.Overflow - ROUND_HALF_EVEN = self.decimal.ROUND_HALF_EVEN c1 = Context() c2 = Context(prec=None, rounding=None, Emax=None, Emin=None, @@ -2739,6 +2748,14 @@ assert_signals(self, c, 'traps', [InvalidOperation, DivisionByZero, Overflow]) + @cpython_only + def test_from_legacy_strings(self): + import _testcapi + c = self.decimal.Context() + + c.rounding = _testcapi.unicode_legacy_string('ROUND_05UP') + self.assertEqual(c.rounding, 'ROUND_05UP') + def test_pickle(self): Context = self.decimal.Context @@ -2762,7 +2779,7 @@ # Test interchangeability combinations = [(C, P), (P, C)] if C else [(P, P)] for dumper, loader in combinations: - for ri, _ in enumerate(RoundingModes[dumper]): + for ri, _ in enumerate(RoundingModes): for fi, _ in enumerate(OrderedSignals[dumper]): for ti, _ in enumerate(OrderedSignals[dumper]): @@ -2776,7 +2793,7 @@ sys.modules['decimal'] = dumper c = dumper.Context( prec=prec, Emin=emin, Emax=emax, - rounding=RoundingModes[dumper][ri], + rounding=RoundingModes[ri], capitals=caps, clamp=clamp, flags=OrderedSignals[dumper][:fi], traps=OrderedSignals[dumper][:ti] @@ -2791,7 +2808,7 @@ self.assertEqual(d.prec, prec) self.assertEqual(d.Emin, emin) self.assertEqual(d.Emax, emax) - self.assertEqual(d.rounding, RoundingModes[loader][ri]) + self.assertEqual(d.rounding, RoundingModes[ri]) self.assertEqual(d.capitals, caps) self.assertEqual(d.clamp, clamp) assert_signals(self, d, 'flags', OrderedSignals[loader][:fi]) @@ -3593,7 +3610,6 @@ Underflow = self.decimal.Underflow Clamped = self.decimal.Clamped Subnormal = self.decimal.Subnormal - ROUND_HALF_EVEN = self.decimal.ROUND_HALF_EVEN def raise_error(context, flag): if self.decimal == C: @@ -3960,17 +3976,6 @@ self.assertRaises(ValueError, setattr, c, 'Emin', 1) self.assertRaises(TypeError, setattr, c, 'Emin', (1,2,3)) - # rounding: always raise TypeError in order to get consistent - # exceptions across implementations. In decimal, rounding - # modes are strings, in _decimal they are integers. The idea - # is to view rounding as an abstract type and not mind the - # implementation details. - # Hence, a user should view the rounding modes as if they - # had been defined in a language that supports abstract - # data types, e.g. ocaml: - # - # type rounding = ROUND_DOWN | ROUND_HALF_UP | ... ;; - # self.assertRaises(TypeError, setattr, c, 'rounding', -1) self.assertRaises(TypeError, setattr, c, 'rounding', 9) self.assertRaises(TypeError, setattr, c, 'rounding', 1.0) @@ -4023,8 +4028,6 @@ decimal = self.decimal Decimal = decimal.Decimal Context = decimal.Context - ROUND_HALF_EVEN = decimal.ROUND_HALF_EVEN - ROUND_DOWN = decimal.ROUND_DOWN Clamped = decimal.Clamped DivisionByZero = decimal.DivisionByZero Inexact = decimal.Inexact @@ -4192,7 +4195,7 @@ c.prec = 425000000 c.Emax = 425000000 c.Emin = -425000000 - c.rounding = self.decimal.ROUND_HALF_DOWN + c.rounding = ROUND_HALF_DOWN c.capitals = 0 c.clamp = 1 for sig in OrderedSignals[self.decimal]: @@ -4584,7 +4587,6 @@ def test_py_rescale(self): # Coverage Decimal = P.Decimal - ROUND_UP = P.ROUND_UP localcontext = P.localcontext with localcontext() as c: @@ -4594,7 +4596,6 @@ def test_py__round(self): # Coverage Decimal = P.Decimal - ROUND_UP = P.ROUND_UP self.assertRaises(ValueError, Decimal("3.1234")._round, 0, ROUND_UP) @@ -4663,11 +4664,6 @@ self.assertEqual(C.DECIMAL128, 128) self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, 512) - # Rounding modes - for i, v in enumerate(RoundingModes[C]): - self.assertEqual(v, i) - self.assertEqual(C.ROUND_TRUNC, 8) - # Conditions for i, v in enumerate(cond): self.assertEqual(v, 1<val)); } + /* Init string constants */ + for (i = 0; i < _PY_DEC_ROUND_GUARD; i++) { + ASSIGN_PTR(round_map[i], PyUnicode_FromString(mpd_round_string[i])); + Py_INCREF(round_map[i]); + CHECK_INT(PyModule_AddObject(m, mpd_round_string[i], round_map[i])); + } + /* Add specification version number */ CHECK_INT(PyModule_AddStringConstant(m, "__version__", " 1.70")); diff --git a/Modules/_decimal/tests/deccheck.py b/Modules/_decimal/tests/deccheck.py --- a/Modules/_decimal/tests/deccheck.py +++ b/Modules/_decimal/tests/deccheck.py @@ -158,17 +158,9 @@ C.FloatOperation: P.FloatOperation, } -RoundMap = { - C.ROUND_UP: P.ROUND_UP, - C.ROUND_DOWN: P.ROUND_DOWN, - C.ROUND_CEILING: P.ROUND_CEILING, - C.ROUND_FLOOR: P.ROUND_FLOOR, - C.ROUND_HALF_UP: P.ROUND_HALF_UP, - C.ROUND_HALF_DOWN: P.ROUND_HALF_DOWN, - C.ROUND_HALF_EVEN: P.ROUND_HALF_EVEN, - C.ROUND_05UP: P.ROUND_05UP -} -RoundModes = RoundMap.items() +RoundModes = [C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR, + C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN, + C.ROUND_05UP] class Context(object): @@ -183,7 +175,7 @@ self.p.prec = self.c.prec self.p.Emin = self.c.Emin self.p.Emax = self.c.Emax - self.p.rounding = RoundMap[self.c.rounding] + self.p.rounding = self.c.rounding self.p.capitals = self.c.capitals self.settraps([sig for sig in self.c.traps if self.c.traps[sig]]) self.setstatus([sig for sig in self.c.flags if self.c.flags[sig]]) @@ -217,12 +209,12 @@ self.p.Emax = val def getround(self): - assert(self.c.rounding == RoundMap[self.p.rounding]) + assert(self.c.rounding == self.p.rounding) return self.c.rounding def setround(self, val): self.c.rounding = val - self.p.rounding = RoundMap[val] + self.p.rounding = val def getcapitals(self): assert(self.c.capitals == self.p.capitals) @@ -627,8 +619,12 @@ context.clear_status() - if not t.contextfunc and i == 0 or \ - convstr and isinstance(op, str): + if op in RoundModes: + t.cop.append(op) + t.pop.append(op) + + elif not t.contextfunc and i == 0 or \ + convstr and isinstance(op, str): try: c = C.Decimal(op) cex = None @@ -662,10 +658,6 @@ t.cop.append(op.c) t.pop.append(op.p) - elif op in RoundModes: - t.cop.append(op[0]) - t.pop.append(op[1]) - else: t.cop.append(op) t.pop.append(op) @@ -809,7 +801,7 @@ log(" prec: %d emin: %d emax: %d", (context.prec, context.Emin, context.Emax)) restr_range = 9999 if context.Emax > 9999 else context.Emax+99 - for rounding in sorted(RoundMap): + for rounding in RoundModes: context.rounding = rounding context.capitals = random.randrange(2) if spec['clamp'] == 'rand': @@ -941,7 +933,7 @@ def test_from_float(method, prec, exprange, restricted_range, itr, stat): """Iterate the __float__ method through many test cases.""" - for rounding in sorted(RoundMap): + for rounding in RoundModes: context.rounding = rounding for i in range(1000): f = randfloat()