From 7ba3ca4741708764e9eb5f3f1756da5b102c6b50 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 3 Mar 2022 12:06:49 +0100 Subject: [PATCH 1/4] Add PyFloat_Pack8() --- docs/api.rst | 36 ++++++++++++++++++ docs/changelog.rst | 3 ++ pythoncapi_compat.h | 31 +++++++++++++++ tests/test_pythoncapi_compat_cext.c | 59 +++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index fbba6b6..97858ed 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -39,6 +39,42 @@ Python 3.11 Not available on PyPy +.. c:function:: int PyFloat_Pack2(double x, unsigned char *p, int le) + + Pack a C double as 16-bit. + + Availability: Python 3.6 and newer. Not available on PyPy + +.. c:function:: int PyFloat_Pack4(double x, unsigned char *p, int le) + + Pack a C double as 32-bit. + + Not available on PyPy + +.. c:function:: int PyFloat_Pack8(double x, unsigned char *p, int le) + + Pack a C double as 64-bit. + + Not available on PyPy + +.. c:function:: double PyFloat_Unpack2(const unsigned char *p, int le) + + Unpack 16-bit as a C double. + + Availability: Python 3.6 and newer. Not available on PyPy + +.. c:function:: double PyFloat_Unpack4(const unsigned char *p, int le) + + Unpack 32-bit as a C double. + + Not available on PyPy + +.. c:function:: double PyFloat_Unpack8(const unsigned char *p, int le) + + Unpack 64-bit as a C double. + + Not available on PyPy + Python 3.10 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index eed780e..9253e76 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,9 @@ Changelog ========= +* 2022-03-12: Add functions ``PyFloat_Pack2()``, ``PyFloat_Pack4()``, + ``PyFloat_Pack8()``, ``PyFloat_Unpack2()``, ``PyFloat_Unpack4()`` and + ``PyFloat_Unpack8()``. * 2022-03-03: The project moved to https://github.com/python/pythoncapi_compat * 2022-02-11: The project license changes from the MIT license to the Zero Clause BSD (0BSD) license. Projects copying ``pythoncapi_compat.h`` no longer diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index d035456..ef76cdd 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -391,6 +391,37 @@ _Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { #endif +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1 +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +PYCAPI_COMPAT_STATIC_INLINE(int) +PyFloat_Pack2(double x, char *p, int le) +{ return _PyFloat_Pack2(x, (unsigned char*)p, le); } + +PYCAPI_COMPAT_STATIC_INLINE(double) +PyFloat_Unpack2(const char *p, int le) +{ return _PyFloat_Unpack2((const unsigned char *)p, le); } +#endif + + +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +PYCAPI_COMPAT_STATIC_INLINE(int) +PyFloat_Pack4(double x, char *p, int le) +{ return _PyFloat_Pack4(x, (unsigned char*)p, le); } + +PYCAPI_COMPAT_STATIC_INLINE(int) +PyFloat_Pack8(double x, char *p, int le) +{ return _PyFloat_Pack8(x, (unsigned char*)p, le); } + +PYCAPI_COMPAT_STATIC_INLINE(double) +PyFloat_Unpack4(const char *p, int le) +{ return _PyFloat_Unpack4((const unsigned char *)p, le); } + +PYCAPI_COMPAT_STATIC_INLINE(double) +PyFloat_Unpack8(const char *p, int le) +{ return _PyFloat_Unpack8((const unsigned char *)p, le); } +#endif + + // Py_UNUSED() was added to Python 3.4.0b2. #if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED) # if defined(__GNUC__) || defined(__clang__) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index d6cf877..4b9c922 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -406,6 +406,62 @@ test_module(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) } +#ifndef PYPY_VERSION +static PyObject * +test_float_pack(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) +{ + const int big_endian = 0; + const int little_endian = 1; + char data[8]; + const double d = 1.5; + +#if PY_VERSION_HEX >= 0x030600B1 +# define HAVE_FLOAT_PACK2 +#endif + + // Test Pack (big endian) +#ifdef HAVE_FLOAT_PACK2 + assert(PyFloat_Pack2(d, data, big_endian) == 0); + assert(memcmp(data, ">\x00", 2) == 0); +#endif + + assert(PyFloat_Pack4(d, data, big_endian) == 0); + assert(memcmp(data, "?\xc0\x00\x00", 2) == 0); + + assert(PyFloat_Pack8(d, data, big_endian) == 0); + assert(memcmp(data, "?\xf8\x00\x00\x00\x00\x00\x00", 2) == 0); + + // Test Pack (little endian) +#ifdef HAVE_FLOAT_PACK2 + assert(PyFloat_Pack2(d, data, little_endian) == 0); + assert(memcmp(data, "\x00>", 2) == 0); +#endif + + assert(PyFloat_Pack4(d, data, little_endian) == 0); + assert(memcmp(data, "\x00\x00\xc0?", 2) == 0); + + assert(PyFloat_Pack8(d, data, little_endian) == 0); + assert(memcmp(data, "\x00\x00\x00\x00\x00\x00\xf8?", 2) == 0); + + // Test Unpack (big endian) +#ifdef HAVE_FLOAT_PACK2 + assert(PyFloat_Unpack2(">\x00", big_endian) == d); +#endif + assert(PyFloat_Unpack4("?\xc0\x00\x00", big_endian) == d); + assert(PyFloat_Unpack8("?\xf8\x00\x00\x00\x00\x00\x00", big_endian) == d); + + // Test Unpack (little endian) +#ifdef HAVE_FLOAT_PACK2 + assert(PyFloat_Unpack2("\x00>", little_endian) == d); +#endif + assert(PyFloat_Unpack4("\x00\x00\xc0?", little_endian) == d); + assert(PyFloat_Unpack8("\x00\x00\x00\x00\x00\x00\xf8?", little_endian) == d); + + Py_RETURN_NONE; +} +#endif // !PYPY_VERSION + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, NULL}, {"test_py_is", test_py_is, METH_NOARGS, NULL}, @@ -418,6 +474,9 @@ static struct PyMethodDef methods[] = { {"test_calls", test_calls, METH_NOARGS, NULL}, {"test_gc", test_gc, METH_NOARGS, NULL}, {"test_module", test_module, METH_NOARGS, NULL}, +#ifndef PYPY_VERSION + {"test_float_pack", test_float_pack, METH_NOARGS, NULL}, +#endif {NULL, NULL, 0, NULL} }; From 0dfa681402d986a41989b4694d9867fa97c64639 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 12 Mar 2022 00:16:43 +0100 Subject: [PATCH 2/4] Update --- docs/api.rst | 12 ++++++------ pythoncapi_compat.h | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 97858ed..bb3fe18 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -41,37 +41,37 @@ Python 3.11 .. c:function:: int PyFloat_Pack2(double x, unsigned char *p, int le) - Pack a C double as 16-bit. + Pack a C double as the IEEE 754 binary16 half-precision format. Availability: Python 3.6 and newer. Not available on PyPy .. c:function:: int PyFloat_Pack4(double x, unsigned char *p, int le) - Pack a C double as 32-bit. + Pack a C double as the IEEE 754 binary32 single precision format. Not available on PyPy .. c:function:: int PyFloat_Pack8(double x, unsigned char *p, int le) - Pack a C double as 64-bit. + Pack a C double as the IEEE 754 binary64 double precision format. Not available on PyPy .. c:function:: double PyFloat_Unpack2(const unsigned char *p, int le) - Unpack 16-bit as a C double. + Unpack the IEEE 754 binary16 half-precision format as a C double. Availability: Python 3.6 and newer. Not available on PyPy .. c:function:: double PyFloat_Unpack4(const unsigned char *p, int le) - Unpack 32-bit as a C double. + Unpack the IEEE 754 binary32 single precision format as a C double. Not available on PyPy .. c:function:: double PyFloat_Unpack8(const unsigned char *p, int le) - Unpack 64-bit as a C double. + Unpack the IEEE 754 binary64 double precision format as a C double. Not available on PyPy diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index ef76cdd..1d08b20 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -391,7 +391,8 @@ _Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { #endif -// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1 +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. +// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7. #if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) PYCAPI_COMPAT_STATIC_INLINE(int) PyFloat_Pack2(double x, char *p, int le) @@ -403,6 +404,8 @@ PyFloat_Unpack2(const char *p, int le) #endif +// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and +// PyFloat_Unpack8() to Python 3.11a7. #if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) PYCAPI_COMPAT_STATIC_INLINE(int) PyFloat_Pack4(double x, char *p, int le) From c7d3657818cfd441bc8b658d65c9bc1fcad523c0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 12 Mar 2022 00:26:35 +0100 Subject: [PATCH 3/4] Fix tests on Python 3.11a5 --- tests/test_pythoncapi_compat_cext.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 4b9c922..ecbb8a1 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1,6 +1,17 @@ // Always enable assertions #undef NDEBUG +// In Python 3.11a2-3.11a6, _PyFloat_Pack8() is part of the internal C API: +// pythoncapi_compat.h doesn't support these early alpha versions. Workaround +// the issue to be able to test pythoncapi_compat.h on these unsupported Python +// versions anyway. +#include "Python.h" +#if (0x030B00A2 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A6) && !defined(PYPY_VERSION) +# define Py_BUILD_CORE 1 +# include "internal/pycore_floatobject.h" +# undef Py_BUILD_CORE +#endif + #include "pythoncapi_compat.h" #ifdef Py_LIMITED_API From 5b669e9524e6c7fa0cbcacd9662fa2adde9b520a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 12 Mar 2022 00:33:53 +0100 Subject: [PATCH 4/4] Fix Python 3.11a2-3.11a6 --- pythoncapi_compat.h | 11 ++++++++--- tests/test_pythoncapi_compat_cext.c | 17 +++-------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 1d08b20..2837467 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -391,9 +391,11 @@ _Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { #endif -// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. // bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7. -#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. +// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal +// C API: Python 3.11a2-3.11a6 versions are not supported. +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) PYCAPI_COMPAT_STATIC_INLINE(int) PyFloat_Pack2(double x, char *p, int le) { return _PyFloat_Pack2(x, (unsigned char*)p, le); } @@ -406,7 +408,10 @@ PyFloat_Unpack2(const char *p, int le) // bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and // PyFloat_Unpack8() to Python 3.11a7. -#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4() +// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions +// are not supported. +#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) PYCAPI_COMPAT_STATIC_INLINE(int) PyFloat_Pack4(double x, char *p, int le) { return _PyFloat_Pack4(x, (unsigned char*)p, le); } diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index ecbb8a1..fa1483c 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1,17 +1,6 @@ // Always enable assertions #undef NDEBUG -// In Python 3.11a2-3.11a6, _PyFloat_Pack8() is part of the internal C API: -// pythoncapi_compat.h doesn't support these early alpha versions. Workaround -// the issue to be able to test pythoncapi_compat.h on these unsupported Python -// versions anyway. -#include "Python.h" -#if (0x030B00A2 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A6) && !defined(PYPY_VERSION) -# define Py_BUILD_CORE 1 -# include "internal/pycore_floatobject.h" -# undef Py_BUILD_CORE -#endif - #include "pythoncapi_compat.h" #ifdef Py_LIMITED_API @@ -417,7 +406,7 @@ test_module(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) } -#ifndef PYPY_VERSION +#if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION) static PyObject * test_float_pack(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) { @@ -470,7 +459,7 @@ test_float_pack(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) Py_RETURN_NONE; } -#endif // !PYPY_VERSION +#endif static struct PyMethodDef methods[] = { @@ -485,7 +474,7 @@ static struct PyMethodDef methods[] = { {"test_calls", test_calls, METH_NOARGS, NULL}, {"test_gc", test_gc, METH_NOARGS, NULL}, {"test_module", test_module, METH_NOARGS, NULL}, -#ifndef PYPY_VERSION +#if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION) {"test_float_pack", test_float_pack, METH_NOARGS, NULL}, #endif {NULL, NULL, 0, NULL}