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/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..e379770e10031d2 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1895,6 +1895,31 @@ int PyFloat_Pack2(double x, char *data, int le) { unsigned char *p = (unsigned char *)data; +#if HAVE_FLOAT16 + /* Conversion can change NaNs type or alter payload. Here we + just fallback to the generic code, instead of providing + workarounds as for single/double precision. */ + if (!isnan(x)) { + _Float16 y = (_Float16)x; + + if (isinf(y) && !isinf(x)) { + goto Overflow; + } + + unsigned char s[sizeof(_Float16)]; + + memcpy(s, &y, sizeof(_Float16)); + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { + p[1] = s[0]; + p[0] = s[1]; + } + else { + p[0] = s[0]; + p[1] = s[1]; + } + return 0; + } +#endif unsigned char sign; int e; double f; @@ -2090,6 +2115,24 @@ double PyFloat_Unpack2(const char *data, int le) { unsigned char *p = (unsigned char *)data; +#if HAVE_FLOAT16 + _Float16 x16; + + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { + char buf[2]; + + buf[1] = p[0]; + buf[0] = p[1]; + memcpy(&x16, buf, 2); + } + else { + memcpy(&x16, p, 2); + } + if (!isnan(x16)) { + return x16; + } + /* Fallback to the generic code for NaNs, see PyFloat_Pack2(). */ +#endif unsigned char sign; int e; unsigned int f; diff --git a/configure b/configure index 6b560fe6841b722..e0ad04b036aeb13 100755 --- a/configure +++ b/configure @@ -16724,6 +16724,65 @@ printf "%s\n" "#define _Py_FFI_SUPPORT_C_COMPLEX 1" >>confdefs.h fi +# Check for native half-float type (_Float16). +{ 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) save_CFLAGS=$CFLAGS +save_CPPFLAGS=$CPPFLAGS +save_LDFLAGS=$LDFLAGS +save_LIBS=$LIBS + + +CFLAGS="$CFLAGS -O0" +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; + double d = 3.14; + val = d; + 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 + +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 "x$ac_cv_float16_supported" = xyes +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 476f13c82bbb2b9..2b8a5b052878c91 100644 --- a/configure.ac +++ b/configure.ac @@ -4469,6 +4469,25 @@ 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 (_Float16). +AC_CACHE_CHECK([for _Float16 support], [ac_cv_float16_supported], +WITH_SAVE_ENV([ +CFLAGS="$CFLAGS -O0" +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], +[ac_cv_float16_supported=no], +[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])]) + 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 64b5c52790b4581..c2a36afe334763c 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -497,6 +497,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