From cdddc2b742750e3f289305cf276433a8170c32c1 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sun, 21 Mar 2021 17:27:54 +0100 Subject: [PATCH] bpo-43422: Revert _decimal C API addition (GH-24960) Stefan Krah requested the reversal of these (unreleased) changes, quoting him: > The capsule API does not meet my testing standards, since I've focused on the upstream mpdecimal in the last couple of months. > Additionally, I'd like to refine the API, perhaps together with the Arrow community. Automerge-Triggered-By: GH:pitrou --- Doc/c-api/concrete.rst | 1 - Doc/c-api/decimal.rst | 231 ---------------- Include/pydecimal.h | 182 ------------- Lib/test/test_decimal.py | 176 ------------ .../2021-03-21-17-02-52.bpo-43422.POk6cU.rst | 1 + Modules/_decimal/_decimal.c | 200 +------------- Modules/_decimal/tests/bench.py | 9 +- Modules/_decimal/tests/deccheck.py | 81 +----- Modules/_decimal/tests/formathelper.py | 5 +- Modules/_testcapimodule.c | 253 ------------------ 10 files changed, 20 insertions(+), 1119 deletions(-) delete mode 100644 Doc/c-api/decimal.rst delete mode 100644 Include/pydecimal.h create mode 100644 Misc/NEWS.d/next/Library/2021-03-21-17-02-52.bpo-43422.POk6cU.rst diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst index bf263d6e4c2..c1d9fa1b41a 100644 --- a/Doc/c-api/concrete.rst +++ b/Doc/c-api/concrete.rst @@ -115,4 +115,3 @@ Other Objects coro.rst contextvars.rst datetime.rst - decimal.rst diff --git a/Doc/c-api/decimal.rst b/Doc/c-api/decimal.rst deleted file mode 100644 index 94cc4a7b845..00000000000 --- a/Doc/c-api/decimal.rst +++ /dev/null @@ -1,231 +0,0 @@ -.. sectionauthor:: Stefan Krah - -.. highlight:: c - - -Decimal capsule API -=================== - -Capsule API functions can be used in the same manner as regular library -functions, provided that the API has been initialized. - - -Initialize ----------- - -Typically, a C extension module that uses the decimal API will do these -steps in its init function: - -.. code-block:: c - - #include "pydecimal.h" - - static int decimal_initialized = 0; - if (!decimal_initialized) { - if (import_decimal() < 0) { - return NULL; - } - - decimal_initialized = 1; - } - - -Type checking, predicates, accessors ------------------------------------- - -.. c:function:: int PyDec_TypeCheck(const PyObject *dec) - - Return 1 if ``dec`` is a Decimal, 0 otherwise. This function does not set - any exceptions. - - -.. c:function:: int PyDec_IsSpecial(const PyObject *dec) - - Return 1 if ``dec`` is ``NaN``, ``sNaN`` or ``Infinity``, 0 otherwise. - - Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that - this is the only failure mode, so if ``dec`` has already been type-checked, no - errors can occur and the function can be treated as a simple predicate. - - -.. c:function:: int PyDec_IsNaN(const PyObject *dec) - - Return 1 if ``dec`` is ``NaN`` or ``sNaN``, 0 otherwise. - - Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that - this is the only failure mode, so if ``dec`` has already been type-checked, no - errors can occur and the function can be treated as a simple predicate. - - -.. c:function:: int PyDec_IsInfinite(const PyObject *dec) - - Return 1 if ``dec`` is ``Infinity``, 0 otherwise. - - Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that - this is the only failure mode, so if ``dec`` has already been type-checked, no - errors can occur and the function can be treated as a simple predicate. - - -.. c:function:: int64_t PyDec_GetDigits(const PyObject *dec) - - Return the number of digits in the coefficient. For ``Infinity``, the - number of digits is always zero. Typically, the same applies to ``NaN`` - and ``sNaN``, but both of these can have a payload that is equivalent to - a coefficient. Therefore, ``NaNs`` can have a nonzero return value. - - Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that - this is the only failure mode, so if ``dec`` has already been type-checked, no - errors can occur and the function can be treated as a simple accessor. - - -Exact conversions between decimals and primitive C types --------------------------------------------------------- - -This API supports conversions for decimals with a coefficient up to 38 digits. - -Data structures -~~~~~~~~~~~~~~~ - -The conversion functions use the following status codes and data structures: - -.. code-block:: c - - /* status cases for getting a triple */ - enum mpd_triple_class { - MPD_TRIPLE_NORMAL, - MPD_TRIPLE_INF, - MPD_TRIPLE_QNAN, - MPD_TRIPLE_SNAN, - MPD_TRIPLE_ERROR, - }; - - typedef struct { - enum mpd_triple_class tag; - uint8_t sign; - uint64_t hi; - uint64_t lo; - int64_t exp; - } mpd_uint128_triple_t; - -The status cases are explained below. ``sign`` is 0 for positive and 1 for negative. -``((uint128_t)hi << 64) + lo`` is the coefficient, ``exp`` is the exponent. - -The data structure is called "triple" because the decimal triple (sign, coeff, exp) -is an established term and (``hi``, ``lo``) represents a single ``uint128_t`` coefficient. - - -Functions -~~~~~~~~~ - -.. c:function:: mpd_uint128_triple_t PyDec_AsUint128Triple(const PyObject *dec) - - Convert a decimal to a triple. As above, it is guaranteed that the only - Python failure mode is a TypeError, checks can be omitted if the type is - known. - - For simplicity, the usage of the function and all special cases are - explained in code form and comments: - -.. code-block:: c - - triple = PyDec_AsUint128Triple(dec); - switch (triple.tag) { - case MPD_TRIPLE_QNAN: - /* - * Success: handle a quiet NaN. - * 1) triple.sign is 0 or 1. - * 2) triple.exp is always 0. - * 3) If triple.hi or triple.lo are nonzero, the NaN has a payload. - */ - break; - - case MPD_TRIPLE_SNAN: - /* - * Success: handle a signaling NaN. - * 1) triple.sign is 0 or 1. - * 2) triple.exp is always 0. - * 3) If triple.hi or triple.lo are nonzero, the sNaN has a payload. - */ - break; - - case MPD_TRIPLE_INF: - /* - * Success: handle Infinity. - * 1) triple.sign is 0 or 1. - * 2) triple.exp is always 0. - * 3) triple.hi and triple.lo are always zero. - */ - break; - - case MPD_TRIPLE_NORMAL: - /* Success: handle a finite value. */ - break; - - case MPD_TRIPLE_ERROR: - /* TypeError check: can be omitted if the type of dec is known. */ - if (PyErr_Occurred()) { - return NULL; - } - - /* Too large for conversion. PyDec_AsUint128Triple() does not set an - exception so applications can choose themselves. Typically this - would be a ValueError. */ - PyErr_SetString(PyExc_ValueError, - "value out of bounds for a uint128 triple"); - return NULL; - } - -.. c:function:: PyObject *PyDec_FromUint128Triple(const mpd_uint128_triple_t *triple) - - Create a decimal from a triple. The following rules must be observed for - initializing the triple: - - 1) ``triple.sign`` must always be 0 (for positive) or 1 (for negative). - - 2) ``MPD_TRIPLE_QNAN``: ``triple.exp`` must be 0. If ``triple.hi`` or ``triple.lo`` - are nonzero, create a ``NaN`` with a payload. - - 3) ``MPD_TRIPLE_SNAN``: ``triple.exp`` must be 0. If ``triple.hi`` or ``triple.lo`` - are nonzero, create an ``sNaN`` with a payload. - - 4) ``MPD_TRIPLE_INF``: ``triple.exp``, ``triple.hi`` and ``triple.lo`` must be zero. - - 5) ``MPD_TRIPLE_NORMAL``: ``MPD_MIN_ETINY + 38 < triple.exp < MPD_MAX_EMAX - 38``. - ``triple.hi`` and ``triple.lo`` can be chosen freely. - - 6) ``MPD_TRIPLE_ERROR``: It is always an error to set this tag. - - - If one of the above conditions is not met, the function returns ``NaN`` if - the ``InvalidOperation`` trap is not set in the thread local context. Otherwise, - it sets the ``InvalidOperation`` exception and returns NULL. - - Additionally, though extremely unlikely give the small allocation sizes, - the function can set ``MemoryError`` and return ``NULL``. - - -Advanced API ------------- - -This API enables the use of ``libmpdec`` functions. Since Python is compiled with -hidden symbols, the API requires an external libmpdec and the ``mpdecimal.h`` -header. - - -Functions -~~~~~~~~~ - -.. c:function:: PyObject *PyDec_Alloc(void) - - Return a new decimal that can be used in the ``result`` position of ``libmpdec`` - functions. - -.. c:function:: mpd_t *PyDec_Get(PyObject *v) - - Get a pointer to the internal ``mpd_t`` of the decimal. Decimals are immutable, - so this function must only be used on a new Decimal that has been created by - PyDec_Alloc(). - -.. c:function:: const mpd_t *PyDec_GetConst(const PyObject *v) - - Get a pointer to the constant internal ``mpd_t`` of the decimal. diff --git a/Include/pydecimal.h b/Include/pydecimal.h deleted file mode 100644 index f82e41f0aac..00000000000 --- a/Include/pydecimal.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2020 Stefan Krah. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - - -#ifndef CPYTHON_DECIMAL_H_ -#define CPYTHON_DECIMAL_H_ - - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************/ -/* Libmpdec API */ -/****************************************************************************/ - -#ifndef LIBMPDEC_MPDECIMAL_H_ -struct mpd_t; /* ABI-stable in the libmpdec-2.x series */ - -/* status cases for getting a triple */ -enum mpd_triple_class { - MPD_TRIPLE_NORMAL, - MPD_TRIPLE_INF, - MPD_TRIPLE_QNAN, - MPD_TRIPLE_SNAN, - MPD_TRIPLE_ERROR, -}; - -typedef struct { - enum mpd_triple_class tag; - uint8_t sign; - uint64_t hi; - uint64_t lo; - int64_t exp; -} mpd_uint128_triple_t; -#endif - - -/****************************************************************************/ -/* Capsule API */ -/****************************************************************************/ - -#define PyDec_CAPSULE_NAME "_decimal._API" - -/* Simple API */ -#define PyDec_TypeCheck_INDEX 0 -#define PyDec_TypeCheck_RETURN int -#define PyDec_TypeCheck_ARGS (const PyObject *) - -#define PyDec_IsSpecial_INDEX 1 -#define PyDec_IsSpecial_RETURN int -#define PyDec_IsSpecial_ARGS (const PyObject *) - -#define PyDec_IsNaN_INDEX 2 -#define PyDec_IsNaN_RETURN int -#define PyDec_IsNaN_ARGS (const PyObject *) - -#define PyDec_IsInfinite_INDEX 3 -#define PyDec_IsInfinite_RETURN int -#define PyDec_IsInfinite_ARGS (const PyObject *) - -#define PyDec_GetDigits_INDEX 4 -#define PyDec_GetDigits_RETURN int64_t -#define PyDec_GetDigits_ARGS (const PyObject *) - -#define PyDec_AsUint128Triple_INDEX 5 -#define PyDec_AsUint128Triple_RETURN mpd_uint128_triple_t -#define PyDec_AsUint128Triple_ARGS (const PyObject *) - -#define PyDec_FromUint128Triple_INDEX 6 -#define PyDec_FromUint128Triple_RETURN PyObject * -#define PyDec_FromUint128Triple_ARGS (const mpd_uint128_triple_t *triple) - -/* Advanced API */ -#define PyDec_Alloc_INDEX 7 -#define PyDec_Alloc_RETURN PyObject * -#define PyDec_Alloc_ARGS (void) - -#define PyDec_Get_INDEX 8 -#define PyDec_Get_RETURN mpd_t * -#define PyDec_Get_ARGS (PyObject *) - -#define PyDec_GetConst_INDEX 9 -#define PyDec_GetConst_RETURN const mpd_t * -#define PyDec_GetConst_ARGS (const PyObject *) - -#define CPYTHON_DECIMAL_MAX_API 10 - - -#ifdef CPYTHON_DECIMAL_MODULE -/* Simple API */ -static PyDec_TypeCheck_RETURN PyDec_TypeCheck PyDec_TypeCheck_ARGS; -static PyDec_IsSpecial_RETURN PyDec_IsSpecial PyDec_IsSpecial_ARGS; -static PyDec_IsNaN_RETURN PyDec_IsNaN PyDec_IsNaN_ARGS; -static PyDec_IsInfinite_RETURN PyDec_IsInfinite PyDec_IsInfinite_ARGS; -static PyDec_GetDigits_RETURN PyDec_GetDigits PyDec_GetDigits_ARGS; -static PyDec_AsUint128Triple_RETURN PyDec_AsUint128Triple PyDec_AsUint128Triple_ARGS; -static PyDec_FromUint128Triple_RETURN PyDec_FromUint128Triple PyDec_FromUint128Triple_ARGS; - -/* Advanced API */ -static PyDec_Alloc_RETURN PyDec_Alloc PyDec_Alloc_ARGS; -static PyDec_Get_RETURN PyDec_Get PyDec_Get_ARGS; -static PyDec_GetConst_RETURN PyDec_GetConst PyDec_GetConst_ARGS; -#else -static void **_decimal_api; - -/* Simple API */ -#define PyDec_TypeCheck \ - (*(PyDec_TypeCheck_RETURN (*)PyDec_TypeCheck_ARGS) _decimal_api[PyDec_TypeCheck_INDEX]) - -#define PyDec_IsSpecial \ - (*(PyDec_IsSpecial_RETURN (*)PyDec_IsSpecial_ARGS) _decimal_api[PyDec_IsSpecial_INDEX]) - -#define PyDec_IsNaN \ - (*(PyDec_IsNaN_RETURN (*)PyDec_IsNaN_ARGS) _decimal_api[PyDec_IsNaN_INDEX]) - -#define PyDec_IsInfinite \ - (*(PyDec_IsInfinite_RETURN (*)PyDec_IsInfinite_ARGS) _decimal_api[PyDec_IsInfinite_INDEX]) - -#define PyDec_GetDigits \ - (*(PyDec_GetDigits_RETURN (*)PyDec_GetDigits_ARGS) _decimal_api[PyDec_GetDigits_INDEX]) - -#define PyDec_AsUint128Triple \ - (*(PyDec_AsUint128Triple_RETURN (*)PyDec_AsUint128Triple_ARGS) _decimal_api[PyDec_AsUint128Triple_INDEX]) - -#define PyDec_FromUint128Triple \ - (*(PyDec_FromUint128Triple_RETURN (*)PyDec_FromUint128Triple_ARGS) _decimal_api[PyDec_FromUint128Triple_INDEX]) - -/* Advanced API */ -#define PyDec_Alloc \ - (*(PyDec_Alloc_RETURN (*)PyDec_Alloc_ARGS) _decimal_api[PyDec_Alloc_INDEX]) - -#define PyDec_Get \ - (*(PyDec_Get_RETURN (*)PyDec_Get_ARGS) _decimal_api[PyDec_Get_INDEX]) - -#define PyDec_GetConst \ - (*(PyDec_GetConst_RETURN (*)PyDec_GetConst_ARGS) _decimal_api[PyDec_GetConst_INDEX]) - - -static int -import_decimal(void) -{ - _decimal_api = (void **)PyCapsule_Import(PyDec_CAPSULE_NAME, 0); - if (_decimal_api == NULL) { - return -1; - } - - return 0; -} -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* CPYTHON_DECIMAL_H_ */ diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index dbd58e8a651..6aa4a9b31a5 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -43,13 +43,6 @@ import random import inspect import threading -from _testcapi import decimal_is_special -from _testcapi import decimal_is_nan -from _testcapi import decimal_is_infinite -from _testcapi import decimal_get_digits -from _testcapi import decimal_as_triple -from _testcapi import decimal_from_triple - C = import_fresh_module('decimal', fresh=['_decimal']) P = import_fresh_module('decimal', blocked=['_decimal']) @@ -4758,175 +4751,6 @@ class CFunctionality(unittest.TestCase): self.assertEqual(C.DecTraps, C.DecErrors|C.DecOverflow|C.DecUnderflow) - def test_decimal_api_predicates(self): - # Capsule API - - d = C.Decimal("0") - self.assertFalse(decimal_is_special(d)) - self.assertFalse(decimal_is_nan(d)) - self.assertFalse(decimal_is_infinite(d)) - - d = C.Decimal("NaN") - self.assertTrue(decimal_is_special(d)) - self.assertTrue(decimal_is_nan(d)) - self.assertFalse(decimal_is_infinite(d)) - - d = C.Decimal("sNaN") - self.assertTrue(decimal_is_special(d)) - self.assertTrue(decimal_is_nan(d)) - self.assertFalse(decimal_is_infinite(d)) - - d = C.Decimal("inf") - self.assertTrue(decimal_is_special(d)) - self.assertFalse(decimal_is_nan(d)) - self.assertTrue(decimal_is_infinite(d)) - - def test_decimal_api_get_digits(self): - # Capsule API - - d = C.Decimal("0") - self.assertEqual(decimal_get_digits(d), 1) - - d = C.Decimal("1234567890") - self.assertEqual(decimal_get_digits(d), 10) - - d = C.Decimal("inf") - self.assertEqual(decimal_get_digits(d), 0) - - d = C.Decimal("NaN") - self.assertEqual(decimal_get_digits(d), 0) - - d = C.Decimal("sNaN") - self.assertEqual(decimal_get_digits(d), 0) - - d = C.Decimal("NaN1234567890") - self.assertEqual(decimal_get_digits(d), 10) - - d = C.Decimal("sNaN1234567890") - self.assertEqual(decimal_get_digits(d), 10) - - def test_decimal_api_triple(self): - # Capsule API - - def as_triple(d): - """Convert a decimal to a decimal triple with a split uint128_t - coefficient: - - (sign, hi, lo, exp) - - It is called 'triple' because (hi, lo) are regarded as a single - uint128_t that is split because not all compilers support uint128_t. - """ - sign, digits, exp = d.as_tuple() - - s = "".join(str(d) for d in digits) - coeff = int(s) if s else 0 - - if coeff < 0 or coeff >= 2**128: - raise ValueError("value out of bounds for a uint128 triple"); - - hi, lo = divmod(coeff, 2**64) - return (sign, hi, lo, exp) - - def from_triple(triple): - """Convert a decimal triple with a split uint128_t coefficient to a string. - """ - sign, hi, lo, exp = triple - coeff = hi * 2**64 + lo - - if coeff < 0 or coeff >= 2**128: - raise ValueError("value out of bounds for a uint128 triple"); - - digits = tuple(int(c) for c in str(coeff)) - - return P.Decimal((sign, digits, exp)) - - signs = ["", "-"] - - coefficients = [ - "000000000000000000000000000000000000000", - - "299999999999999999999999999999999999999", - "299999999999999999990000000000000000000", - "200000000000000000009999999999999999999", - "000000000000000000009999999999999999999", - - "299999999999999999999999999999000000000", - "299999999999999999999000000000999999999", - "299999999999000000000999999999999999999", - "299000000000999999999999999999999999999", - "000999999999999999999999999999999999999", - - "300000000000000000000000000000000000000", - "310000000000000000001000000000000000000", - "310000000000000000000000000000000000000", - "300000000000000000001000000000000000000", - - "340100000000100000000100000000100000000", - "340100000000100000000100000000000000000", - "340100000000100000000000000000100000000", - "340100000000000000000100000000100000000", - "340000000000100000000100000000100000000", - - "340282366920938463463374607431768211455", - ] - - exponents = [ - "E+0", "E+1", "E-1", - "E+%s" % str(C.MAX_EMAX-38), - "E-%s" % str(C.MIN_ETINY+38), - ] - - for sign in signs: - for coeff in coefficients: - for exp in exponents: - s = sign + coeff + exp - - ctriple = decimal_as_triple(C.Decimal(s)) - ptriple = as_triple(P.Decimal(s)) - self.assertEqual(ctriple, ptriple) - - c = decimal_from_triple(ctriple) - p = decimal_from_triple(ptriple) - self.assertEqual(str(c), str(p)) - - for s in ["NaN", "-NaN", "sNaN", "-sNaN", "NaN123", "sNaN123", "inf", "-inf"]: - ctriple = decimal_as_triple(C.Decimal(s)) - ptriple = as_triple(P.Decimal(s)) - self.assertEqual(ctriple, ptriple) - - c = decimal_from_triple(ctriple) - p = decimal_from_triple(ptriple) - self.assertEqual(str(c), str(p)) - - def test_decimal_api_errors(self): - # Capsule API - - self.assertRaises(TypeError, decimal_as_triple, "X") - self.assertRaises(ValueError, decimal_as_triple, C.Decimal(2**128)) - self.assertRaises(ValueError, decimal_as_triple, C.Decimal(-2**128)) - - self.assertRaises(TypeError, decimal_from_triple, "X") - self.assertRaises(ValueError, decimal_from_triple, ()) - self.assertRaises(ValueError, decimal_from_triple, (1, 2, 3, 4, 5)) - self.assertRaises(ValueError, decimal_from_triple, (2**8, 0, 0, 0)) - self.assertRaises(OverflowError, decimal_from_triple, (0, 2**64, 0, 0)) - self.assertRaises(OverflowError, decimal_from_triple, (0, 0, 2**64, 0)) - self.assertRaises(OverflowError, decimal_from_triple, (0, 0, 0, 2**63)) - self.assertRaises(OverflowError, decimal_from_triple, (0, 0, 0, -2**63-1)) - self.assertRaises(ValueError, decimal_from_triple, (0, 0, 0, "X")) - self.assertRaises(TypeError, decimal_from_triple, (0, 0, 0, ())) - - with C.localcontext(C.Context()): - self.assertRaises(C.InvalidOperation, decimal_from_triple, (2, 0, 0, 0)) - self.assertRaises(C.InvalidOperation, decimal_from_triple, (0, 0, 0, 2**63-1)) - self.assertRaises(C.InvalidOperation, decimal_from_triple, (0, 0, 0, -2**63)) - - self.assertRaises(TypeError, decimal_is_special, "X") - self.assertRaises(TypeError, decimal_is_nan, "X") - self.assertRaises(TypeError, decimal_is_infinite, "X") - self.assertRaises(TypeError, decimal_get_digits, "X") - class CWhitebox(unittest.TestCase): """Whitebox testing for _decimal""" diff --git a/Misc/NEWS.d/next/Library/2021-03-21-17-02-52.bpo-43422.POk6cU.rst b/Misc/NEWS.d/next/Library/2021-03-21-17-02-52.bpo-43422.POk6cU.rst new file mode 100644 index 00000000000..4de3060d26e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-03-21-17-02-52.bpo-43422.POk6cU.rst @@ -0,0 +1 @@ +Revert the _decimal C API which was added in bpo-41324. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 664d45a9048..83e237d02bf 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2020 Stefan Krah. All rights reserved. + * Copyright (c) 2008-2012 Stefan Krah. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -33,8 +33,6 @@ #include -#define CPYTHON_DECIMAL_MODULE -#include "pydecimal.h" #include "docstrings.h" @@ -5570,175 +5568,6 @@ static PyTypeObject PyDecContext_Type = }; -/****************************************************************************/ -/* C-API */ -/****************************************************************************/ - -/* Simple API */ -static int -PyDec_TypeCheck(const PyObject *v) -{ - return PyDec_Check(v); -} - -static int -PyDec_IsSpecial(const PyObject *v) -{ - if (!PyDec_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "PyDec_IsSpecial: argument must be a Decimal"); - return -1; - } - - return mpd_isspecial(MPD(v)); -} - -static int -PyDec_IsNaN(const PyObject *v) -{ - if (!PyDec_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "PyDec_IsNaN: argument must be a Decimal"); - return -1; - } - - return mpd_isnan(MPD(v)); -} - -static int -PyDec_IsInfinite(const PyObject *v) -{ - if (!PyDec_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "PyDec_IsInfinite: argument must be a Decimal"); - return -1; - } - - return mpd_isinfinite(MPD(v)); -} - -static int64_t -PyDec_GetDigits(const PyObject *v) -{ - if (!PyDec_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "PyDec_GetDigits: argument must be a Decimal"); - return -1; - } - - return MPD(v)->digits; -} - -static mpd_uint128_triple_t -PyDec_AsUint128Triple(const PyObject *v) -{ - if (!PyDec_Check(v)) { - mpd_uint128_triple_t triple = { MPD_TRIPLE_ERROR, 0, 0, 0, 0 }; - PyErr_SetString(PyExc_TypeError, - "PyDec_AsUint128Triple: argument must be a Decimal"); - return triple; - } - - return mpd_as_uint128_triple(MPD(v)); -} - -static PyObject * -PyDec_FromUint128Triple(const mpd_uint128_triple_t *triple) -{ - PyObject *context; - PyObject *result; - uint32_t status = 0; - - CURRENT_CONTEXT(context); - - result = dec_alloc(); - if (result == NULL) { - return NULL; - } - - if (mpd_from_uint128_triple(MPD(result), triple, &status) < 0) { - if (dec_addstatus(context, status)) { - Py_DECREF(result); - return NULL; - } - } - - return result; -} - -/* Advanced API */ -static PyObject * -PyDec_Alloc(void) -{ - return dec_alloc(); -} - -static mpd_t * -PyDec_Get(PyObject *v) -{ - if (!PyDec_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "PyDec_Get: argument must be a Decimal"); - return NULL; - } - - return MPD(v); -} - -static const mpd_t * -PyDec_GetConst(const PyObject *v) -{ - if (!PyDec_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "PyDec_GetConst: argument must be a Decimal"); - return NULL; - } - - return MPD(v); -} - -static void -destroy_api(PyObject *capsule) -{ - void *capi = PyCapsule_GetPointer(capsule, PyDec_CAPSULE_NAME); - PyMem_Free(capi); -} - -static PyObject * -init_api(void) -{ - void **_decimal_api = PyMem_Calloc(CPYTHON_DECIMAL_MAX_API, sizeof(void *)); - if (_decimal_api == NULL) { - PyErr_NoMemory(); - return NULL; - } - - /* Simple API */ - _decimal_api[PyDec_TypeCheck_INDEX] = (void *)PyDec_TypeCheck; - _decimal_api[PyDec_IsSpecial_INDEX] = (void *)PyDec_IsSpecial; - _decimal_api[PyDec_IsNaN_INDEX] = (void *)PyDec_IsNaN; - _decimal_api[PyDec_IsInfinite_INDEX] = (void *)PyDec_IsInfinite; - _decimal_api[PyDec_GetDigits_INDEX] = (void *)PyDec_GetDigits; - _decimal_api[PyDec_AsUint128Triple_INDEX] = (void *)PyDec_AsUint128Triple; - _decimal_api[PyDec_FromUint128Triple_INDEX] = (void *)PyDec_FromUint128Triple; - - /* Advanced API */ - _decimal_api[PyDec_Alloc_INDEX] = (void *)PyDec_Alloc; - _decimal_api[PyDec_Get_INDEX] = (void *)PyDec_Get; - _decimal_api[PyDec_GetConst_INDEX] = (void *)PyDec_GetConst; - - PyObject *capsule = PyCapsule_New(_decimal_api, PyDec_CAPSULE_NAME, destroy_api); - if (!capsule) { - PyMem_Free(_decimal_api); - } - return capsule; -} - - -/****************************************************************************/ -/* Module */ -/****************************************************************************/ - static PyMethodDef _decimal_methods [] = { { "getcontext", (PyCFunction)PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext}, @@ -5849,27 +5678,17 @@ PyInit__decimal(void) DecCondMap *cm; struct ssize_constmap *ssize_cm; struct int_constmap *int_cm; - static PyObject *capsule = NULL; - static int initialized = 0; int i; /* Init libmpdec */ - if (!initialized) { - mpd_traphandler = dec_traphandler; - mpd_mallocfunc = PyMem_Malloc; - mpd_reallocfunc = PyMem_Realloc; - mpd_callocfunc = mpd_callocfunc_em; - mpd_free = PyMem_Free; - mpd_setminalloc(_Py_DEC_MINALLOC); + mpd_traphandler = dec_traphandler; + mpd_mallocfunc = PyMem_Malloc; + mpd_reallocfunc = PyMem_Realloc; + mpd_callocfunc = mpd_callocfunc_em; + mpd_free = PyMem_Free; + mpd_setminalloc(_Py_DEC_MINALLOC); - capsule = init_api(); - if (capsule == NULL) { - return NULL; - } - - initialized = 1; - } /* Init external C-API functions */ _py_long_multiply = PyLong_Type.tp_as_number->nb_multiply; @@ -6094,10 +5913,6 @@ PyInit__decimal(void) CHECK_INT(PyModule_AddStringConstant(m, "__version__", "1.70")); CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", mpd_version())); - /* Add capsule API */ - if (PyModule_AddObjectRef(m, "_API", capsule) < 0) { - goto error; - } return m; @@ -6121,7 +5936,6 @@ error: Py_CLEAR(basic_context_template); /* GCOV_NOT_REACHED */ Py_CLEAR(extended_context_template); /* GCOV_NOT_REACHED */ Py_CLEAR(m); /* GCOV_NOT_REACHED */ - Py_CLEAR(capsule); /* GCOV_NOT_REACHED */ return NULL; /* GCOV_NOT_REACHED */ } diff --git a/Modules/_decimal/tests/bench.py b/Modules/_decimal/tests/bench.py index 88fd7b5ae0b..3726db194e0 100644 --- a/Modules/_decimal/tests/bench.py +++ b/Modules/_decimal/tests/bench.py @@ -7,10 +7,13 @@ import time +try: + from test.support import import_fresh_module +except ImportError: + from test.test_support import import_fresh_module -import _decimal as C -import _pydecimal as P - +C = import_fresh_module('decimal', fresh=['_decimal']) +P = import_fresh_module('decimal', blocked=['_decimal']) # # NOTE: This is the pi function from the decimal documentation, modified diff --git a/Modules/_decimal/tests/deccheck.py b/Modules/_decimal/tests/deccheck.py index 5de57d1f875..98ecd502c03 100644 --- a/Modules/_decimal/tests/deccheck.py +++ b/Modules/_decimal/tests/deccheck.py @@ -47,18 +47,14 @@ from subprocess import PIPE, STDOUT from queue import Queue, Empty from threading import Thread, Event, Lock +from test.support import import_fresh_module from randdec import randfloat, all_unary, all_binary, all_ternary from randdec import unary_optarg, binary_optarg, ternary_optarg from formathelper import rand_format, rand_locale from _pydecimal import _dec_from_triple -from _testcapi import decimal_as_triple -from _testcapi import decimal_from_triple - -import _decimal as C -import _pydecimal as P - - +C = import_fresh_module('decimal', fresh=['_decimal']) +P = import_fresh_module('decimal', blocked=['_decimal']) EXIT_STATUS = 0 @@ -161,45 +157,6 @@ BinaryRestricted = ['__round__'] TernaryRestricted = ['__pow__', 'context.power'] -# ====================================================================== -# Triple tests -# ====================================================================== - -def c_as_triple(dec): - sign, hi, lo, exp = decimal_as_triple(dec) - - coeff = hi * 2**64 + lo - return (sign, coeff, exp) - -def c_from_triple(triple): - sign, coeff, exp = triple - - hi = coeff // 2**64 - lo = coeff % 2**64 - return decimal_from_triple((sign, hi, lo, exp)) - -def p_as_triple(dec): - sign, digits, exp = dec.as_tuple() - - s = "".join(str(d) for d in digits) - coeff = int(s) if s else 0 - - if coeff < 0 or coeff >= 2**128: - raise ValueError("value out of bounds for a uint128 triple") - - return (sign, coeff, exp) - -def p_from_triple(triple): - sign, coeff, exp = triple - - if coeff < 0 or coeff >= 2**128: - raise ValueError("value out of bounds for a uint128 triple") - - digits = tuple(int(c) for c in str(coeff)) - - return P.Decimal((sign, digits, exp)) - - # ====================================================================== # Unified Context # ====================================================================== @@ -893,44 +850,12 @@ def verify(t, stat): t.presults.append(str(t.rp.imag)) t.presults.append(str(t.rp.real)) - ctriple = None - if str(t.rc) == str(t.rp): # see skip handler - try: - ctriple = c_as_triple(t.rc) - except ValueError: - try: - ptriple = p_as_triple(t.rp) - except ValueError: - pass - else: - raise RuntimeError("ValueError not raised") - else: - cres = c_from_triple(ctriple) - t.cresults.append(ctriple) - t.cresults.append(str(cres)) - - ptriple = p_as_triple(t.rp) - pres = p_from_triple(ptriple) - t.presults.append(ptriple) - t.presults.append(str(pres)) - if t.with_maxcontext and isinstance(t.rmax, C.Decimal): t.maxresults.append(t.rmax.to_eng_string()) t.maxresults.append(t.rmax.as_tuple()) t.maxresults.append(str(t.rmax.imag)) t.maxresults.append(str(t.rmax.real)) - if ctriple is not None: - # NaN payloads etc. depend on precision and clamp. - if all_nan(t.rc) and all_nan(t.rmax): - t.maxresults.append(ctriple) - t.maxresults.append(str(cres)) - else: - maxtriple = c_as_triple(t.rmax) - maxres = c_from_triple(maxtriple) - t.maxresults.append(maxtriple) - t.maxresults.append(str(maxres)) - nc = t.rc.number_class().lstrip('+-s') stat[nc] += 1 else: diff --git a/Modules/_decimal/tests/formathelper.py b/Modules/_decimal/tests/formathelper.py index 482e02a25c2..19b2aad4a50 100644 --- a/Modules/_decimal/tests/formathelper.py +++ b/Modules/_decimal/tests/formathelper.py @@ -31,10 +31,11 @@ import os, sys, locale, random import platform, subprocess +from test.support import import_fresh_module from distutils.spawn import find_executable -import _decimal as C -import _pydecimal as P +C = import_fresh_module('decimal', fresh=['_decimal']) +P = import_fresh_module('decimal', blocked=['_decimal']) windows_lang_strings = [ diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ed59c32e9c5..20107f22579 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -19,7 +19,6 @@ #include "Python.h" #include "datetime.h" -#include "pydecimal.h" #include "marshal.h" #include "structmember.h" // PyMemberDef #include @@ -2764,252 +2763,6 @@ test_PyDateTime_DELTA_GET(PyObject *self, PyObject *obj) return Py_BuildValue("(lll)", days, seconds, microseconds); } -/* Test decimal API */ -static int decimal_initialized = 0; -static PyObject * -decimal_is_special(PyObject *module, PyObject *dec) -{ - int is_special; - - (void)module; - if (!decimal_initialized) { - if (import_decimal() < 0) { - return NULL; - } - - decimal_initialized = 1; - } - - is_special = PyDec_IsSpecial(dec); - if (is_special < 0) { - return NULL; - } - - return PyBool_FromLong(is_special); -} - -static PyObject * -decimal_is_nan(PyObject *module, PyObject *dec) -{ - int is_nan; - - (void)module; - if (!decimal_initialized) { - if (import_decimal() < 0) { - return NULL; - } - - decimal_initialized = 1; - } - - is_nan = PyDec_IsNaN(dec); - if (is_nan < 0) { - return NULL; - } - - return PyBool_FromLong(is_nan); -} - -static PyObject * -decimal_is_infinite(PyObject *module, PyObject *dec) -{ - int is_infinite; - - (void)module; - if (!decimal_initialized) { - if (import_decimal() < 0) { - return NULL; - } - - decimal_initialized = 1; - } - - is_infinite = PyDec_IsInfinite(dec); - if (is_infinite < 0) { - return NULL; - } - - return PyBool_FromLong(is_infinite); -} - -static PyObject * -decimal_get_digits(PyObject *module, PyObject *dec) -{ - int64_t digits; - - (void)module; - if (!decimal_initialized) { - if (import_decimal() < 0) { - return NULL; - } - - decimal_initialized = 1; - } - - digits = PyDec_GetDigits(dec); - if (digits < 0) { - return NULL; - } - - return PyLong_FromLongLong(digits); -} - -static PyObject * -decimal_as_triple(PyObject *module, PyObject *dec) -{ - PyObject *tuple = NULL; - PyObject *sign, *hi, *lo; - mpd_uint128_triple_t triple; - - (void)module; - if (!decimal_initialized) { - if (import_decimal() < 0) { - return NULL; - } - - decimal_initialized = 1; - } - - triple = PyDec_AsUint128Triple(dec); - if (triple.tag == MPD_TRIPLE_ERROR && PyErr_Occurred()) { - return NULL; - } - - sign = PyLong_FromUnsignedLong(triple.sign); - if (sign == NULL) { - return NULL; - } - - hi = PyLong_FromUnsignedLongLong(triple.hi); - if (hi == NULL) { - Py_DECREF(sign); - return NULL; - } - - lo = PyLong_FromUnsignedLongLong(triple.lo); - if (lo == NULL) { - Py_DECREF(hi); - Py_DECREF(sign); - return NULL; - } - - switch (triple.tag) { - case MPD_TRIPLE_QNAN: - assert(triple.exp == 0); - tuple = Py_BuildValue("(OOOs)", sign, hi, lo, "n"); - break; - - case MPD_TRIPLE_SNAN: - assert(triple.exp == 0); - tuple = Py_BuildValue("(OOOs)", sign, hi, lo, "N"); - break; - - case MPD_TRIPLE_INF: - assert(triple.hi == 0); - assert(triple.lo == 0); - assert(triple.exp == 0); - tuple = Py_BuildValue("(OOOs)", sign, hi, lo, "F"); - break; - - case MPD_TRIPLE_NORMAL: - tuple = Py_BuildValue("(OOOL)", sign, hi, lo, triple.exp); - break; - - case MPD_TRIPLE_ERROR: - PyErr_SetString(PyExc_ValueError, - "value out of bounds for a uint128 triple"); - break; - - default: - PyErr_SetString(PyExc_RuntimeError, - "decimal_as_triple: internal error: unexpected tag"); - break; - } - - Py_DECREF(lo); - Py_DECREF(hi); - Py_DECREF(sign); - - return tuple; -} - -static PyObject * -decimal_from_triple(PyObject *module, PyObject *tuple) -{ - mpd_uint128_triple_t triple = { MPD_TRIPLE_ERROR, 0, 0, 0, 0 }; - PyObject *exp; - unsigned long sign; - - (void)module; - if (!decimal_initialized) { - if (import_decimal() < 0) { - return NULL; - } - - decimal_initialized = 1; - } - - if (!PyTuple_Check(tuple)) { - PyErr_SetString(PyExc_TypeError, "argument must be a tuple"); - return NULL; - } - - if (PyTuple_GET_SIZE(tuple) != 4) { - PyErr_SetString(PyExc_ValueError, "tuple size must be 4"); - return NULL; - } - - sign = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(tuple, 0)); - if (sign == (unsigned long)-1 && PyErr_Occurred()) { - return NULL; - } - if (sign > UINT8_MAX) { - PyErr_SetString(PyExc_ValueError, "sign must be 0 or 1"); - return NULL; - } - triple.sign = (uint8_t)sign; - - triple.hi = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(tuple, 1)); - if (triple.hi == (unsigned long long)-1 && PyErr_Occurred()) { - return NULL; - } - - triple.lo = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(tuple, 2)); - if (triple.lo == (unsigned long long)-1 && PyErr_Occurred()) { - return NULL; - } - - exp = PyTuple_GET_ITEM(tuple, 3); - if (PyLong_Check(exp)) { - triple.tag = MPD_TRIPLE_NORMAL; - triple.exp = PyLong_AsLongLong(exp); - if (triple.exp == -1 && PyErr_Occurred()) { - return NULL; - } - } - else if (PyUnicode_Check(exp)) { - if (PyUnicode_CompareWithASCIIString(exp, "F") == 0) { - triple.tag = MPD_TRIPLE_INF; - } - else if (PyUnicode_CompareWithASCIIString(exp, "n") == 0) { - triple.tag = MPD_TRIPLE_QNAN; - } - else if (PyUnicode_CompareWithASCIIString(exp, "N") == 0) { - triple.tag = MPD_TRIPLE_SNAN; - } - else { - PyErr_SetString(PyExc_ValueError, "not a valid exponent"); - return NULL; - } - } - else { - PyErr_SetString(PyExc_TypeError, "exponent must be int or string"); - return NULL; - } - - return PyDec_FromUint128Triple(&triple); -} - /* test_thread_state spawns a thread of its own, and that thread releases * `thread_done` when it's finished. The driver code has to know when the * thread finishes, because the thread uses a PyObject (the callable) that @@ -5725,12 +5478,6 @@ static PyMethodDef TestMethods[] = { {"PyDateTime_DATE_GET", test_PyDateTime_DATE_GET, METH_O}, {"PyDateTime_TIME_GET", test_PyDateTime_TIME_GET, METH_O}, {"PyDateTime_DELTA_GET", test_PyDateTime_DELTA_GET, METH_O}, - {"decimal_is_special", decimal_is_special, METH_O}, - {"decimal_is_nan", decimal_is_nan, METH_O}, - {"decimal_is_infinite", decimal_is_infinite, METH_O}, - {"decimal_get_digits", decimal_get_digits, METH_O}, - {"decimal_as_triple", decimal_as_triple, METH_O}, - {"decimal_from_triple", decimal_from_triple, METH_O}, {"test_list_api", test_list_api, METH_NOARGS}, {"test_dict_iteration", test_dict_iteration, METH_NOARGS}, {"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS},