From 502589d98f81ca8c3fa5769587f2adc4627ada7f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 24 Jul 2026 12:23:52 +0300 Subject: [PATCH 01/10] gh-153740: use native _Float16 in PyFloat_Pack/Unpack2 --- ...-07-28-04-29-52.gh-issue-153740.7_LMSA.rst | 3 + Objects/floatobject.c | 79 +++++++++++++++++++ configure | 43 ++++++++++ configure.ac | 16 ++++ pyconfig.h.in | 3 + 5 files changed, 144 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2026-07-28-04-29-52.gh-issue-153740.7_LMSA.rst diff --git a/Misc/NEWS.d/next/C_API/2026-07-28-04-29-52.gh-issue-153740.7_LMSA.rst b/Misc/NEWS.d/next/C_API/2026-07-28-04-29-52.gh-issue-153740.7_LMSA.rst new file mode 100644 index 000000000000000..f01009f10e9b5b1 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-07-28-04-29-52.gh-issue-153740.7_LMSA.rst @@ -0,0 +1,3 @@ +If available on the platform, use native :c:type:`_Float16` in +:c:func:`PyFloat_Pack2` and :c:func:`PyFloat_Unpack2` functions. Patch by +Sergey B Kirpichev. diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 17e6a729dcd83fc..2ce24445eb7cf44 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1894,6 +1894,47 @@ _PyFloat_DebugMallocStats(FILE *out) int PyFloat_Pack2(double x, char *data, int le) { +#if HAVE_FLOAT16 + unsigned char *p = (unsigned char *)data; + _Float16 y = (_Float16)x; + int i, incr = 1; + + if (isinf(y) && !isinf(x)) { + PyErr_SetString(PyExc_OverflowError, + "float too large to pack with e format"); + return -1; + } + + /* correct y if x was a sNaN, transformed to qNaN by conversion */ + if (isnan(x)) { + uint64_t v; + + memcpy(&v, &x, 8); + if ((v & (1ULL << 51)) == 0) { + uint16_t u16; + memcpy(&u16, &y, 2); + /* if have payload, make sNaN */ + if (u16 & 0x1ff) { + u16 &= ~(1 << 9); + } + memcpy(&y, &u16, 2); + } + } + + unsigned char s[sizeof(_Float16)]; + memcpy(s, &y, sizeof(_Float16)); + + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { + p += 1; + incr = -1; + } + + for (i = 0; i < 2; i++) { + *p = s[i]; + p += incr; + } + return 0; +#else unsigned char *p = (unsigned char *)data; unsigned char sign; int e; @@ -1997,6 +2038,7 @@ PyFloat_Pack2(double x, char *data, int le) PyErr_SetString(PyExc_OverflowError, "float too large to pack with e format"); return -1; +#endif /* HAVE_FLOAT16 */ } int @@ -2089,6 +2131,42 @@ PyFloat_Pack8(double x, char *data, int le) double PyFloat_Unpack2(const char *data, int le) { +#if HAVE_FLOAT16 + unsigned char *p = (unsigned char *)data; + _Float16 x; + + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { + char buf[2]; + char *d = &buf[1]; + int i; + + for (i = 0; i < 2; i++) { + *d-- = *p++; + } + memcpy(&x, buf, 2); + } + else { + memcpy(&x, p, 2); + } + + /* return sNaN double if x was sNaN float */ + if (isnan(x)) { + uint16_t v; + + memcpy(&v, &x, 2); + if ((v & (1 << 9)) == 0) { + double y = x; /* will make qNaN double */ + uint64_t u64; + + memcpy(&u64, &y, 8); + u64 &= ~(1ULL << 51); /* make sNaN */ + memcpy(&y, &u64, 8); + return y; + } + } + + return x; +#else unsigned char *p = (unsigned char *)data; unsigned char sign; int e; @@ -2140,6 +2218,7 @@ PyFloat_Unpack2(const char *data, int le) x = -x; return x; +#endif /* HAVE_FLOAT16 */ } double diff --git a/configure b/configure index 69d76f7b9f2dba2..9af3031b32297b2 100755 --- a/configure +++ b/configure @@ -16699,6 +16699,49 @@ printf "%s\n" "#define _Py_FFI_SUPPORT_C_COMPLEX 1" >>confdefs.h fi +# Check for native half-float type. +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _Float16 support" >&5 +printf %s "checking for _Float16 support... " >&6; } +if test ${ac_cv_float16_supported+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test "$cross_compiling" = yes +then : + ac_cv_float16_supported=no +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int main(void) +{ + _Float16 val = 1.0f16; + return 0; +} + +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + ac_cv_float16_supported=yes +else case e in #( + e) ac_cv_float16_supported=no ;; +esac +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_float16_supported" >&5 +printf "%s\n" "$ac_cv_float16_supported" >&6; } +if test "$ac_cv_float16_supported" = "yes"; then + +printf "%s\n" "#define HAVE_FLOAT16 1" >>confdefs.h + +fi + pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libmpdec >= 2.5.0" >&5 diff --git a/configure.ac b/configure.ac index 93b211eef749880..9c59e8e10c314b5 100644 --- a/configure.ac +++ b/configure.ac @@ -4457,6 +4457,22 @@ if test "$ac_cv_ffi_complex_double_supported" = "yes"; then [Defined if _Complex C type can be used with libffi.]) fi +# Check for native half-float type. +AC_CACHE_CHECK([for _Float16 support], [ac_cv_float16_supported], +AC_RUN_IFELSE([AC_LANG_SOURCE([[ +int main(void) +{ + _Float16 val = 1.0f16; + return 0; +} +]])], [ac_cv_float16_supported=yes], +[ac_cv_float16_supported=no], +[ac_cv_float16_supported=no])) +if test "$ac_cv_float16_supported" = "yes"; then + AC_DEFINE([HAVE_FLOAT16], [1], + [Defined if _Float16 C type is supported]) +fi + dnl Check for libmpdec >= 2.5.0 PKG_CHECK_MODULES([LIBMPDEC], [libmpdec >= 2.5.0], [have_mpdec=yes], [ WITH_SAVE_ENV([ diff --git a/pyconfig.h.in b/pyconfig.h.in index 2658fe8116781db..a61135e40bf9371 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -494,6 +494,9 @@ /* Define if you have the 'ffi_prep_closure_loc' function. */ #undef HAVE_FFI_PREP_CLOSURE_LOC +/* Defined if _Float16 C type is supported */ +#undef HAVE_FLOAT16 + /* Define to 1 if you have the 'flock' function. */ #undef HAVE_FLOCK From d90dafb78f72ef3e9acf7ff5b6ac2b37a94f5e4a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 28 Jul 2026 07:38:20 +0300 Subject: [PATCH 02/10] +1 --- configure | 19 +++++++++++++++++-- configure.ac | 12 +++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/configure b/configure index 9af3031b32297b2..ad93cbe94afe11f 100755 --- a/configure +++ b/configure @@ -16706,7 +16706,14 @@ if test ${ac_cv_float16_supported+y} then : printf %s "(cached) " >&6 else case e in #( - e) if test "$cross_compiling" = yes + e) save_CFLAGS=$CFLAGS +save_CPPFLAGS=$CPPFLAGS +save_LDFLAGS=$LDFLAGS +save_LIBS=$LIBS + + +CFLAGS="$CFLAGS -mf16c" +if test "$cross_compiling" = yes then : ac_cv_float16_supported=no else case e in #( @@ -16731,15 +16738,23 @@ rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi + +CFLAGS=$save_CFLAGS +CPPFLAGS=$save_CPPFLAGS +LDFLAGS=$save_LDFLAGS +LIBS=$save_LIBS + ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_float16_supported" >&5 printf "%s\n" "$ac_cv_float16_supported" >&6; } -if test "$ac_cv_float16_supported" = "yes"; then +if test "x$ac_cv_float16_supported" = xyes +then : printf "%s\n" "#define HAVE_FLOAT16 1" >>confdefs.h + BASECFLAGS="$BASECFLAGS -mf16c" fi diff --git a/configure.ac b/configure.ac index 9c59e8e10c314b5..237d016c21bf6c0 100644 --- a/configure.ac +++ b/configure.ac @@ -4459,6 +4459,8 @@ fi # Check for native half-float type. AC_CACHE_CHECK([for _Float16 support], [ac_cv_float16_supported], +WITH_SAVE_ENV([ +CFLAGS="$CFLAGS -mf16c" AC_RUN_IFELSE([AC_LANG_SOURCE([[ int main(void) { @@ -4467,11 +4469,11 @@ int main(void) } ]])], [ac_cv_float16_supported=yes], [ac_cv_float16_supported=no], -[ac_cv_float16_supported=no])) -if test "$ac_cv_float16_supported" = "yes"; then - AC_DEFINE([HAVE_FLOAT16], [1], - [Defined if _Float16 C type is supported]) -fi +[ac_cv_float16_supported=no])])) +AS_VAR_IF([ac_cv_float16_supported], [yes], + [AC_DEFINE([HAVE_FLOAT16], [1], + [Defined if _Float16 C type is supported]) + BASECFLAGS="$BASECFLAGS -mf16c"]) dnl Check for libmpdec >= 2.5.0 PKG_CHECK_MODULES([LIBMPDEC], [libmpdec >= 2.5.0], [have_mpdec=yes], [ From 8bda53ab6358caf880c1338137e029e758359672 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 30 Jul 2026 02:45:52 +0300 Subject: [PATCH 03/10] + test conversion from double --- configure | 2 ++ configure.ac | 2 ++ 2 files changed, 4 insertions(+) diff --git a/configure b/configure index ad93cbe94afe11f..4072f360bec0f20 100755 --- a/configure +++ b/configure @@ -16723,6 +16723,8 @@ else case e in #( int main(void) { _Float16 val = 1.0f16; + double d = 3.14; + val = d; return 0; } diff --git a/configure.ac b/configure.ac index 237d016c21bf6c0..ffaeae9c4875e8f 100644 --- a/configure.ac +++ b/configure.ac @@ -4465,6 +4465,8 @@ AC_RUN_IFELSE([AC_LANG_SOURCE([[ int main(void) { _Float16 val = 1.0f16; + double d = 3.14; + val = d; return 0; } ]])], [ac_cv_float16_supported=yes], From ac38cb83b7e5b602d034eb09bea7b41ef9f243bd Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 30 Jul 2026 03:35:56 +0300 Subject: [PATCH 04/10] + -O0 --- configure | 2 +- configure.ac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index f1b99924bd92e76..946d1bf12750abd 100755 --- a/configure +++ b/configure @@ -16718,7 +16718,7 @@ save_LDFLAGS=$LDFLAGS save_LIBS=$LIBS -CFLAGS="$CFLAGS -mf16c" +CFLAGS="$CFLAGS -mf16c -O0" if test "$cross_compiling" = yes then : ac_cv_float16_supported=no diff --git a/configure.ac b/configure.ac index 3bdcd5a5e9f7551..b5cf151a86a3865 100644 --- a/configure.ac +++ b/configure.ac @@ -4465,7 +4465,7 @@ fi # Check for native half-float type. AC_CACHE_CHECK([for _Float16 support], [ac_cv_float16_supported], WITH_SAVE_ENV([ -CFLAGS="$CFLAGS -mf16c" +CFLAGS="$CFLAGS -mf16c -O0" AC_RUN_IFELSE([AC_LANG_SOURCE([[ int main(void) { From a8aa77a0107632a5f03deb8b08af2710745aa674 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 30 Jul 2026 10:00:28 +0300 Subject: [PATCH 05/10] drop -mf16c --- configure | 2 +- configure.ac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 946d1bf12750abd..8b291a842adb4c4 100755 --- a/configure +++ b/configure @@ -16718,7 +16718,7 @@ save_LDFLAGS=$LDFLAGS save_LIBS=$LIBS -CFLAGS="$CFLAGS -mf16c -O0" +CFLAGS="$CFLAGS -O0" if test "$cross_compiling" = yes then : ac_cv_float16_supported=no diff --git a/configure.ac b/configure.ac index b5cf151a86a3865..c4cc7aac2de7906 100644 --- a/configure.ac +++ b/configure.ac @@ -4465,7 +4465,7 @@ fi # Check for native half-float type. AC_CACHE_CHECK([for _Float16 support], [ac_cv_float16_supported], WITH_SAVE_ENV([ -CFLAGS="$CFLAGS -mf16c -O0" +CFLAGS="$CFLAGS -O0" AC_RUN_IFELSE([AC_LANG_SOURCE([[ int main(void) { From 7267d2decbc00915bfcdaad7137112a5c579c8da Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 30 Jul 2026 20:01:09 +0300 Subject: [PATCH 06/10] +1 --- configure | 1 - configure.ac | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/configure b/configure index 8b291a842adb4c4..47a78bbd2e6d578 100755 --- a/configure +++ b/configure @@ -16762,7 +16762,6 @@ then : printf "%s\n" "#define HAVE_FLOAT16 1" >>confdefs.h - BASECFLAGS="$BASECFLAGS -mf16c" fi diff --git a/configure.ac b/configure.ac index c4cc7aac2de7906..8189379b70f7a52 100644 --- a/configure.ac +++ b/configure.ac @@ -4479,8 +4479,7 @@ int main(void) [ac_cv_float16_supported=no])])) AS_VAR_IF([ac_cv_float16_supported], [yes], [AC_DEFINE([HAVE_FLOAT16], [1], - [Defined if _Float16 C type is supported]) - BASECFLAGS="$BASECFLAGS -mf16c"]) + [Defined if _Float16 C type is supported])]) dnl Check for libmpdec >= 2.5.0 PKG_CHECK_MODULES([LIBMPDEC], [libmpdec >= 2.5.0], [have_mpdec=yes], [ From 00d48b7c7f6f295d4ecf2bcbea03b8d8d580307f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 31 Jul 2026 04:17:10 +0300 Subject: [PATCH 07/10] add risc-v workaround --- Objects/floatobject.c | 45 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 2ce24445eb7cf44..86301e1dcf4a5fd 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1910,6 +1910,7 @@ PyFloat_Pack2(double x, char *data, int le) uint64_t v; memcpy(&v, &x, 8); +#ifndef __riscv if ((v & (1ULL << 51)) == 0) { uint16_t u16; memcpy(&u16, &y, 2); @@ -1919,6 +1920,27 @@ PyFloat_Pack2(double x, char *data, int le) } memcpy(&y, &u16, 2); } +#else + uint16_t u16; + + memcpy(&u16, &y, 2); + /* Workaround RISC-V: "If a NaN value is converted to a + * different floating-point type, the result is the + * canonical NaN of the new type". The canonical NaN here + * is a positive qNaN with zero payload. */ + if (v & (1ULL << 63)) { + u16 |= (1 << 15); /* set sign */ + } + /* add payload */ + u16 -= (u16 & 0x1ff); + u16 += (uint16_t)((v & 0x7ffffffffffffULL) >> 42); + /* if have payload, make sNaN */ + if ((v & (1ULL << 51)) == 0 && (u16 & 0x1ff)) { + u16 &= ~(1 << 9); + } + + memcpy(&y, &u16, 2); +#endif } unsigned char s[sizeof(_Float16)]; @@ -2152,8 +2174,9 @@ PyFloat_Unpack2(const char *data, int le) /* return sNaN double if x was sNaN float */ if (isnan(x)) { uint16_t v; - memcpy(&v, &x, 2); + +#ifndef __riscv if ((v & (1 << 9)) == 0) { double y = x; /* will make qNaN double */ uint64_t u64; @@ -2163,6 +2186,26 @@ PyFloat_Unpack2(const char *data, int le) memcpy(&y, &u64, 8); return y; } + +#else + double y = x; + uint64_t u64; + + memcpy(&u64, &y, 8); + if ((v & (1 << 9)) == 0) { + u64 &= ~(1ULL << 51); + } + /* Workaround RISC-V, see PyFloat_Pack4() */ + if (v & (1 << 15)) { + u64 |= (1ULL << 63); /* set sign */ + } + /* add payload */ + u64 -= (u64 & 0x7ffffffffffffULL); + u64 += ((v & 0x1ff) << 42); + + memcpy(&y, &u64, 8); + return y; +#endif } return x; From d7a79fa983ab2fb4b1092a04ecc4b7c7e0f2cb97 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 31 Jul 2026 07:17:39 +0300 Subject: [PATCH 08/10] +1 --- Objects/floatobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 86301e1dcf4a5fd..f5914b9f094178c 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2201,7 +2201,7 @@ PyFloat_Unpack2(const char *data, int le) } /* add payload */ u64 -= (u64 & 0x7ffffffffffffULL); - u64 += ((v & 0x1ff) << 42); + u64 += ((v & 0x1ffULL) << 42); memcpy(&y, &u64, 8); return y; From 7776661e25f238b793b81e3ed1d60575ab492575 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 31 Jul 2026 10:06:05 +0300 Subject: [PATCH 09/10] + test sign of nan value --- Lib/test/test_capi/test_float.py | 2 ++ Objects/floatobject.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 8b25607b6d504f7..91da2cf5ee9f616 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -226,6 +226,8 @@ def test_pack_unpack_roundtrip_for_nans(self): value = unpack(data1, endian) data2 = pack(size, value, endian) self.assertTrue(math.isnan(value)) + self.assertEqual(math.copysign(1.0, value), + -1.0 if sign else 1.0) self.assertEqual(data1, data2) @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") diff --git a/Objects/floatobject.c b/Objects/floatobject.c index f5914b9f094178c..1a523a3124f8d07 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1929,7 +1929,7 @@ PyFloat_Pack2(double x, char *data, int le) * canonical NaN of the new type". The canonical NaN here * is a positive qNaN with zero payload. */ if (v & (1ULL << 63)) { - u16 |= (1 << 15); /* set sign */ + u16 |= (1U << 15); /* set sign */ } /* add payload */ u16 -= (u16 & 0x1ff); @@ -2196,7 +2196,7 @@ PyFloat_Unpack2(const char *data, int le) u64 &= ~(1ULL << 51); } /* Workaround RISC-V, see PyFloat_Pack4() */ - if (v & (1 << 15)) { + if (v & (1U << 15)) { u64 |= (1ULL << 63); /* set sign */ } /* add payload */ From 2a90e57ef299a88cf63de7c59a0c08068138f963 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 31 Jul 2026 11:26:59 +0300 Subject: [PATCH 10/10] + cast --- Objects/floatobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 1a523a3124f8d07..3d32f6a11d90b30 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1929,7 +1929,7 @@ PyFloat_Pack2(double x, char *data, int le) * canonical NaN of the new type". The canonical NaN here * is a positive qNaN with zero payload. */ if (v & (1ULL << 63)) { - u16 |= (1U << 15); /* set sign */ + u16 |= (uint16_t)(1U << 15); /* set sign */ } /* add payload */ u16 -= (u16 & 0x1ff);