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)
-}
+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.
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)
'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,
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
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)
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)
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)
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(
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,
assert_signals(self, c, 'traps', [InvalidOperation, DivisionByZero,
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):
Context = self.decimal.Context
# 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]):
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]
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])
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:
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)
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
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]:
def test_py_rescale(self):
# Coverage
Decimal = P.Decimal
- ROUND_UP = P.ROUND_UP
localcontext = P.localcontext
with localcontext() as c:
def test_py__round(self):
# Coverage
Decimal = P.Decimal
- ROUND_UP = P.ROUND_UP
self.assertRaises(ValueError, Decimal("3.1234")._round, 0, ROUND_UP)
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<<i)
# in the same order.
DefaultContext = C.DefaultContext
FloatOperation = C.FloatOperation
- ROUND_HALF_DOWN = C.ROUND_HALF_DOWN
c = DefaultContext.copy()
self.assertRaises(OverflowError, Context, prec=int_max+1)
self.assertRaises(OverflowError, Context, Emax=int_max+1)
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, capitals=int_max+1)
self.assertRaises(ValueError, setattr, c, attr, int_max)
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
if C.MAX_PREC == 425000000:
self.assertRaises(OverflowError, getattr(c, '_unsafe_setprec'),
self.assertRaises(TypeError, setcontext, "xyz")
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
def test_c_context_errors_extra(self):
Context = C.Context
def test_c_valid_context(self):
# These tests are for code coverage in _decimal.
DefaultContext = C.DefaultContext
- ROUND_HALF_UP = C.ROUND_HALF_UP
Clamped = C.Clamped
Underflow = C.Underflow
Inexact = C.Inexact
def test_c_integral(self):
Decimal = C.Decimal
Inexact = C.Inexact
- ROUND_UP = C.ROUND_UP
localcontext = C.localcontext
x = Decimal(10)
Decimal = C.Decimal
InvalidOperation = C.InvalidOperation
DivisionByZero = C.DivisionByZero
- ROUND_UP = C.ROUND_UP
getcontext = C.getcontext
localcontext = C.localcontext
lim = len(OrderedSignals[C])
for r in range(lim):
for t in range(lim):
- for round in RoundingModes[C]:
+ for round in RoundingModes:
flags = random.sample(OrderedSignals[C], r)
traps = random.sample(OrderedSignals[C], t)
prec = random.randrange(1, 10000)
"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 =
"valid values for rounding are:\n\
[ROUND_CEILING, ROUND_FLOOR, ROUND_UP, ROUND_DOWN,\n\
return -1;
}
-static PyObject *
-type_error_ptr(const char *mesg)
-{
- PyErr_SetString(PyExc_TypeError, mesg);
- return NULL;
-}
-
static int
runtime_error_int(const char *mesg)
{
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 */
Dec_CONTEXT_GET_SSIZE(prec)
Dec_CONTEXT_GET_SSIZE(emax)
Dec_CONTEXT_GET_SSIZE(emin)
-Dec_CONTEXT_GET_SSIZE(round)
Dec_CONTEXT_GET_SSIZE(clamp)
#ifdef EXTRA_FUNCTIONALITY
Dec_CONTEXT_GET_ULONG(status)
#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 *
context_getcapitals(PyObject *self, void *closure UNUSED)
{
context_setround(PyObject *self, PyObject *value, void *closure UNUSED)
{
mpd_context_t *ctx;
- mpd_ssize_t x;
+ int x;
- x = PyLong_AsSsize_t(value);
- if (x == -1 && PyErr_Occurred()) {
+ x = getround(value);
+ if (x == -1) {
return -1;
}
- BOUNDS_CHECK(x, INT_MIN, INT_MAX);
ctx = CTX(self);
- if (!mpd_qsetround(ctx, (int)x)) {
- return type_error_int(invalid_rounding_err);
+ if (!mpd_qsetround(ctx, x)) {
+ INTERNAL_ERROR_INT("context_setround"); /* GCOV_NOT_REACHED */
}
return 0;
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
context_init(PyObject *self, PyObject *args, PyObject *kwds)
{
if (prec != Py_None && context_setprec(self, prec, NULL) < 0) {
return -1;
}
+ if (rounding != Py_None && context_setround(self, rounding, NULL) < 0) {
+ return -1;
+ }
if (emin != Py_None && context_setemin(self, emin, NULL) < 0) {
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 (PyList_Check(traps)) {
ret = context_settraps_list(self, traps);
return NULL;
}
if (!mpd_qsetround(&workctx, round)) {
- return type_error_ptr(invalid_rounding_err);
+ INTERNAL_ERROR_PTR("PyDec_ToIntegralValue"); /* GCOV_NOT_REACHED */
}
}
return NULL;
}
if (!mpd_qsetround(&workctx, round)) {
- return type_error_ptr(invalid_rounding_err);
+ INTERNAL_ERROR_PTR("PyDec_ToIntegralExact"); /* GCOV_NOT_REACHED */
}
}
return NULL;
}
if (!mpd_qsetround(&workctx, round)) {
- return type_error_ptr(invalid_rounding_err);
+ INTERNAL_ERROR_PTR("dec_mpd_qquantize"); /* GCOV_NOT_REACHED */
}
}
{"DECIMAL64", MPD_DECIMAL64},
{"DECIMAL128", MPD_DECIMAL128},
{"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 */
{"DecClamped", MPD_Clamped},
{"DecConversionSyntax", MPD_Conversion_syntax},
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 */
CHECK_INT(PyModule_AddStringConstant(m, "__version__", " 1.70"));