Commit 59a4a93f authored by Stefan Krah's avatar Stefan Krah

Issue #16422: Use strings for rounding mode constants for better readability

and pickling compatibility.
parent 0ad344a8
...@@ -78,14 +78,20 @@ def assert_signals(cls, context, attr, expected): ...@@ -78,14 +78,20 @@ def assert_signals(cls, context, attr, expected):
d = getattr(context, attr) d = getattr(context, attr)
cls.assertTrue(all(d[s] if s in expected else not d[s] for s in d)) cls.assertTrue(all(d[s] if s in expected else not d[s] for s in d))
RoundingModes = { ROUND_UP = P.ROUND_UP
C: (C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR, ROUND_DOWN = P.ROUND_DOWN
C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN, ROUND_CEILING = P.ROUND_CEILING
C.ROUND_05UP) if C else None, ROUND_FLOOR = P.ROUND_FLOOR
P: (P.ROUND_UP, P.ROUND_DOWN, P.ROUND_CEILING, P.ROUND_FLOOR, ROUND_HALF_UP = P.ROUND_HALF_UP
P.ROUND_HALF_UP, P.ROUND_HALF_DOWN, P.ROUND_HALF_EVEN, ROUND_HALF_DOWN = P.ROUND_HALF_DOWN
P.ROUND_05UP) 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. # Tests are built around these assumed context defaults.
# test_main() restores the original context. # test_main() restores the original context.
...@@ -96,7 +102,7 @@ ORIGINAL_CONTEXT = { ...@@ -96,7 +102,7 @@ ORIGINAL_CONTEXT = {
def init(m): def init(m):
if not m: return if not m: return
DefaultTestContext = m.Context( 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) m.setcontext(DefaultTestContext)
...@@ -229,14 +235,14 @@ class IBMTestCases(unittest.TestCase): ...@@ -229,14 +235,14 @@ class IBMTestCases(unittest.TestCase):
'xor':'logical_xor'} 'xor':'logical_xor'}
# Map test-case names to roundings. # Map test-case names to roundings.
self.RoundingDict = {'ceiling' : self.decimal.ROUND_CEILING, self.RoundingDict = {'ceiling' : ROUND_CEILING,
'down' : self.decimal.ROUND_DOWN, 'down' : ROUND_DOWN,
'floor' : self.decimal.ROUND_FLOOR, 'floor' : ROUND_FLOOR,
'half_down' : self.decimal.ROUND_HALF_DOWN, 'half_down' : ROUND_HALF_DOWN,
'half_even' : self.decimal.ROUND_HALF_EVEN, 'half_even' : ROUND_HALF_EVEN,
'half_up' : self.decimal.ROUND_HALF_UP, 'half_up' : ROUND_HALF_UP,
'up' : self.decimal.ROUND_UP, 'up' : ROUND_UP,
'05up' : self.decimal.ROUND_05UP} '05up' : ROUND_05UP}
# Map the test cases' error names to the actual errors. # Map the test cases' error names to the actual errors.
self.ErrorNames = {'clamped' : self.decimal.Clamped, self.ErrorNames = {'clamped' : self.decimal.Clamped,
...@@ -2101,9 +2107,6 @@ class UsabilityTest(unittest.TestCase): ...@@ -2101,9 +2107,6 @@ class UsabilityTest(unittest.TestCase):
Inexact = self.decimal.Inexact Inexact = self.decimal.Inexact
Rounded = self.decimal.Rounded Rounded = self.decimal.Rounded
Clamped = self.decimal.Clamped 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: with localcontext(Context()) as c:
c.prec = 7 c.prec = 7
...@@ -2430,7 +2433,6 @@ class PythonAPItests(unittest.TestCase): ...@@ -2430,7 +2433,6 @@ class PythonAPItests(unittest.TestCase):
def test_int(self): def test_int(self):
Decimal = self.decimal.Decimal Decimal = self.decimal.Decimal
ROUND_DOWN = self.decimal.ROUND_DOWN
for x in range(-250, 250): for x in range(-250, 250):
s = '%0.2f' % (x / 100.0) s = '%0.2f' % (x / 100.0)
...@@ -2448,7 +2450,6 @@ class PythonAPItests(unittest.TestCase): ...@@ -2448,7 +2450,6 @@ class PythonAPItests(unittest.TestCase):
def test_trunc(self): def test_trunc(self):
Decimal = self.decimal.Decimal Decimal = self.decimal.Decimal
ROUND_DOWN = self.decimal.ROUND_DOWN
for x in range(-250, 250): for x in range(-250, 250):
s = '%0.2f' % (x / 100.0) s = '%0.2f' % (x / 100.0)
...@@ -2491,8 +2492,6 @@ class PythonAPItests(unittest.TestCase): ...@@ -2491,8 +2492,6 @@ class PythonAPItests(unittest.TestCase):
def test_create_decimal_from_float(self): def test_create_decimal_from_float(self):
Decimal = self.decimal.Decimal Decimal = self.decimal.Decimal
Context = self.decimal.Context Context = self.decimal.Context
ROUND_DOWN = self.decimal.ROUND_DOWN
ROUND_UP = self.decimal.ROUND_UP
Inexact = self.decimal.Inexact Inexact = self.decimal.Inexact
context = Context(prec=5, rounding=ROUND_DOWN) context = Context(prec=5, rounding=ROUND_DOWN)
...@@ -2522,7 +2521,6 @@ class PythonAPItests(unittest.TestCase): ...@@ -2522,7 +2521,6 @@ class PythonAPItests(unittest.TestCase):
Decimal = self.decimal.Decimal Decimal = self.decimal.Decimal
Context = self.decimal.Context Context = self.decimal.Context
InvalidOperation = self.decimal.InvalidOperation InvalidOperation = self.decimal.InvalidOperation
ROUND_DOWN = self.decimal.ROUND_DOWN
c = Context(Emax=99999, Emin=-99999) c = Context(Emax=99999, Emin=-99999)
self.assertEqual( self.assertEqual(
...@@ -2723,7 +2721,6 @@ class ContextAPItests(unittest.TestCase): ...@@ -2723,7 +2721,6 @@ class ContextAPItests(unittest.TestCase):
InvalidOperation = self.decimal.InvalidOperation InvalidOperation = self.decimal.InvalidOperation
DivisionByZero = self.decimal.DivisionByZero DivisionByZero = self.decimal.DivisionByZero
Overflow = self.decimal.Overflow Overflow = self.decimal.Overflow
ROUND_HALF_EVEN = self.decimal.ROUND_HALF_EVEN
c1 = Context() c1 = Context()
c2 = Context(prec=None, rounding=None, Emax=None, Emin=None, c2 = Context(prec=None, rounding=None, Emax=None, Emin=None,
...@@ -2739,6 +2736,21 @@ class ContextAPItests(unittest.TestCase): ...@@ -2739,6 +2736,21 @@ class ContextAPItests(unittest.TestCase):
assert_signals(self, c, 'traps', [InvalidOperation, DivisionByZero, assert_signals(self, c, 'traps', [InvalidOperation, DivisionByZero,
Overflow]) Overflow])
@cpython_only
def test_from_legacy_strings(self):
import _testcapi
c = self.decimal.Context()
for rnd in RoundingModes:
c.rounding = _testcapi.unicode_legacy_string(rnd)
self.assertEqual(c.rounding, rnd)
s = _testcapi.unicode_legacy_string('')
self.assertRaises(TypeError, setattr, c, 'rounding', s)
s = _testcapi.unicode_legacy_string('ROUND_\x00UP')
self.assertRaises(TypeError, setattr, c, 'rounding', s)
def test_pickle(self): def test_pickle(self):
Context = self.decimal.Context Context = self.decimal.Context
...@@ -2762,7 +2774,7 @@ class ContextAPItests(unittest.TestCase): ...@@ -2762,7 +2774,7 @@ class ContextAPItests(unittest.TestCase):
# Test interchangeability # Test interchangeability
combinations = [(C, P), (P, C)] if C else [(P, P)] combinations = [(C, P), (P, C)] if C else [(P, P)]
for dumper, loader in combinations: for dumper, loader in combinations:
for ri, _ in enumerate(RoundingModes[dumper]): for ri, _ in enumerate(RoundingModes):
for fi, _ in enumerate(OrderedSignals[dumper]): for fi, _ in enumerate(OrderedSignals[dumper]):
for ti, _ in enumerate(OrderedSignals[dumper]): for ti, _ in enumerate(OrderedSignals[dumper]):
...@@ -2776,7 +2788,7 @@ class ContextAPItests(unittest.TestCase): ...@@ -2776,7 +2788,7 @@ class ContextAPItests(unittest.TestCase):
sys.modules['decimal'] = dumper sys.modules['decimal'] = dumper
c = dumper.Context( c = dumper.Context(
prec=prec, Emin=emin, Emax=emax, prec=prec, Emin=emin, Emax=emax,
rounding=RoundingModes[dumper][ri], rounding=RoundingModes[ri],
capitals=caps, clamp=clamp, capitals=caps, clamp=clamp,
flags=OrderedSignals[dumper][:fi], flags=OrderedSignals[dumper][:fi],
traps=OrderedSignals[dumper][:ti] traps=OrderedSignals[dumper][:ti]
...@@ -2791,7 +2803,7 @@ class ContextAPItests(unittest.TestCase): ...@@ -2791,7 +2803,7 @@ class ContextAPItests(unittest.TestCase):
self.assertEqual(d.prec, prec) self.assertEqual(d.prec, prec)
self.assertEqual(d.Emin, emin) self.assertEqual(d.Emin, emin)
self.assertEqual(d.Emax, emax) 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.capitals, caps)
self.assertEqual(d.clamp, clamp) self.assertEqual(d.clamp, clamp)
assert_signals(self, d, 'flags', OrderedSignals[loader][:fi]) assert_signals(self, d, 'flags', OrderedSignals[loader][:fi])
...@@ -3593,7 +3605,6 @@ class ContextFlags(unittest.TestCase): ...@@ -3593,7 +3605,6 @@ class ContextFlags(unittest.TestCase):
Underflow = self.decimal.Underflow Underflow = self.decimal.Underflow
Clamped = self.decimal.Clamped Clamped = self.decimal.Clamped
Subnormal = self.decimal.Subnormal Subnormal = self.decimal.Subnormal
ROUND_HALF_EVEN = self.decimal.ROUND_HALF_EVEN
def raise_error(context, flag): def raise_error(context, flag):
if self.decimal == C: if self.decimal == C:
...@@ -3960,17 +3971,6 @@ class ContextInputValidation(unittest.TestCase): ...@@ -3960,17 +3971,6 @@ class ContextInputValidation(unittest.TestCase):
self.assertRaises(ValueError, setattr, c, 'Emin', 1) self.assertRaises(ValueError, setattr, c, 'Emin', 1)
self.assertRaises(TypeError, setattr, c, 'Emin', (1,2,3)) 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', -1)
self.assertRaises(TypeError, setattr, c, 'rounding', 9) self.assertRaises(TypeError, setattr, c, 'rounding', 9)
self.assertRaises(TypeError, setattr, c, 'rounding', 1.0) self.assertRaises(TypeError, setattr, c, 'rounding', 1.0)
...@@ -4023,8 +4023,6 @@ class ContextSubclassing(unittest.TestCase): ...@@ -4023,8 +4023,6 @@ class ContextSubclassing(unittest.TestCase):
decimal = self.decimal decimal = self.decimal
Decimal = decimal.Decimal Decimal = decimal.Decimal
Context = decimal.Context Context = decimal.Context
ROUND_HALF_EVEN = decimal.ROUND_HALF_EVEN
ROUND_DOWN = decimal.ROUND_DOWN
Clamped = decimal.Clamped Clamped = decimal.Clamped
DivisionByZero = decimal.DivisionByZero DivisionByZero = decimal.DivisionByZero
Inexact = decimal.Inexact Inexact = decimal.Inexact
...@@ -4192,7 +4190,7 @@ class Coverage(unittest.TestCase): ...@@ -4192,7 +4190,7 @@ class Coverage(unittest.TestCase):
c.prec = 425000000 c.prec = 425000000
c.Emax = 425000000 c.Emax = 425000000
c.Emin = -425000000 c.Emin = -425000000
c.rounding = self.decimal.ROUND_HALF_DOWN c.rounding = ROUND_HALF_DOWN
c.capitals = 0 c.capitals = 0
c.clamp = 1 c.clamp = 1
for sig in OrderedSignals[self.decimal]: for sig in OrderedSignals[self.decimal]:
...@@ -4584,7 +4582,6 @@ class PyWhitebox(unittest.TestCase): ...@@ -4584,7 +4582,6 @@ class PyWhitebox(unittest.TestCase):
def test_py_rescale(self): def test_py_rescale(self):
# Coverage # Coverage
Decimal = P.Decimal Decimal = P.Decimal
ROUND_UP = P.ROUND_UP
localcontext = P.localcontext localcontext = P.localcontext
with localcontext() as c: with localcontext() as c:
...@@ -4594,7 +4591,6 @@ class PyWhitebox(unittest.TestCase): ...@@ -4594,7 +4591,6 @@ class PyWhitebox(unittest.TestCase):
def test_py__round(self): def test_py__round(self):
# Coverage # Coverage
Decimal = P.Decimal Decimal = P.Decimal
ROUND_UP = P.ROUND_UP
self.assertRaises(ValueError, Decimal("3.1234")._round, 0, ROUND_UP) self.assertRaises(ValueError, Decimal("3.1234")._round, 0, ROUND_UP)
...@@ -4663,11 +4659,6 @@ class CFunctionality(unittest.TestCase): ...@@ -4663,11 +4659,6 @@ class CFunctionality(unittest.TestCase):
self.assertEqual(C.DECIMAL128, 128) self.assertEqual(C.DECIMAL128, 128)
self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, 512) 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 # Conditions
for i, v in enumerate(cond): for i, v in enumerate(cond):
self.assertEqual(v, 1<<i) self.assertEqual(v, 1<<i)
...@@ -4727,7 +4718,6 @@ class CWhitebox(unittest.TestCase): ...@@ -4727,7 +4718,6 @@ class CWhitebox(unittest.TestCase):
# in the same order. # in the same order.
DefaultContext = C.DefaultContext DefaultContext = C.DefaultContext
FloatOperation = C.FloatOperation FloatOperation = C.FloatOperation
ROUND_HALF_DOWN = C.ROUND_HALF_DOWN
c = DefaultContext.copy() c = DefaultContext.copy()
...@@ -4800,7 +4790,6 @@ class CWhitebox(unittest.TestCase): ...@@ -4800,7 +4790,6 @@ class CWhitebox(unittest.TestCase):
self.assertRaises(OverflowError, Context, prec=int_max+1) self.assertRaises(OverflowError, Context, prec=int_max+1)
self.assertRaises(OverflowError, Context, Emax=int_max+1) self.assertRaises(OverflowError, Context, Emax=int_max+1)
self.assertRaises(OverflowError, Context, Emin=-int_max-2) self.assertRaises(OverflowError, Context, Emin=-int_max-2)
self.assertRaises(OverflowError, Context, rounding=int_max+1)
self.assertRaises(OverflowError, Context, clamp=int_max+1) self.assertRaises(OverflowError, Context, clamp=int_max+1)
self.assertRaises(OverflowError, Context, capitals=int_max+1) self.assertRaises(OverflowError, Context, capitals=int_max+1)
...@@ -4812,14 +4801,6 @@ class CWhitebox(unittest.TestCase): ...@@ -4812,14 +4801,6 @@ class CWhitebox(unittest.TestCase):
self.assertRaises(ValueError, setattr, c, attr, int_max) self.assertRaises(ValueError, setattr, c, attr, int_max)
self.assertRaises(ValueError, setattr, c, attr, -int_max-1) self.assertRaises(ValueError, setattr, c, attr, -int_max-1)
# OverflowError, general TypeError
for attr in ('rounding',):
self.assertRaises(OverflowError, setattr, c, attr, int_max+1)
self.assertRaises(OverflowError, setattr, c, attr, -int_max-2)
if sys.platform != 'win32':
self.assertRaises(TypeError, setattr, c, attr, int_max)
self.assertRaises(TypeError, setattr, c, attr, -int_max-1)
# OverflowError: _unsafe_setprec, _unsafe_setemin, _unsafe_setemax # OverflowError: _unsafe_setprec, _unsafe_setemin, _unsafe_setemax
if C.MAX_PREC == 425000000: if C.MAX_PREC == 425000000:
self.assertRaises(OverflowError, getattr(c, '_unsafe_setprec'), self.assertRaises(OverflowError, getattr(c, '_unsafe_setprec'),
...@@ -4862,6 +4843,17 @@ class CWhitebox(unittest.TestCase): ...@@ -4862,6 +4843,17 @@ class CWhitebox(unittest.TestCase):
self.assertRaises(TypeError, setcontext, "xyz") self.assertRaises(TypeError, setcontext, "xyz")
setcontext(saved_context) setcontext(saved_context)
def test_rounding_strings_interned(self):
self.assertIs(C.ROUND_UP, P.ROUND_UP)
self.assertIs(C.ROUND_DOWN, P.ROUND_DOWN)
self.assertIs(C.ROUND_CEILING, P.ROUND_CEILING)
self.assertIs(C.ROUND_FLOOR, P.ROUND_FLOOR)
self.assertIs(C.ROUND_HALF_UP, P.ROUND_HALF_UP)
self.assertIs(C.ROUND_HALF_DOWN, P.ROUND_HALF_DOWN)
self.assertIs(C.ROUND_HALF_EVEN, P.ROUND_HALF_EVEN)
self.assertIs(C.ROUND_05UP, P.ROUND_05UP)
@requires_extra_functionality @requires_extra_functionality
def test_c_context_errors_extra(self): def test_c_context_errors_extra(self):
Context = C.Context Context = C.Context
...@@ -4908,7 +4900,6 @@ class CWhitebox(unittest.TestCase): ...@@ -4908,7 +4900,6 @@ class CWhitebox(unittest.TestCase):
def test_c_valid_context(self): def test_c_valid_context(self):
# These tests are for code coverage in _decimal. # These tests are for code coverage in _decimal.
DefaultContext = C.DefaultContext DefaultContext = C.DefaultContext
ROUND_HALF_UP = C.ROUND_HALF_UP
Clamped = C.Clamped Clamped = C.Clamped
Underflow = C.Underflow Underflow = C.Underflow
Inexact = C.Inexact Inexact = C.Inexact
...@@ -5000,7 +4991,6 @@ class CWhitebox(unittest.TestCase): ...@@ -5000,7 +4991,6 @@ class CWhitebox(unittest.TestCase):
def test_c_integral(self): def test_c_integral(self):
Decimal = C.Decimal Decimal = C.Decimal
Inexact = C.Inexact Inexact = C.Inexact
ROUND_UP = C.ROUND_UP
localcontext = C.localcontext localcontext = C.localcontext
x = Decimal(10) x = Decimal(10)
...@@ -5034,7 +5024,6 @@ class CWhitebox(unittest.TestCase): ...@@ -5034,7 +5024,6 @@ class CWhitebox(unittest.TestCase):
Decimal = C.Decimal Decimal = C.Decimal
InvalidOperation = C.InvalidOperation InvalidOperation = C.InvalidOperation
DivisionByZero = C.DivisionByZero DivisionByZero = C.DivisionByZero
ROUND_UP = C.ROUND_UP
getcontext = C.getcontext getcontext = C.getcontext
localcontext = C.localcontext localcontext = C.localcontext
...@@ -5237,7 +5226,7 @@ class CWhitebox(unittest.TestCase): ...@@ -5237,7 +5226,7 @@ class CWhitebox(unittest.TestCase):
lim = len(OrderedSignals[C]) lim = len(OrderedSignals[C])
for r in range(lim): for r in range(lim):
for t in range(lim): for t in range(lim):
for round in RoundingModes[C]: for round in RoundingModes:
flags = random.sample(OrderedSignals[C], r) flags = random.sample(OrderedSignals[C], r)
traps = random.sample(OrderedSignals[C], t) traps = random.sample(OrderedSignals[C], t)
prec = random.randrange(1, 10000) prec = random.randrange(1, 10000)
......
...@@ -150,6 +150,9 @@ Core and Builtins ...@@ -150,6 +150,9 @@ Core and Builtins
Library Library
------- -------
- Issue #16422: Use strings for rounding mode constants for better readability
and pickling compatibility.
- Issue #15861: tkinter now correctly works with lists and tuples containing - Issue #15861: tkinter now correctly works with lists and tuples containing
strings with whitespaces, backslashes or unbalanced braces. strings with whitespaces, backslashes or unbalanced braces.
......
...@@ -202,6 +202,13 @@ static const char *dec_signal_string[MPD_NUM_FLAGS] = { ...@@ -202,6 +202,13 @@ static const char *dec_signal_string[MPD_NUM_FLAGS] = {
"Underflow", "Underflow",
}; };
#ifdef EXTRA_FUNCTIONALITY
#define _PY_DEC_ROUND_GUARD MPD_ROUND_GUARD
#else
#define _PY_DEC_ROUND_GUARD (MPD_ROUND_GUARD-1)
#endif
static PyObject *round_map[_PY_DEC_ROUND_GUARD];
static const char *invalid_rounding_err = static const char *invalid_rounding_err =
"valid values for rounding are:\n\ "valid values for rounding are:\n\
[ROUND_CEILING, ROUND_FLOOR, ROUND_UP, ROUND_DOWN,\n\ [ROUND_CEILING, ROUND_FLOOR, ROUND_UP, ROUND_DOWN,\n\
...@@ -249,13 +256,6 @@ type_error_int(const char *mesg) ...@@ -249,13 +256,6 @@ type_error_int(const char *mesg)
return -1; return -1;
} }
static PyObject *
type_error_ptr(const char *mesg)
{
PyErr_SetString(PyExc_TypeError, mesg);
return NULL;
}
static int static int
runtime_error_int(const char *mesg) runtime_error_int(const char *mesg)
{ {
...@@ -502,6 +502,27 @@ dec_addstatus(PyObject *context, uint32_t status) ...@@ -502,6 +502,27 @@ dec_addstatus(PyObject *context, uint32_t status)
return 0; return 0;
} }
static int
getround(PyObject *v)
{
int i;
if (PyUnicode_Check(v)) {
for (i = 0; i < _PY_DEC_ROUND_GUARD; i++) {
if (v == round_map[i]) {
return i;
}
}
for (i = 0; i < _PY_DEC_ROUND_GUARD; i++) {
if (PyUnicode_Compare(v, round_map[i]) == 0) {
return i;
}
}
}
return type_error_int(invalid_rounding_err);
}
/******************************************************************************/ /******************************************************************************/
/* SignalDict Object */ /* SignalDict Object */
...@@ -715,7 +736,6 @@ context_get##mem(PyObject *self, void *closure UNUSED) \ ...@@ -715,7 +736,6 @@ context_get##mem(PyObject *self, void *closure UNUSED) \
Dec_CONTEXT_GET_SSIZE(prec) Dec_CONTEXT_GET_SSIZE(prec)
Dec_CONTEXT_GET_SSIZE(emax) Dec_CONTEXT_GET_SSIZE(emax)
Dec_CONTEXT_GET_SSIZE(emin) Dec_CONTEXT_GET_SSIZE(emin)
Dec_CONTEXT_GET_SSIZE(round)
Dec_CONTEXT_GET_SSIZE(clamp) Dec_CONTEXT_GET_SSIZE(clamp)
#ifdef EXTRA_FUNCTIONALITY #ifdef EXTRA_FUNCTIONALITY
...@@ -723,6 +743,15 @@ Dec_CONTEXT_GET_ULONG(traps) ...@@ -723,6 +743,15 @@ Dec_CONTEXT_GET_ULONG(traps)
Dec_CONTEXT_GET_ULONG(status) Dec_CONTEXT_GET_ULONG(status)
#endif #endif
static PyObject *
context_getround(PyObject *self, void *closure UNUSED)
{
int i = mpd_getround(CTX(self));
Py_INCREF(round_map[i]);
return round_map[i];
}
static PyObject * static PyObject *
context_getcapitals(PyObject *self, void *closure UNUSED) context_getcapitals(PyObject *self, void *closure UNUSED)
{ {
...@@ -875,17 +904,16 @@ static int ...@@ -875,17 +904,16 @@ static int
context_setround(PyObject *self, PyObject *value, void *closure UNUSED) context_setround(PyObject *self, PyObject *value, void *closure UNUSED)
{ {
mpd_context_t *ctx; mpd_context_t *ctx;
mpd_ssize_t x; int x;
x = PyLong_AsSsize_t(value); x = getround(value);
if (x == -1 && PyErr_Occurred()) { if (x == -1) {
return -1; return -1;
} }
BOUNDS_CHECK(x, INT_MIN, INT_MAX);
ctx = CTX(self); ctx = CTX(self);
if (!mpd_qsetround(ctx, (int)x)) { if (!mpd_qsetround(ctx, x)) {
return type_error_int(invalid_rounding_err); INTERNAL_ERROR_INT("context_setround"); /* GCOV_NOT_REACHED */
} }
return 0; return 0;
...@@ -1207,33 +1235,6 @@ context_dealloc(PyDecContextObject *self) ...@@ -1207,33 +1235,6 @@ context_dealloc(PyDecContextObject *self)
Py_TYPE(self)->tp_free(self); Py_TYPE(self)->tp_free(self);
} }
static int
getround(PyObject *v)
{
const char *s;
long x;
int i;
if (PyLong_Check(v)) {
x = PyLong_AsLong(v);
if (x == -1 && PyErr_Occurred()) {
return -1;
}
BOUNDS_CHECK(x, 0, INT_MAX);
return (int)x;
}
else if (PyUnicode_Check(v)) {
for (i = 0; i < MPD_ROUND_GUARD; i++) {
s = mpd_round_string[i];
if (PyUnicode_CompareWithASCIIString(v, s) == 0) {
return i;
}
}
}
return type_error_int("invalid rounding mode");
}
static int static int
context_init(PyObject *self, PyObject *args, PyObject *kwds) context_init(PyObject *self, PyObject *args, PyObject *kwds)
{ {
...@@ -1264,6 +1265,9 @@ context_init(PyObject *self, PyObject *args, PyObject *kwds) ...@@ -1264,6 +1265,9 @@ context_init(PyObject *self, PyObject *args, PyObject *kwds)
if (prec != Py_None && context_setprec(self, prec, NULL) < 0) { if (prec != Py_None && context_setprec(self, prec, NULL) < 0) {
return -1; return -1;
} }
if (rounding != Py_None && context_setround(self, rounding, NULL) < 0) {
return -1;
}
if (emin != Py_None && context_setemin(self, emin, NULL) < 0) { if (emin != Py_None && context_setemin(self, emin, NULL) < 0) {
return -1; return -1;
} }
...@@ -1277,16 +1281,6 @@ context_init(PyObject *self, PyObject *args, PyObject *kwds) ...@@ -1277,16 +1281,6 @@ context_init(PyObject *self, PyObject *args, PyObject *kwds)
return -1; return -1;
} }
if (rounding != Py_None) {
int x = getround(rounding);
if (x < 0) {
return -1;
}
if (!mpd_qsetround(CTX(self), x)) {
return type_error_int(invalid_rounding_err);
}
}
if (traps != Py_None) { if (traps != Py_None) {
if (PyList_Check(traps)) { if (PyList_Check(traps)) {
ret = context_settraps_list(self, traps); ret = context_settraps_list(self, traps);
...@@ -3345,7 +3339,7 @@ PyDec_ToIntegralValue(PyObject *dec, PyObject *args, PyObject *kwds) ...@@ -3345,7 +3339,7 @@ PyDec_ToIntegralValue(PyObject *dec, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
if (!mpd_qsetround(&workctx, round)) { if (!mpd_qsetround(&workctx, round)) {
return type_error_ptr(invalid_rounding_err); INTERNAL_ERROR_PTR("PyDec_ToIntegralValue"); /* GCOV_NOT_REACHED */
} }
} }
...@@ -3386,7 +3380,7 @@ PyDec_ToIntegralExact(PyObject *dec, PyObject *args, PyObject *kwds) ...@@ -3386,7 +3380,7 @@ PyDec_ToIntegralExact(PyObject *dec, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
if (!mpd_qsetround(&workctx, round)) { if (!mpd_qsetround(&workctx, round)) {
return type_error_ptr(invalid_rounding_err); INTERNAL_ERROR_PTR("PyDec_ToIntegralExact"); /* GCOV_NOT_REACHED */
} }
} }
...@@ -4187,7 +4181,7 @@ dec_mpd_qquantize(PyObject *v, PyObject *args, PyObject *kwds) ...@@ -4187,7 +4181,7 @@ dec_mpd_qquantize(PyObject *v, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
if (!mpd_qsetround(&workctx, round)) { if (!mpd_qsetround(&workctx, round)) {
return type_error_ptr(invalid_rounding_err); INTERNAL_ERROR_PTR("dec_mpd_qquantize"); /* GCOV_NOT_REACHED */
} }
} }
...@@ -5419,17 +5413,6 @@ static struct int_constmap int_constants [] = { ...@@ -5419,17 +5413,6 @@ static struct int_constmap int_constants [] = {
{"DECIMAL64", MPD_DECIMAL64}, {"DECIMAL64", MPD_DECIMAL64},
{"DECIMAL128", MPD_DECIMAL128}, {"DECIMAL128", MPD_DECIMAL128},
{"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS}, {"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS},
#endif
{"ROUND_CEILING", MPD_ROUND_CEILING},
{"ROUND_FLOOR", MPD_ROUND_FLOOR},
{"ROUND_UP", MPD_ROUND_UP},
{"ROUND_DOWN", MPD_ROUND_DOWN},
{"ROUND_HALF_UP", MPD_ROUND_HALF_UP},
{"ROUND_HALF_DOWN", MPD_ROUND_HALF_DOWN},
{"ROUND_HALF_EVEN", MPD_ROUND_HALF_EVEN},
{"ROUND_05UP", MPD_ROUND_05UP},
#ifdef EXTRA_FUNCTIONALITY
{"ROUND_TRUNC", MPD_ROUND_TRUNC},
/* int condition flags */ /* int condition flags */
{"DecClamped", MPD_Clamped}, {"DecClamped", MPD_Clamped},
{"DecConversionSyntax", MPD_Conversion_syntax}, {"DecConversionSyntax", MPD_Conversion_syntax},
...@@ -5680,6 +5663,13 @@ PyInit__decimal(void) ...@@ -5680,6 +5663,13 @@ PyInit__decimal(void)
int_cm->val)); int_cm->val));
} }
/* Init string constants */
for (i = 0; i < _PY_DEC_ROUND_GUARD; i++) {
ASSIGN_PTR(round_map[i], PyUnicode_InternFromString(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 */ /* Add specification version number */
CHECK_INT(PyModule_AddStringConstant(m, "__version__", " 1.70")); CHECK_INT(PyModule_AddStringConstant(m, "__version__", " 1.70"));
......
...@@ -158,17 +158,9 @@ CondMap = { ...@@ -158,17 +158,9 @@ CondMap = {
C.FloatOperation: P.FloatOperation, C.FloatOperation: P.FloatOperation,
} }
RoundMap = { RoundModes = [C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR,
C.ROUND_UP: P.ROUND_UP, C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN,
C.ROUND_DOWN: P.ROUND_DOWN, C.ROUND_05UP]
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()
class Context(object): class Context(object):
...@@ -183,7 +175,7 @@ class Context(object): ...@@ -183,7 +175,7 @@ class Context(object):
self.p.prec = self.c.prec self.p.prec = self.c.prec
self.p.Emin = self.c.Emin self.p.Emin = self.c.Emin
self.p.Emax = self.c.Emax 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.p.capitals = self.c.capitals
self.settraps([sig for sig in self.c.traps if self.c.traps[sig]]) 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]]) self.setstatus([sig for sig in self.c.flags if self.c.flags[sig]])
...@@ -217,12 +209,12 @@ class Context(object): ...@@ -217,12 +209,12 @@ class Context(object):
self.p.Emax = val self.p.Emax = val
def getround(self): def getround(self):
assert(self.c.rounding == RoundMap[self.p.rounding]) assert(self.c.rounding == self.p.rounding)
return self.c.rounding return self.c.rounding
def setround(self, val): def setround(self, val):
self.c.rounding = val self.c.rounding = val
self.p.rounding = RoundMap[val] self.p.rounding = val
def getcapitals(self): def getcapitals(self):
assert(self.c.capitals == self.p.capitals) assert(self.c.capitals == self.p.capitals)
...@@ -627,7 +619,11 @@ def convert(t, convstr=True): ...@@ -627,7 +619,11 @@ def convert(t, convstr=True):
context.clear_status() context.clear_status()
if not t.contextfunc and i == 0 or \ 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): convstr and isinstance(op, str):
try: try:
c = C.Decimal(op) c = C.Decimal(op)
...@@ -662,10 +658,6 @@ def convert(t, convstr=True): ...@@ -662,10 +658,6 @@ def convert(t, convstr=True):
t.cop.append(op.c) t.cop.append(op.c)
t.pop.append(op.p) t.pop.append(op.p)
elif op in RoundModes:
t.cop.append(op[0])
t.pop.append(op[1])
else: else:
t.cop.append(op) t.cop.append(op)
t.pop.append(op) t.pop.append(op)
...@@ -809,7 +801,7 @@ def test_method(method, testspecs, testfunc): ...@@ -809,7 +801,7 @@ def test_method(method, testspecs, testfunc):
log(" prec: %d emin: %d emax: %d", log(" prec: %d emin: %d emax: %d",
(context.prec, context.Emin, context.Emax)) (context.prec, context.Emin, context.Emax))
restr_range = 9999 if context.Emax > 9999 else context.Emax+99 restr_range = 9999 if context.Emax > 9999 else context.Emax+99
for rounding in sorted(RoundMap): for rounding in RoundModes:
context.rounding = rounding context.rounding = rounding
context.capitals = random.randrange(2) context.capitals = random.randrange(2)
if spec['clamp'] == 'rand': if spec['clamp'] == 'rand':
...@@ -941,7 +933,7 @@ def test_round(method, prec, exprange, restricted_range, itr, stat): ...@@ -941,7 +933,7 @@ def test_round(method, prec, exprange, restricted_range, itr, stat):
def test_from_float(method, prec, exprange, restricted_range, itr, stat): def test_from_float(method, prec, exprange, restricted_range, itr, stat):
"""Iterate the __float__ method through many test cases.""" """Iterate the __float__ method through many test cases."""
for rounding in sorted(RoundMap): for rounding in RoundModes:
context.rounding = rounding context.rounding = rounding
for i in range(1000): for i in range(1000):
f = randfloat() f = randfloat()
......
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