From 8754016bc49174c4d7fe3711b427dd5225663099 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Fri, 17 Jul 2020 12:48:26 +0200 Subject: [PATCH 01/17] Add a minimal decimal capsule API. --- Include/decimal.h | 137 +++++++++ Lib/test/test_decimal.py | 120 ++++++++ Modules/_decimal/_decimal.c | 114 +++++++- Modules/_decimal/libmpdec_triple.h | 455 +++++++++++++++++++++++++++++ Modules/_decimal/tests/deccheck.py | 74 +++++ Modules/_testcapimodule.c | 164 +++++++++++ setup.py | 1 + 7 files changed, 1058 insertions(+), 7 deletions(-) create mode 100644 Include/decimal.h create mode 100644 Modules/_decimal/libmpdec_triple.h diff --git a/Include/decimal.h b/Include/decimal.h new file mode 100644 index 00000000000000..6e9a406be57902 --- /dev/null +++ b/Include/decimal.h @@ -0,0 +1,137 @@ +/* + * 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 */ +/****************************************************************************/ + +/* + * Copied from libmpdec_triple.h. This will be in mpdecimal.h once the + * functions are integrated into libmpdec. + */ + +#ifndef LIBMPDEC_TRIPLE_H_ +struct mpd_t; + +/* 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_TypeCheck_INDEX 0 +#define PyDec_TypeCheck_RETURN int +#define PyDec_TypeCheck_ARGS (const PyObject *) + +#define PyDec_Alloc_INDEX 1 +#define PyDec_Alloc_RETURN PyObject * +#define PyDec_Alloc_ARGS (void) + +#define PyDec_Get_INDEX 2 +#define PyDec_Get_RETURN mpd_t * +#define PyDec_Get_ARGS (const PyObject *) + +#define PyDec_AsUint128Triple_INDEX 3 +#define PyDec_AsUint128Triple_RETURN mpd_uint128_triple_t +#define PyDec_AsUint128Triple_ARGS (const PyObject *) + +#define PyDec_FromUint128Triple_INDEX 4 +#define PyDec_FromUint128Triple_RETURN PyObject * +#define PyDec_FromUint128Triple_ARGS (const mpd_uint128_triple_t *triple) + +#define CPYTHON_DECIMAL_MAX_API 5 + +#ifdef CPYTHON_DECIMAL_MODULE +static PyDec_TypeCheck_RETURN PyDec_TypeCheck PyDec_TypeCheck_ARGS; +static PyDec_Alloc_RETURN PyDec_Alloc PyDec_Alloc_ARGS; +static PyDec_Get_RETURN PyDec_Get PyDec_Get_ARGS; +static PyDec_AsUint128Triple_RETURN PyDec_AsUint128Triple PyDec_AsUint128Triple_ARGS; +static PyDec_FromUint128Triple_RETURN PyDec_FromUint128Triple PyDec_FromUint128Triple_ARGS; +#else +static void **_decimal_api; + +#define PyDec_TypeCheck \ + (*(PyDec_TypeCheck_RETURN (*)PyDec_TypeCheck_ARGS) _decimal_api[PyDec_TypeCheck_INDEX]) + +#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_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]) + + +static int +import_decimal(void) +{ + _decimal_api = (void **)PyCapsule_Import("_decimal._API", 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 9dbae449fb61d9..fb805940c682e0 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -42,6 +42,9 @@ import inspect import threading +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']) @@ -4748,6 +4751,123 @@ def test_constants(self): self.assertEqual(C.DecTraps, C.DecErrors|C.DecOverflow|C.DecUnderflow) + 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)) + class CWhitebox(unittest.TestCase): """Whitebox testing for _decimal""" diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index fb4e020f1260e6..4ca531728ecded 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2012 Stefan Krah. All rights reserved. + * Copyright (c) 2008-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 @@ -33,6 +33,9 @@ #include +#define CPYTHON_DECIMAL_MODULE +#include "libmpdec_triple.h" +#include "decimal.h" #include "docstrings.h" @@ -5555,6 +5558,88 @@ static PyTypeObject PyDecContext_Type = }; +/****************************************************************************/ +/* C-API */ +/****************************************************************************/ + +static void **_decimal_api[CPYTHON_DECIMAL_MAX_API]; + +static int +PyDec_TypeCheck(const PyObject *v) +{ + return PyDec_Check(v); +} + +static PyObject * +PyDec_Alloc(void) +{ + return dec_alloc(); +} + +static mpd_t * +PyDec_Get(const PyObject *v) +{ + if (!PyDec_Check(v)) { + PyErr_SetString(PyExc_TypeError, + "PyDec_Get: argument must be a Decimal"); + return NULL; + } + + return MPD(v); +} + +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 }; + 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; +} + +static PyObject * +init_api(void) +{ + _decimal_api[PyDec_TypeCheck_INDEX] = (void *)PyDec_TypeCheck; + _decimal_api[PyDec_Alloc_INDEX] = (void *)PyDec_Alloc; + _decimal_api[PyDec_Get_INDEX] = (void *)PyDec_Get; + _decimal_api[PyDec_AsUint128Triple_INDEX] = (void *)PyDec_AsUint128Triple; + _decimal_api[PyDec_FromUint128Triple_INDEX] = (void *)PyDec_FromUint128Triple; + + return PyCapsule_New(_decimal_api, "_decimal._API", NULL); +} + + +/****************************************************************************/ +/* Module */ +/****************************************************************************/ + static PyMethodDef _decimal_methods [] = { { "getcontext", (PyCFunction)PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext}, @@ -5665,17 +5750,27 @@ 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 */ - 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); + 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); + + 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; @@ -5900,6 +5995,11 @@ PyInit__decimal(void) CHECK_INT(PyModule_AddStringConstant(m, "__version__", "1.70")); CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", mpd_version())); + /* Add capsule API */ + Py_INCREF(capsule); + if (PyModule_AddObject(m, "_API", capsule) < 0) { + goto error; + } return m; diff --git a/Modules/_decimal/libmpdec_triple.h b/Modules/_decimal/libmpdec_triple.h new file mode 100644 index 00000000000000..f5b6444930718c --- /dev/null +++ b/Modules/_decimal/libmpdec_triple.h @@ -0,0 +1,455 @@ +/* + * 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. + */ + + +/* + * This file contains the mpd_triple API that will be part of mpdecimal + * in a future release. libmpdec is now strictly externally developed, + * so the libmpdec copy in Python should remain unchanged. + */ + + +#ifndef LIBMPDEC_TRIPLE_H_ +#define LIBMPDEC_TRIPLE_H_ + + +#include "mpdecimal.h" +#include "basearith.h" + + +#ifdef __cplus_plus +#include +#include +#include +extern "C" { +#else +#include +#include +#include +#endif + + +/* 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; + + +/******************************************************************************/ +/* Util */ +/******************************************************************************/ + +/* Internal function: Copy a decimal, share data with src: USE WITH CARE! */ +static inline void +_mpd_copy_shared(mpd_t *dest, const mpd_t *src) +{ + dest->flags = src->flags; + dest->exp = src->exp; + dest->digits = src->digits; + dest->len = src->len; + dest->alloc = src->alloc; + dest->data = src->data; + + mpd_set_shared_data(dest); +} + +#if !defined(CONFIG_64) || !defined(__SIZEOF_INT128__) +static inline mpd_ssize_t +_mpd_real_size(mpd_uint_t *data, mpd_ssize_t size) +{ + while (size > 1 && data[size-1] == 0) { + size--; + } + + return size; +} + +static inline size_t +_uint_from_u16(mpd_uint_t *w, mpd_ssize_t wlen, const uint16_t *u, size_t ulen) +{ + const mpd_uint_t ubase = 1U<<16; + mpd_ssize_t n = 0; + mpd_uint_t carry; + + assert(wlen > 0 && ulen > 0); + + w[n++] = u[--ulen]; + while (--ulen != SIZE_MAX) { + carry = _mpd_shortmul_c(w, w, n, ubase); + if (carry) { + if (n >= wlen) { + abort(); /* GCOV_NOT_REACHED */ + } + w[n++] = carry; + } + carry = _mpd_shortadd(w, n, u[ulen]); + if (carry) { + if (n >= wlen) { + abort(); /* GCOV_NOT_REACHED */ + } + w[n++] = carry; + } + } + + return n; +} + +static inline size_t +_uint_to_u16(uint16_t w[8], mpd_uint_t *u, mpd_ssize_t ulen) +{ + const mpd_uint_t wbase = 1U<<16; + size_t n = 0; + + assert(ulen > 0); + + do { + if (n >= 8) { + abort(); /* GCOV_NOT_REACHED */ + } + w[n++] = (uint16_t)_mpd_shortdiv(u, u, ulen, wbase); + /* ulen is at least 1. u[ulen-1] can only be zero if ulen == 1. */ + ulen = _mpd_real_size(u, ulen); + + } while (u[ulen-1] != 0); + + return n; +} +#endif + + +/******************************************************************************/ +/* From triple */ +/******************************************************************************/ + +#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) +static inline mpd_ssize_t +_set_coeff(uint64_t data[3], uint64_t hi, uint64_t lo) +{ + __uint128_t d = ((__uint128_t)hi << 64) + lo; + __uint128_t q, r; + + q = d / MPD_RADIX; + r = d % MPD_RADIX; + data[0] = (uint64_t)r; + d = q; + + q = d / MPD_RADIX; + r = d % MPD_RADIX; + data[1] = (uint64_t)r; + d = q; + + q = d / MPD_RADIX; + r = d % MPD_RADIX; + data[2] = (uint64_t)r; + + if (q != 0) { + abort(); /* GCOV_NOT_REACHED */ + } + + return data[2] != 0 ? 3 : (data[1] != 0 ? 2 : 1); +} +#else +static inline mpd_ssize_t +_set_coeff(mpd_uint_t *data, mpd_ssize_t len, uint64_t hi, uint64_t lo) +{ + uint16_t u16[8] = {0}; + + u16[7] = (uint16_t)((hi & 0xFFFF000000000000ULL) >> 48); + u16[6] = (uint16_t)((hi & 0x0000FFFF00000000ULL) >> 32); + u16[5] = (uint16_t)((hi & 0x00000000FFFF0000ULL) >> 16); + u16[4] = (uint16_t) (hi & 0x000000000000FFFFULL); + + u16[3] = (uint16_t)((lo & 0xFFFF000000000000ULL) >> 48); + u16[2] = (uint16_t)((lo & 0x0000FFFF00000000ULL) >> 32); + u16[1] = (uint16_t)((lo & 0x00000000FFFF0000ULL) >> 16); + u16[0] = (uint16_t) (lo & 0x000000000000FFFFULL); + + return (mpd_ssize_t)_uint_from_u16(data, len, u16, 8); +} +#endif + +static inline int +_set_uint128_coeff_exp(mpd_t *result, uint64_t hi, uint64_t lo, mpd_ssize_t exp) +{ + mpd_uint_t data[5] = {0}; + uint32_t status = 0; + mpd_ssize_t len; + +#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) + len = _set_coeff(data, hi, lo); +#else + len = _set_coeff(data, 5, hi, lo); +#endif + + if (!mpd_qresize(result, len, &status)) { + return -1; + } + + for (mpd_ssize_t i = 0; i < len; i++) { + result->data[i] = data[i]; + } + + result->exp = exp; + result->len = len; + mpd_setdigits(result); + + return 0; +} + +static inline int +mpd_from_uint128_triple(mpd_t *result, const mpd_uint128_triple_t *triple, uint32_t *status) +{ + const enum mpd_triple_class tag = triple->tag; + const uint8_t sign = triple->sign; + const uint64_t hi = triple->hi; + const uint64_t lo = triple->lo; + mpd_context_t maxcontext; + mpd_ssize_t exp; + +#ifdef CONFIG_32 + if (triple->exp < MPD_SSIZE_MIN || triple->exp > MPD_SSIZE_MAX) { + goto conversion_error; + } +#endif + exp = (mpd_ssize_t)triple->exp; + + switch (tag) { + case MPD_TRIPLE_QNAN: case MPD_TRIPLE_SNAN: { + if (sign > 1 || exp != 0) { + goto conversion_error; + } + + const uint8_t flags = tag == MPD_TRIPLE_QNAN ? MPD_NAN : MPD_SNAN; + mpd_setspecial(result, sign, flags); + + if (hi == 0 && lo == 0) { /* no payload */ + return 0; + } + + if (_set_uint128_coeff_exp(result, hi, lo, exp) < 0) { + goto malloc_error; + } + + return 0; + } + + case MPD_TRIPLE_INF: { + if (sign > 1 || hi != 0 || lo != 0 || exp != 0) { + goto conversion_error; + } + + mpd_setspecial(result, sign, MPD_INF); + + return 0; + } + + case MPD_TRIPLE_NORMAL: { + if (sign > 1) { + goto conversion_error; + } + + const uint8_t flags = sign ? MPD_NEG : MPD_POS; + mpd_set_flags(result, flags); + + if (exp > MPD_EXP_INF) { + exp = MPD_EXP_INF; + } + if (exp == MPD_SSIZE_MIN) { + exp = MPD_SSIZE_MIN+1; + } + + if (_set_uint128_coeff_exp(result, hi, lo, exp) < 0) { + goto malloc_error; + } + + mpd_maxcontext(&maxcontext); + uint32_t workstatus = 0; + mpd_qfinalize(result, &maxcontext, &workstatus); + if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) { + goto conversion_error; + } + + return 0; + } + + default: + goto conversion_error; + } + + return 0; + +conversion_error: + mpd_seterror(result, MPD_Conversion_syntax, status); + return -1; + +malloc_error: + mpd_seterror(result, MPD_Malloc_error, status); + return -1; +} + + +/******************************************************************************/ +/* As triple */ +/******************************************************************************/ + +#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) +static inline void +_get_coeff(uint64_t *hi, uint64_t *lo, const mpd_t *a) +{ + __uint128_t u128 = 0; + + switch (a->len) { + case 3: + u128 = a->data[2]; /* fall through */ + case 2: + u128 = u128 * MPD_RADIX + a->data[1]; /* fall through */ + case 1: + u128 = u128 * MPD_RADIX + a->data[0]; + break; + default: + abort(); /* GCOV_NOT_REACHED */ + } + + *hi = u128 >> 64; + *lo = (uint64_t)u128; +} +#else +static inline void +_get_coeff(uint64_t *hi, uint64_t *lo, const mpd_t *a) +{ + uint16_t u16[8] = {0}; + mpd_uint_t data[5] = {0}; + + switch (a->len) { + case 5: + data[4] = a->data[4]; /* fall through */ + case 4: + data[3] = a->data[3]; /* fall through */ + case 3: + data[2] = a->data[2]; /* fall through */ + case 2: + data[1] = a->data[1]; /* fall through */ + case 1: + data[0] = a->data[0]; + break; + default: + abort(); /* GCOV_NOT_REACHED */ + } + + _uint_to_u16(u16, data, a->len); + + *hi = (uint64_t)u16[7] << 48; + *hi |= (uint64_t)u16[6] << 32; + *hi |= (uint64_t)u16[5] << 16; + *hi |= (uint64_t)u16[4]; + + *lo = (uint64_t)u16[3] << 48; + *lo |= (uint64_t)u16[2] << 32; + *lo |= (uint64_t)u16[1] << 16; + *lo |= (uint64_t)u16[0]; +} +#endif + +static inline enum mpd_triple_class +_coeff_as_uint128(uint64_t *hi, uint64_t *lo, const mpd_t *a) +{ +#ifdef CONFIG_64 + static mpd_uint_t uint128_max_data[3] = { 3374607431768211455ULL, 4028236692093846346ULL, 3ULL }; + static const mpd_t uint128_max = { MPD_STATIC|MPD_CONST_DATA, 0, 39, 3, 3, uint128_max_data }; +#else + static mpd_uint_t uint128_max_data[5] = { 768211455U, 374607431U, 938463463U, 282366920U, 340U }; + static const mpd_t uint128_max = { MPD_STATIC|MPD_CONST_DATA, 0, 39, 5, 5, uint128_max_data }; +#endif + enum mpd_triple_class ret = MPD_TRIPLE_NORMAL; + uint32_t status = 0; + mpd_t coeff; + + *hi = *lo = 0ULL; + + if (mpd_isspecial(a)) { + if (mpd_isinfinite(a)) { + return MPD_TRIPLE_INF; + } + + ret = mpd_isqnan(a) ? MPD_TRIPLE_QNAN : MPD_TRIPLE_SNAN; + if (a->len == 0) { /* no payload */ + return ret; + } + } + else if (mpd_iszero(a)) { + return ret; + } + + _mpd_copy_shared(&coeff, a); + mpd_set_flags(&coeff, 0); + coeff.exp = 0; + + if (mpd_qcmp(&coeff, &uint128_max, &status) > 0) { + return MPD_TRIPLE_ERROR; + } + + _get_coeff(hi, lo, &coeff); + return ret; +} + +static inline mpd_uint128_triple_t +mpd_as_uint128_triple(const mpd_t *a) +{ + mpd_uint128_triple_t triple = { MPD_TRIPLE_ERROR, 0, 0, 0, 0 }; + + triple.tag = _coeff_as_uint128(&triple.hi, &triple.lo, a); + if (triple.tag == MPD_TRIPLE_ERROR) { + return triple; + } + + triple.sign = !!mpd_isnegative(a); + if (triple.tag == MPD_TRIPLE_NORMAL) { + triple.exp = a->exp; + } + + return triple; +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif /* LIBMPDEC_TRIPLE_H_ */ diff --git a/Modules/_decimal/tests/deccheck.py b/Modules/_decimal/tests/deccheck.py index 5d9179e61689d0..15f104dc463cb8 100644 --- a/Modules/_decimal/tests/deccheck.py +++ b/Modules/_decimal/tests/deccheck.py @@ -49,6 +49,9 @@ 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 + C = import_fresh_module('decimal', fresh=['_decimal']) P = import_fresh_module('decimal', blocked=['_decimal']) EXIT_STATUS = 0 @@ -153,6 +156,45 @@ 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 # ====================================================================== @@ -846,12 +888,44 @@ def verify(t, stat): t.presults.append(str(t.rp.imag)) t.presults.append(str(t.rp.real)) + ctriple = None + if t.funcname not in ['__radd__', '__rmul__']: # 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/_testcapimodule.c b/Modules/_testcapimodule.c index fca94a83a5d04e..ac541c247945ab 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -19,6 +19,7 @@ #include "Python.h" #include "datetime.h" +#include "decimal.h" #include "marshal.h" #include "structmember.h" // PyMemberDef #include @@ -2705,6 +2706,167 @@ 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_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; + } + + if (!PyDec_TypeCheck(dec)) { + PyErr_SetString(PyExc_TypeError, + "decimal_as_triple: argument must be a decimal"); + return NULL; + } + + triple = PyDec_AsUint128Triple(dec); + + 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 @@ -5299,6 +5461,8 @@ static PyMethodDef TestMethods[] = { {"datetime_check_datetime", datetime_check_datetime, METH_VARARGS}, {"datetime_check_delta", datetime_check_delta, METH_VARARGS}, {"datetime_check_tzinfo", datetime_check_tzinfo, METH_VARARGS}, + {"decimal_as_triple", decimal_as_triple, METH_O}, + {"decimal_from_triple", decimal_from_triple, METH_O}, {"make_timezones_capi", make_timezones_capi, METH_NOARGS}, {"get_timezones_offset_zero", get_timezones_offset_zero, METH_NOARGS}, {"get_timezone_utc_capi", get_timezone_utc_capi, METH_VARARGS}, diff --git a/setup.py b/setup.py index 21a5a58981fc15..542e9071cfe03a 100644 --- a/setup.py +++ b/setup.py @@ -2249,6 +2249,7 @@ def detect_decimal(self): ] depends = [ '_decimal/docstrings.h', + '_decimal/libmpdec_triple.h', '_decimal/libmpdec/basearith.h', '_decimal/libmpdec/bits.h', '_decimal/libmpdec/constants.h', From 72f95d80087185d358981056d149dfb6a9cbce36 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 13:16:45 +0200 Subject: [PATCH 02/17] Add PyDec_GetConst() and PyDec_GetDigits(). --- Include/decimal.h | 24 ++++++++++++++++++++---- Lib/test/test_decimal.py | 27 +++++++++++++++++++++++++++ Modules/_decimal/_decimal.c | 28 +++++++++++++++++++++++++++- Modules/_testcapimodule.c | 23 +++++++++++++++++++++++ 4 files changed, 97 insertions(+), 5 deletions(-) diff --git a/Include/decimal.h b/Include/decimal.h index 6e9a406be57902..d206fbd7a8268d 100644 --- a/Include/decimal.h +++ b/Include/decimal.h @@ -81,22 +81,32 @@ typedef struct { #define PyDec_Get_INDEX 2 #define PyDec_Get_RETURN mpd_t * -#define PyDec_Get_ARGS (const PyObject *) +#define PyDec_Get_ARGS (PyObject *) -#define PyDec_AsUint128Triple_INDEX 3 +#define PyDec_GetConst_INDEX 3 +#define PyDec_GetConst_RETURN const mpd_t * +#define PyDec_GetConst_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 4 +#define PyDec_FromUint128Triple_INDEX 6 #define PyDec_FromUint128Triple_RETURN PyObject * #define PyDec_FromUint128Triple_ARGS (const mpd_uint128_triple_t *triple) -#define CPYTHON_DECIMAL_MAX_API 5 +#define CPYTHON_DECIMAL_MAX_API 7 #ifdef CPYTHON_DECIMAL_MODULE static PyDec_TypeCheck_RETURN PyDec_TypeCheck PyDec_TypeCheck_ARGS; 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; +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; #else @@ -111,6 +121,12 @@ static void **_decimal_api; #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]) + +#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]) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index fb805940c682e0..37613e2a160112 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -42,6 +42,7 @@ import inspect import threading +from _testcapi import decimal_get_digits from _testcapi import decimal_as_triple from _testcapi import decimal_from_triple @@ -4845,6 +4846,30 @@ def from_triple(triple): p = decimal_from_triple(ptriple) self.assertEqual(str(c), str(p)) + def test_decimal_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_errors(self): # Capsule API @@ -4868,6 +4893,8 @@ def test_decimal_api_errors(self): 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_get_digits, "X") + class CWhitebox(unittest.TestCase): """Whitebox testing for _decimal""" diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 4ca531728ecded..a362b36618b9c2 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5577,7 +5577,7 @@ PyDec_Alloc(void) } static mpd_t * -PyDec_Get(const PyObject *v) +PyDec_Get(PyObject *v) { if (!PyDec_Check(v)) { PyErr_SetString(PyExc_TypeError, @@ -5588,6 +5588,30 @@ PyDec_Get(const PyObject *v) 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 int64_t +PyDec_GetDigits(const PyObject *v) +{ + if (!PyDec_Check(v)) { + PyErr_SetString(PyExc_TypeError, + "PyDec_GetConst: argument must be a Decimal"); + return -1; + } + + return MPD(v)->digits; +} + static mpd_uint128_triple_t PyDec_AsUint128Triple(const PyObject *v) { @@ -5629,6 +5653,8 @@ init_api(void) _decimal_api[PyDec_TypeCheck_INDEX] = (void *)PyDec_TypeCheck; _decimal_api[PyDec_Alloc_INDEX] = (void *)PyDec_Alloc; _decimal_api[PyDec_Get_INDEX] = (void *)PyDec_Get; + _decimal_api[PyDec_GetConst_INDEX] = (void *)PyDec_GetConst; + _decimal_api[PyDec_GetDigits_INDEX] = (void *)PyDec_GetDigits; _decimal_api[PyDec_AsUint128Triple_INDEX] = (void *)PyDec_AsUint128Triple; _decimal_api[PyDec_FromUint128Triple_INDEX] = (void *)PyDec_FromUint128Triple; diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index ac541c247945ab..c6835c2814b967 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2708,6 +2708,28 @@ test_PyDateTime_DELTA_GET(PyObject *self, PyObject *obj) /* Test decimal API */ static int decimal_initialized = 0; +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) { @@ -5461,6 +5483,7 @@ static PyMethodDef TestMethods[] = { {"datetime_check_datetime", datetime_check_datetime, METH_VARARGS}, {"datetime_check_delta", datetime_check_delta, METH_VARARGS}, {"datetime_check_tzinfo", datetime_check_tzinfo, METH_VARARGS}, + {"decimal_get_digits", decimal_get_digits, METH_O}, {"decimal_as_triple", decimal_as_triple, METH_O}, {"decimal_from_triple", decimal_from_triple, METH_O}, {"make_timezones_capi", make_timezones_capi, METH_NOARGS}, From 83366fb0e2a0f96c2fffdc87b8d555d41e2da7a4 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 13:32:33 +0200 Subject: [PATCH 03/17] Group the functions into simple and advanced API. --- Include/decimal.h | 62 ++++++++++++++++++-------------- Modules/_decimal/_decimal.c | 71 ++++++++++++++++++++----------------- 2 files changed, 73 insertions(+), 60 deletions(-) diff --git a/Include/decimal.h b/Include/decimal.h index d206fbd7a8268d..1ae92370d4298c 100644 --- a/Include/decimal.h +++ b/Include/decimal.h @@ -71,59 +71,57 @@ typedef struct { /* Capsule API */ /****************************************************************************/ +/* Simple API */ #define PyDec_TypeCheck_INDEX 0 #define PyDec_TypeCheck_RETURN int #define PyDec_TypeCheck_ARGS (const PyObject *) -#define PyDec_Alloc_INDEX 1 -#define PyDec_Alloc_RETURN PyObject * -#define PyDec_Alloc_ARGS (void) - -#define PyDec_Get_INDEX 2 -#define PyDec_Get_RETURN mpd_t * -#define PyDec_Get_ARGS (PyObject *) - -#define PyDec_GetConst_INDEX 3 -#define PyDec_GetConst_RETURN const mpd_t * -#define PyDec_GetConst_ARGS (const PyObject *) - -#define PyDec_GetDigits_INDEX 4 +#define PyDec_GetDigits_INDEX 1 #define PyDec_GetDigits_RETURN int64_t #define PyDec_GetDigits_ARGS (const PyObject *) -#define PyDec_AsUint128Triple_INDEX 5 +#define PyDec_AsUint128Triple_INDEX 2 #define PyDec_AsUint128Triple_RETURN mpd_uint128_triple_t #define PyDec_AsUint128Triple_ARGS (const PyObject *) -#define PyDec_FromUint128Triple_INDEX 6 +#define PyDec_FromUint128Triple_INDEX 3 #define PyDec_FromUint128Triple_RETURN PyObject * #define PyDec_FromUint128Triple_ARGS (const mpd_uint128_triple_t *triple) +/* Advanced API */ +#define PyDec_Alloc_INDEX 4 +#define PyDec_Alloc_RETURN PyObject * +#define PyDec_Alloc_ARGS (void) + +#define PyDec_Get_INDEX 5 +#define PyDec_Get_RETURN mpd_t * +#define PyDec_Get_ARGS (PyObject *) + +#define PyDec_GetConst_INDEX 6 +#define PyDec_GetConst_RETURN const mpd_t * +#define PyDec_GetConst_ARGS (const PyObject *) + #define CPYTHON_DECIMAL_MAX_API 7 + #ifdef CPYTHON_DECIMAL_MODULE +/* Simple API */ static PyDec_TypeCheck_RETURN PyDec_TypeCheck PyDec_TypeCheck_ARGS; -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; 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_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]) - #define PyDec_GetDigits \ (*(PyDec_GetDigits_RETURN (*)PyDec_GetDigits_ARGS) _decimal_api[PyDec_GetDigits_INDEX]) @@ -133,6 +131,16 @@ static void **_decimal_api; #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) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index a362b36618b9c2..16814d6b3e4735 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5564,42 +5564,13 @@ static PyTypeObject PyDecContext_Type = static void **_decimal_api[CPYTHON_DECIMAL_MAX_API]; +/* Simple API */ static int PyDec_TypeCheck(const PyObject *v) { return PyDec_Check(v); } -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 int64_t PyDec_GetDigits(const PyObject *v) { @@ -5647,17 +5618,51 @@ PyDec_FromUint128Triple(const mpd_uint128_triple_t *triple) 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 PyObject * init_api(void) { + /* Simple API */ _decimal_api[PyDec_TypeCheck_INDEX] = (void *)PyDec_TypeCheck; - _decimal_api[PyDec_Alloc_INDEX] = (void *)PyDec_Alloc; - _decimal_api[PyDec_Get_INDEX] = (void *)PyDec_Get; - _decimal_api[PyDec_GetConst_INDEX] = (void *)PyDec_GetConst; _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; + return PyCapsule_New(_decimal_api, "_decimal._API", NULL); } From 3636589126edcbe02d73684f5874c5ba15f8e5d8 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 14:48:35 +0200 Subject: [PATCH 04/17] Add PyDec_IsSpecial(), PyDec_IsNaN() and PyDec_IsInfinite(). --- Include/decimal.h | 38 +++++++++++--- Lib/test/test_decimal.py | 101 +++++++++++++++++++++++++++--------- Modules/_decimal/_decimal.c | 39 ++++++++++++++ Modules/_testcapimodule.c | 69 ++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 31 deletions(-) diff --git a/Include/decimal.h b/Include/decimal.h index 1ae92370d4298c..678c2e960ed9f2 100644 --- a/Include/decimal.h +++ b/Include/decimal.h @@ -76,37 +76,52 @@ typedef struct { #define PyDec_TypeCheck_RETURN int #define PyDec_TypeCheck_ARGS (const PyObject *) -#define PyDec_GetDigits_INDEX 1 +#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 2 +#define PyDec_AsUint128Triple_INDEX 5 #define PyDec_AsUint128Triple_RETURN mpd_uint128_triple_t #define PyDec_AsUint128Triple_ARGS (const PyObject *) -#define PyDec_FromUint128Triple_INDEX 3 +#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 4 +#define PyDec_Alloc_INDEX 7 #define PyDec_Alloc_RETURN PyObject * #define PyDec_Alloc_ARGS (void) -#define PyDec_Get_INDEX 5 +#define PyDec_Get_INDEX 8 #define PyDec_Get_RETURN mpd_t * #define PyDec_Get_ARGS (PyObject *) -#define PyDec_GetConst_INDEX 6 +#define PyDec_GetConst_INDEX 9 #define PyDec_GetConst_RETURN const mpd_t * #define PyDec_GetConst_ARGS (const PyObject *) -#define CPYTHON_DECIMAL_MAX_API 7 +#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; @@ -122,6 +137,15 @@ static void **_decimal_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]) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 37613e2a160112..6840644b116fde 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -42,6 +42,9 @@ 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 @@ -4752,6 +4755,77 @@ def test_constants(self): self.assertEqual(C.DecTraps, C.DecErrors|C.DecOverflow|C.DecUnderflow) + 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_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_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_triple(self): # Capsule API @@ -4846,30 +4920,6 @@ def from_triple(triple): p = decimal_from_triple(ptriple) self.assertEqual(str(c), str(p)) - def test_decimal_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_errors(self): # Capsule API @@ -4893,6 +4943,9 @@ def test_decimal_api_errors(self): 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): diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 16814d6b3e4735..d9eac704fb5f7f 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5571,6 +5571,42 @@ 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) { @@ -5654,6 +5690,9 @@ init_api(void) { /* 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; diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index c6835c2814b967..55c4d904f0bd52 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2708,6 +2708,72 @@ test_PyDateTime_DELTA_GET(PyObject *self, PyObject *obj) /* 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) { @@ -5483,6 +5549,9 @@ static PyMethodDef TestMethods[] = { {"datetime_check_datetime", datetime_check_datetime, METH_VARARGS}, {"datetime_check_delta", datetime_check_delta, METH_VARARGS}, {"datetime_check_tzinfo", datetime_check_tzinfo, METH_VARARGS}, + {"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}, From 3208099393fcfe4ddf404934bbe4b4d06344aaf6 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 15:00:46 +0200 Subject: [PATCH 05/17] Mention ABI stability of mpd_t. --- Include/decimal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/decimal.h b/Include/decimal.h index 678c2e960ed9f2..bdae0db76da32e 100644 --- a/Include/decimal.h +++ b/Include/decimal.h @@ -46,7 +46,7 @@ extern "C" { */ #ifndef LIBMPDEC_TRIPLE_H_ -struct mpd_t; +struct mpd_t; /* ABI-stable in the libmpdec-2.x series */ /* status cases for getting a triple */ enum mpd_triple_class { From fa6e201984c0692c87654dc630b35455d3b5f49b Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 15:14:03 +0200 Subject: [PATCH 06/17] Remove test_decimal_api_get_digits() duplicate. --- Lib/test/test_decimal.py | 52 +++++++++++----------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 6840644b116fde..c2eeb9e0f4f23a 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4755,29 +4755,28 @@ def test_constants(self): self.assertEqual(C.DecTraps, C.DecErrors|C.DecOverflow|C.DecUnderflow) - def test_decimal_api_get_digits(self): + def test_decimal_api_predicates(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) + self.assertFalse(decimal_is_special(d)) + self.assertFalse(decimal_is_nan(d)) + self.assertFalse(decimal_is_infinite(d)) d = C.Decimal("NaN") - self.assertEqual(decimal_get_digits(d), 0) + self.assertTrue(decimal_is_special(d)) + self.assertTrue(decimal_is_nan(d)) + self.assertFalse(decimal_is_infinite(d)) d = C.Decimal("sNaN") - self.assertEqual(decimal_get_digits(d), 0) - - d = C.Decimal("NaN1234567890") - self.assertEqual(decimal_get_digits(d), 10) + self.assertTrue(decimal_is_special(d)) + self.assertTrue(decimal_is_nan(d)) + self.assertFalse(decimal_is_infinite(d)) - d = C.Decimal("sNaN1234567890") - self.assertEqual(decimal_get_digits(d), 10) + 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 @@ -4803,29 +4802,6 @@ def test_decimal_api_get_digits(self): d = C.Decimal("sNaN1234567890") self.assertEqual(decimal_get_digits(d), 10) - 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_triple(self): # Capsule API From 3a0db41faa0c59ef4c1b9c28775928cd06197aa6 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 15:58:01 +0200 Subject: [PATCH 07/17] Move the TypeError into PyDec_AsUint128Triple() for a consistent API. --- Modules/_decimal/_decimal.c | 2 ++ Modules/_testcapimodule.c | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index d9eac704fb5f7f..203bd002aca785 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5624,6 +5624,8 @@ 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; } diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 55c4d904f0bd52..fa585f03e4073a 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2812,14 +2812,11 @@ decimal_as_triple(PyObject *module, PyObject *dec) decimal_initialized = 1; } - if (!PyDec_TypeCheck(dec)) { - PyErr_SetString(PyExc_TypeError, - "decimal_as_triple: argument must be a decimal"); + triple = PyDec_AsUint128Triple(dec); + if (triple.tag == MPD_TRIPLE_ERROR && PyErr_Occurred()) { return NULL; } - triple = PyDec_AsUint128Triple(dec); - sign = PyLong_FromUnsignedLong(triple.sign); if (sign == NULL) { return NULL; From 2fcc6ff9f80b386c8d6f8f8c324be25135d9ecd5 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 16:09:06 +0200 Subject: [PATCH 08/17] Fix error message. --- Modules/_decimal/_decimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 203bd002aca785..80bf2ee1c2ef8b 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5612,7 +5612,7 @@ PyDec_GetDigits(const PyObject *v) { if (!PyDec_Check(v)) { PyErr_SetString(PyExc_TypeError, - "PyDec_GetConst: argument must be a Decimal"); + "PyDec_GetDigits: argument must be a Decimal"); return -1; } From 9ac33c35b89aa8f6d73f77f78d86c1f70cf6f4ca Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 19:14:33 +0200 Subject: [PATCH 09/17] Add documentation. --- Doc/c-api/concrete.rst | 1 + Doc/c-api/decimal.rst | 231 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 Doc/c-api/decimal.rst diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst index c1d9fa1b41a3fe..bf263d6e4c2641 100644 --- a/Doc/c-api/concrete.rst +++ b/Doc/c-api/concrete.rst @@ -115,3 +115,4 @@ Other Objects coro.rst contextvars.rst datetime.rst + decimal.rst diff --git a/Doc/c-api/decimal.rst b/Doc/c-api/decimal.rst new file mode 100644 index 00000000000000..de6ac7cd058095 --- /dev/null +++ b/Doc/c-api/decimal.rst @@ -0,0 +1,231 @@ +.. 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:: + + #include + + 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:: + + /* 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:: + + 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 and 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. From d8e23dac0db5554b0d33772520c15855bdfbb846 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Sat, 18 Jul 2020 20:56:54 +0200 Subject: [PATCH 10/17] Appease the Sphinx linter. --- Doc/c-api/decimal.rst | 74 +++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Doc/c-api/decimal.rst b/Doc/c-api/decimal.rst index de6ac7cd058095..69071c440b0c62 100644 --- a/Doc/c-api/decimal.rst +++ b/Doc/c-api/decimal.rst @@ -35,46 +35,46 @@ 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 + 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. + 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 + 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. + 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 + 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. + 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 + 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. + 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 + 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. @@ -107,11 +107,11 @@ The conversion functions use the following status codes and data structures: 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 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. +is an established term and (``hi``, ``lo``) represents a single ``uint128_t`` coefficient. Functions @@ -180,35 +180,35 @@ Functions 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). + 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. + 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. + 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. + 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. + 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. + 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. + 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`. + 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 and external libmpdec and the `mpdecimal.h` +This API enables the use of ``libmpdec`` functions. Since Python is compiled with +hidden symbols, the API requires and external libmpdec and the ``mpdecimal.h`` header. @@ -217,15 +217,15 @@ Functions .. c:function:: PyObject *PyDec_Alloc(void) - Return a new decimal that can be used in the `result` position of `libmpdec` + 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, + 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. + Get a pointer to the constant internal ``mpd_t`` of the decimal. From 948a62e307143c618f39da23308e9faf45993d37 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Mon, 20 Jul 2020 13:19:57 +0200 Subject: [PATCH 11/17] Rename decimal.h to pydecimal.h. --- Doc/c-api/decimal.rst | 2 +- Include/{decimal.h => pydecimal.h} | 0 Modules/_decimal/_decimal.c | 2 +- Modules/_testcapimodule.c | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename Include/{decimal.h => pydecimal.h} (100%) diff --git a/Doc/c-api/decimal.rst b/Doc/c-api/decimal.rst index 69071c440b0c62..1b5f5d49014380 100644 --- a/Doc/c-api/decimal.rst +++ b/Doc/c-api/decimal.rst @@ -18,7 +18,7 @@ steps in its init function: .. code-block:: - #include + #include "pydecimal.h" static int decimal_initialized = 0; if (!decimal_initialized) { diff --git a/Include/decimal.h b/Include/pydecimal.h similarity index 100% rename from Include/decimal.h rename to Include/pydecimal.h diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 80bf2ee1c2ef8b..187f9557e66e3c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -35,7 +35,7 @@ #define CPYTHON_DECIMAL_MODULE #include "libmpdec_triple.h" -#include "decimal.h" +#include "pydecimal.h" #include "docstrings.h" diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index fa585f03e4073a..1ce2d6633fbdc6 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -19,7 +19,7 @@ #include "Python.h" #include "datetime.h" -#include "decimal.h" +#include "pydecimal.h" #include "marshal.h" #include "structmember.h" // PyMemberDef #include From cff9084ab9d27f1864f7b15c74685fcca8654998 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Mon, 20 Jul 2020 13:41:55 +0200 Subject: [PATCH 12/17] Move decimal test functions out of the datetime section. --- Modules/_testcapimodule.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 1ce2d6633fbdc6..593034ef65e2ca 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5546,12 +5546,6 @@ static PyMethodDef TestMethods[] = { {"datetime_check_datetime", datetime_check_datetime, METH_VARARGS}, {"datetime_check_delta", datetime_check_delta, METH_VARARGS}, {"datetime_check_tzinfo", datetime_check_tzinfo, METH_VARARGS}, - {"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}, {"make_timezones_capi", make_timezones_capi, METH_NOARGS}, {"get_timezones_offset_zero", get_timezones_offset_zero, METH_NOARGS}, {"get_timezone_utc_capi", get_timezone_utc_capi, METH_VARARGS}, @@ -5567,6 +5561,12 @@ 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}, From fff88268f2f3420cdc6acc3721a9a5b8485a4588 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Mon, 20 Jul 2020 14:05:21 +0200 Subject: [PATCH 13/17] Fix typo. --- Doc/c-api/decimal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/decimal.rst b/Doc/c-api/decimal.rst index 1b5f5d49014380..f530571ebae577 100644 --- a/Doc/c-api/decimal.rst +++ b/Doc/c-api/decimal.rst @@ -208,7 +208,7 @@ Advanced API ------------ This API enables the use of ``libmpdec`` functions. Since Python is compiled with -hidden symbols, the API requires and external libmpdec and the ``mpdecimal.h`` +hidden symbols, the API requires an external libmpdec and the ``mpdecimal.h`` header. From 79c46d9dd3855a77d0760d088b2a292ebf28fcca Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Mon, 20 Jul 2020 15:38:20 +0200 Subject: [PATCH 14/17] Make maxcontext a static const. --- Modules/_decimal/libmpdec_triple.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/libmpdec_triple.h b/Modules/_decimal/libmpdec_triple.h index f5b6444930718c..a4bd128de135c8 100644 --- a/Modules/_decimal/libmpdec_triple.h +++ b/Modules/_decimal/libmpdec_triple.h @@ -235,11 +235,21 @@ _set_uint128_coeff_exp(mpd_t *result, uint64_t hi, uint64_t lo, mpd_ssize_t exp) static inline int mpd_from_uint128_triple(mpd_t *result, const mpd_uint128_triple_t *triple, uint32_t *status) { + static const mpd_context_t maxcontext = { + .prec=MPD_MAX_PREC, + .emax=MPD_MAX_EMAX, + .emin=MPD_MIN_EMIN, + .round=MPD_ROUND_HALF_EVEN, + .traps=MPD_Traps, + .status=0, + .newtrap=0, + .clamp=0, + .allcr=1, + }; const enum mpd_triple_class tag = triple->tag; const uint8_t sign = triple->sign; const uint64_t hi = triple->hi; const uint64_t lo = triple->lo; - mpd_context_t maxcontext; mpd_ssize_t exp; #ifdef CONFIG_32 @@ -298,7 +308,6 @@ mpd_from_uint128_triple(mpd_t *result, const mpd_uint128_triple_t *triple, uint3 goto malloc_error; } - mpd_maxcontext(&maxcontext); uint32_t workstatus = 0; mpd_qfinalize(result, &maxcontext, &workstatus); if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) { From 81992c5e8720ed2190c5a14f3435acdbc93d3d21 Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Wed, 22 Jul 2020 22:30:32 +0200 Subject: [PATCH 15/17] Remove libmpdec_triple.h. --- Include/pydecimal.h | 7 +- Modules/_decimal/_decimal.c | 1 - Modules/_decimal/libmpdec_triple.h | 464 ----------------------------- setup.py | 1 - 4 files changed, 1 insertion(+), 472 deletions(-) delete mode 100644 Modules/_decimal/libmpdec_triple.h diff --git a/Include/pydecimal.h b/Include/pydecimal.h index bdae0db76da32e..9b6440e1c2ab1c 100644 --- a/Include/pydecimal.h +++ b/Include/pydecimal.h @@ -40,12 +40,7 @@ extern "C" { /* Libmpdec API */ /****************************************************************************/ -/* - * Copied from libmpdec_triple.h. This will be in mpdecimal.h once the - * functions are integrated into libmpdec. - */ - -#ifndef LIBMPDEC_TRIPLE_H_ +#ifndef LIBMPDEC_MPDECIMAL_H_ struct mpd_t; /* ABI-stable in the libmpdec-2.x series */ /* status cases for getting a triple */ diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 187f9557e66e3c..cf72bec7add74c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -34,7 +34,6 @@ #include #define CPYTHON_DECIMAL_MODULE -#include "libmpdec_triple.h" #include "pydecimal.h" #include "docstrings.h" diff --git a/Modules/_decimal/libmpdec_triple.h b/Modules/_decimal/libmpdec_triple.h deleted file mode 100644 index a4bd128de135c8..00000000000000 --- a/Modules/_decimal/libmpdec_triple.h +++ /dev/null @@ -1,464 +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. - */ - - -/* - * This file contains the mpd_triple API that will be part of mpdecimal - * in a future release. libmpdec is now strictly externally developed, - * so the libmpdec copy in Python should remain unchanged. - */ - - -#ifndef LIBMPDEC_TRIPLE_H_ -#define LIBMPDEC_TRIPLE_H_ - - -#include "mpdecimal.h" -#include "basearith.h" - - -#ifdef __cplus_plus -#include -#include -#include -extern "C" { -#else -#include -#include -#include -#endif - - -/* 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; - - -/******************************************************************************/ -/* Util */ -/******************************************************************************/ - -/* Internal function: Copy a decimal, share data with src: USE WITH CARE! */ -static inline void -_mpd_copy_shared(mpd_t *dest, const mpd_t *src) -{ - dest->flags = src->flags; - dest->exp = src->exp; - dest->digits = src->digits; - dest->len = src->len; - dest->alloc = src->alloc; - dest->data = src->data; - - mpd_set_shared_data(dest); -} - -#if !defined(CONFIG_64) || !defined(__SIZEOF_INT128__) -static inline mpd_ssize_t -_mpd_real_size(mpd_uint_t *data, mpd_ssize_t size) -{ - while (size > 1 && data[size-1] == 0) { - size--; - } - - return size; -} - -static inline size_t -_uint_from_u16(mpd_uint_t *w, mpd_ssize_t wlen, const uint16_t *u, size_t ulen) -{ - const mpd_uint_t ubase = 1U<<16; - mpd_ssize_t n = 0; - mpd_uint_t carry; - - assert(wlen > 0 && ulen > 0); - - w[n++] = u[--ulen]; - while (--ulen != SIZE_MAX) { - carry = _mpd_shortmul_c(w, w, n, ubase); - if (carry) { - if (n >= wlen) { - abort(); /* GCOV_NOT_REACHED */ - } - w[n++] = carry; - } - carry = _mpd_shortadd(w, n, u[ulen]); - if (carry) { - if (n >= wlen) { - abort(); /* GCOV_NOT_REACHED */ - } - w[n++] = carry; - } - } - - return n; -} - -static inline size_t -_uint_to_u16(uint16_t w[8], mpd_uint_t *u, mpd_ssize_t ulen) -{ - const mpd_uint_t wbase = 1U<<16; - size_t n = 0; - - assert(ulen > 0); - - do { - if (n >= 8) { - abort(); /* GCOV_NOT_REACHED */ - } - w[n++] = (uint16_t)_mpd_shortdiv(u, u, ulen, wbase); - /* ulen is at least 1. u[ulen-1] can only be zero if ulen == 1. */ - ulen = _mpd_real_size(u, ulen); - - } while (u[ulen-1] != 0); - - return n; -} -#endif - - -/******************************************************************************/ -/* From triple */ -/******************************************************************************/ - -#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) -static inline mpd_ssize_t -_set_coeff(uint64_t data[3], uint64_t hi, uint64_t lo) -{ - __uint128_t d = ((__uint128_t)hi << 64) + lo; - __uint128_t q, r; - - q = d / MPD_RADIX; - r = d % MPD_RADIX; - data[0] = (uint64_t)r; - d = q; - - q = d / MPD_RADIX; - r = d % MPD_RADIX; - data[1] = (uint64_t)r; - d = q; - - q = d / MPD_RADIX; - r = d % MPD_RADIX; - data[2] = (uint64_t)r; - - if (q != 0) { - abort(); /* GCOV_NOT_REACHED */ - } - - return data[2] != 0 ? 3 : (data[1] != 0 ? 2 : 1); -} -#else -static inline mpd_ssize_t -_set_coeff(mpd_uint_t *data, mpd_ssize_t len, uint64_t hi, uint64_t lo) -{ - uint16_t u16[8] = {0}; - - u16[7] = (uint16_t)((hi & 0xFFFF000000000000ULL) >> 48); - u16[6] = (uint16_t)((hi & 0x0000FFFF00000000ULL) >> 32); - u16[5] = (uint16_t)((hi & 0x00000000FFFF0000ULL) >> 16); - u16[4] = (uint16_t) (hi & 0x000000000000FFFFULL); - - u16[3] = (uint16_t)((lo & 0xFFFF000000000000ULL) >> 48); - u16[2] = (uint16_t)((lo & 0x0000FFFF00000000ULL) >> 32); - u16[1] = (uint16_t)((lo & 0x00000000FFFF0000ULL) >> 16); - u16[0] = (uint16_t) (lo & 0x000000000000FFFFULL); - - return (mpd_ssize_t)_uint_from_u16(data, len, u16, 8); -} -#endif - -static inline int -_set_uint128_coeff_exp(mpd_t *result, uint64_t hi, uint64_t lo, mpd_ssize_t exp) -{ - mpd_uint_t data[5] = {0}; - uint32_t status = 0; - mpd_ssize_t len; - -#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) - len = _set_coeff(data, hi, lo); -#else - len = _set_coeff(data, 5, hi, lo); -#endif - - if (!mpd_qresize(result, len, &status)) { - return -1; - } - - for (mpd_ssize_t i = 0; i < len; i++) { - result->data[i] = data[i]; - } - - result->exp = exp; - result->len = len; - mpd_setdigits(result); - - return 0; -} - -static inline int -mpd_from_uint128_triple(mpd_t *result, const mpd_uint128_triple_t *triple, uint32_t *status) -{ - static const mpd_context_t maxcontext = { - .prec=MPD_MAX_PREC, - .emax=MPD_MAX_EMAX, - .emin=MPD_MIN_EMIN, - .round=MPD_ROUND_HALF_EVEN, - .traps=MPD_Traps, - .status=0, - .newtrap=0, - .clamp=0, - .allcr=1, - }; - const enum mpd_triple_class tag = triple->tag; - const uint8_t sign = triple->sign; - const uint64_t hi = triple->hi; - const uint64_t lo = triple->lo; - mpd_ssize_t exp; - -#ifdef CONFIG_32 - if (triple->exp < MPD_SSIZE_MIN || triple->exp > MPD_SSIZE_MAX) { - goto conversion_error; - } -#endif - exp = (mpd_ssize_t)triple->exp; - - switch (tag) { - case MPD_TRIPLE_QNAN: case MPD_TRIPLE_SNAN: { - if (sign > 1 || exp != 0) { - goto conversion_error; - } - - const uint8_t flags = tag == MPD_TRIPLE_QNAN ? MPD_NAN : MPD_SNAN; - mpd_setspecial(result, sign, flags); - - if (hi == 0 && lo == 0) { /* no payload */ - return 0; - } - - if (_set_uint128_coeff_exp(result, hi, lo, exp) < 0) { - goto malloc_error; - } - - return 0; - } - - case MPD_TRIPLE_INF: { - if (sign > 1 || hi != 0 || lo != 0 || exp != 0) { - goto conversion_error; - } - - mpd_setspecial(result, sign, MPD_INF); - - return 0; - } - - case MPD_TRIPLE_NORMAL: { - if (sign > 1) { - goto conversion_error; - } - - const uint8_t flags = sign ? MPD_NEG : MPD_POS; - mpd_set_flags(result, flags); - - if (exp > MPD_EXP_INF) { - exp = MPD_EXP_INF; - } - if (exp == MPD_SSIZE_MIN) { - exp = MPD_SSIZE_MIN+1; - } - - if (_set_uint128_coeff_exp(result, hi, lo, exp) < 0) { - goto malloc_error; - } - - uint32_t workstatus = 0; - mpd_qfinalize(result, &maxcontext, &workstatus); - if (workstatus & (MPD_Inexact|MPD_Rounded|MPD_Clamped)) { - goto conversion_error; - } - - return 0; - } - - default: - goto conversion_error; - } - - return 0; - -conversion_error: - mpd_seterror(result, MPD_Conversion_syntax, status); - return -1; - -malloc_error: - mpd_seterror(result, MPD_Malloc_error, status); - return -1; -} - - -/******************************************************************************/ -/* As triple */ -/******************************************************************************/ - -#if defined(CONFIG_64) && defined(__SIZEOF_INT128__) -static inline void -_get_coeff(uint64_t *hi, uint64_t *lo, const mpd_t *a) -{ - __uint128_t u128 = 0; - - switch (a->len) { - case 3: - u128 = a->data[2]; /* fall through */ - case 2: - u128 = u128 * MPD_RADIX + a->data[1]; /* fall through */ - case 1: - u128 = u128 * MPD_RADIX + a->data[0]; - break; - default: - abort(); /* GCOV_NOT_REACHED */ - } - - *hi = u128 >> 64; - *lo = (uint64_t)u128; -} -#else -static inline void -_get_coeff(uint64_t *hi, uint64_t *lo, const mpd_t *a) -{ - uint16_t u16[8] = {0}; - mpd_uint_t data[5] = {0}; - - switch (a->len) { - case 5: - data[4] = a->data[4]; /* fall through */ - case 4: - data[3] = a->data[3]; /* fall through */ - case 3: - data[2] = a->data[2]; /* fall through */ - case 2: - data[1] = a->data[1]; /* fall through */ - case 1: - data[0] = a->data[0]; - break; - default: - abort(); /* GCOV_NOT_REACHED */ - } - - _uint_to_u16(u16, data, a->len); - - *hi = (uint64_t)u16[7] << 48; - *hi |= (uint64_t)u16[6] << 32; - *hi |= (uint64_t)u16[5] << 16; - *hi |= (uint64_t)u16[4]; - - *lo = (uint64_t)u16[3] << 48; - *lo |= (uint64_t)u16[2] << 32; - *lo |= (uint64_t)u16[1] << 16; - *lo |= (uint64_t)u16[0]; -} -#endif - -static inline enum mpd_triple_class -_coeff_as_uint128(uint64_t *hi, uint64_t *lo, const mpd_t *a) -{ -#ifdef CONFIG_64 - static mpd_uint_t uint128_max_data[3] = { 3374607431768211455ULL, 4028236692093846346ULL, 3ULL }; - static const mpd_t uint128_max = { MPD_STATIC|MPD_CONST_DATA, 0, 39, 3, 3, uint128_max_data }; -#else - static mpd_uint_t uint128_max_data[5] = { 768211455U, 374607431U, 938463463U, 282366920U, 340U }; - static const mpd_t uint128_max = { MPD_STATIC|MPD_CONST_DATA, 0, 39, 5, 5, uint128_max_data }; -#endif - enum mpd_triple_class ret = MPD_TRIPLE_NORMAL; - uint32_t status = 0; - mpd_t coeff; - - *hi = *lo = 0ULL; - - if (mpd_isspecial(a)) { - if (mpd_isinfinite(a)) { - return MPD_TRIPLE_INF; - } - - ret = mpd_isqnan(a) ? MPD_TRIPLE_QNAN : MPD_TRIPLE_SNAN; - if (a->len == 0) { /* no payload */ - return ret; - } - } - else if (mpd_iszero(a)) { - return ret; - } - - _mpd_copy_shared(&coeff, a); - mpd_set_flags(&coeff, 0); - coeff.exp = 0; - - if (mpd_qcmp(&coeff, &uint128_max, &status) > 0) { - return MPD_TRIPLE_ERROR; - } - - _get_coeff(hi, lo, &coeff); - return ret; -} - -static inline mpd_uint128_triple_t -mpd_as_uint128_triple(const mpd_t *a) -{ - mpd_uint128_triple_t triple = { MPD_TRIPLE_ERROR, 0, 0, 0, 0 }; - - triple.tag = _coeff_as_uint128(&triple.hi, &triple.lo, a); - if (triple.tag == MPD_TRIPLE_ERROR) { - return triple; - } - - triple.sign = !!mpd_isnegative(a); - if (triple.tag == MPD_TRIPLE_NORMAL) { - triple.exp = a->exp; - } - - return triple; -} - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - - -#endif /* LIBMPDEC_TRIPLE_H_ */ diff --git a/setup.py b/setup.py index 542e9071cfe03a..21a5a58981fc15 100644 --- a/setup.py +++ b/setup.py @@ -2249,7 +2249,6 @@ def detect_decimal(self): ] depends = [ '_decimal/docstrings.h', - '_decimal/libmpdec_triple.h', '_decimal/libmpdec/basearith.h', '_decimal/libmpdec/bits.h', '_decimal/libmpdec/constants.h', From 3bf0e2d17765cb93e219729294c005afd9a8654b Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Mon, 10 Aug 2020 15:27:15 +0200 Subject: [PATCH 16/17] Fix array type (not that it matters with all that capsule casting). --- Modules/_decimal/_decimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index cf72bec7add74c..e7c44acba02fc2 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5561,7 +5561,7 @@ static PyTypeObject PyDecContext_Type = /* C-API */ /****************************************************************************/ -static void **_decimal_api[CPYTHON_DECIMAL_MAX_API]; +static void *_decimal_api[CPYTHON_DECIMAL_MAX_API]; /* Simple API */ static int From ec73b29872f75368b96f8e295c119fde9890f6fa Mon Sep 17 00:00:00 2001 From: Stefan Krah Date: Mon, 10 Aug 2020 16:06:04 +0200 Subject: [PATCH 17/17] Add blurb. --- .../NEWS.d/next/C API/2020-08-10-16-05-08.bpo-41324.waZD35.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2020-08-10-16-05-08.bpo-41324.waZD35.rst diff --git a/Misc/NEWS.d/next/C API/2020-08-10-16-05-08.bpo-41324.waZD35.rst b/Misc/NEWS.d/next/C API/2020-08-10-16-05-08.bpo-41324.waZD35.rst new file mode 100644 index 00000000000000..e09332ab11e1d7 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2020-08-10-16-05-08.bpo-41324.waZD35.rst @@ -0,0 +1,3 @@ +Add a minimal decimal capsule API. The API supports fast conversions +between Decimals up to 38 digits and their triple representation as a C +struct.