From de58b64f5243ada44d6f35c831fde373d54ad639 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 7 Mar 2026 11:37:22 +0300 Subject: [PATCH 1/9] gh-145633: remove support for ancient ARM platforms with mixed-endian doubles * drop DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754 macro * use DOUBLE_IS_BIG/LITTLE_ENDIAN_IEEE754 to detect endianness of float/doubles * drop "unknown_format" code path in PyFloat_Pack/Unpack*() --- Include/internal/pycore_pymath.h | 3 +- Include/pymacconfig.h | 1 - ...-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst | 2 + Objects/floatobject.c | 588 ++++-------------- Python/dtoa.c | 6 +- configure | 13 - configure.ac | 14 +- pyconfig.h.in | 4 - 8 files changed, 121 insertions(+), 510 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst diff --git a/Include/internal/pycore_pymath.h b/Include/internal/pycore_pymath.h index f66325aa59c4c9..532c5ceafb5639 100644 --- a/Include/internal/pycore_pymath.h +++ b/Include/internal/pycore_pymath.h @@ -182,8 +182,7 @@ extern void _Py_set_387controlword(unsigned short); // (extended precision), and we don't know how to change // the rounding precision. #if !defined(DOUBLE_IS_LITTLE_ENDIAN_IEEE754) && \ - !defined(DOUBLE_IS_BIG_ENDIAN_IEEE754) && \ - !defined(DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754) + !defined(DOUBLE_IS_BIG_ENDIAN_IEEE754) # define _PY_SHORT_FLOAT_REPR 0 #endif diff --git a/Include/pymacconfig.h b/Include/pymacconfig.h index 615abe103ca038..9d63ddf8a716f4 100644 --- a/Include/pymacconfig.h +++ b/Include/pymacconfig.h @@ -18,7 +18,6 @@ #undef SIZEOF_UINTPTR_T #undef SIZEOF_PTHREAD_T #undef WORDS_BIGENDIAN -#undef DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754 #undef DOUBLE_IS_BIG_ENDIAN_IEEE754 #undef DOUBLE_IS_LITTLE_ENDIAN_IEEE754 #undef HAVE_GCC_ASM_FOR_X87 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst new file mode 100644 index 00000000000000..2f09bb416295e3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst @@ -0,0 +1,2 @@ +Remove support for ancient ARM platforms, using mixed-endian representation +for doubles. Patch by Sergey B Kirpichev. diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 579765281ca484..9b953e86076e57 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1668,7 +1668,6 @@ float___getnewargs___impl(PyObject *self) /* this is for the benefit of the pack/unpack routines below */ typedef enum _py_float_format_type float_format_type; -#define unknown_format _py_float_format_unknown #define ieee_big_endian_format _py_float_format_ieee_big_endian #define ieee_little_endian_format _py_float_format_ieee_little_endian @@ -1689,7 +1688,7 @@ You probably don't want to use this function. It exists mainly to be used in Python's test suite. -This function returns whichever of 'unknown', 'IEEE, big-endian' or 'IEEE, +This function returns whichever of 'IEEE, big-endian' or 'IEEE, little-endian' best describes the format of floating-point numbers used by the C type named by typestr. [clinic start generated code]*/ @@ -1714,8 +1713,6 @@ float___getformat___impl(PyTypeObject *type, const char *typestr) } switch (r) { - case unknown_format: - return PyUnicode_FromString("unknown"); case ieee_little_endian_format: return PyUnicode_FromString("IEEE, little-endian"); case ieee_big_endian_format: @@ -1727,7 +1724,6 @@ float___getformat___impl(PyTypeObject *type, const char *typestr) } } - static PyObject * float_getreal(PyObject *v, void *Py_UNUSED(closure)) { @@ -1881,53 +1877,13 @@ PyTypeObject PyFloat_Type = { static void _init_global_state(void) { - float_format_type detected_double_format, detected_float_format; - - /* We attempt to determine if this machine is using IEEE - floating-point formats by peering at the bits of some - carefully chosen values. If it looks like we are on an - IEEE platform, the float packing/unpacking routines can - just copy bits, if not they resort to arithmetic & shifts - and masks. The shifts & masks approach works on all finite - values, but what happens to infinities, NaNs and signed - zeroes on packing is an accident, and attempting to unpack - a NaN or an infinity will raise an exception. - - Note that if we're on some whacked-out platform which uses - IEEE formats but isn't strictly little-endian or big- - endian, we will fall back to the portable shifts & masks - method. */ - -#if SIZEOF_DOUBLE == 8 - { - double x = 9006104071832581.0; - if (memcmp(&x, "\x43\x3f\xff\x01\x02\x03\x04\x05", 8) == 0) - detected_double_format = ieee_big_endian_format; - else if (memcmp(&x, "\x05\x04\x03\x02\x01\xff\x3f\x43", 8) == 0) - detected_double_format = ieee_little_endian_format; - else - detected_double_format = unknown_format; - } +#ifdef DOUBLE_IS_BIG_ENDIAN_IEEE754 + double_format = ieee_big_endian_format; + float_format = ieee_big_endian_format; #else - detected_double_format = unknown_format; + double_format = ieee_little_endian_format; + float_format = ieee_little_endian_format; #endif - -#if SIZEOF_FLOAT == 4 - { - float y = 16711938.0; - if (memcmp(&y, "\x4b\x7f\x01\x02", 4) == 0) - detected_float_format = ieee_big_endian_format; - else if (memcmp(&y, "\x02\x01\x7f\x4b", 4) == 0) - detected_float_format = ieee_little_endian_format; - else - detected_float_format = unknown_format; - } -#else - detected_float_format = unknown_format; -#endif - - double_format = detected_double_format; - float_format = detected_float_format; } void @@ -2092,278 +2048,89 @@ int PyFloat_Pack4(double x, char *data, int le) { unsigned char *p = (unsigned char *)data; - if (float_format == unknown_format) { - unsigned char sign; - int e; - double f; - unsigned int fbits; - int incr = 1; - - if (le) { - p += 3; - incr = -1; - } - - if (x < 0) { - sign = 1; - x = -x; - } - else - sign = 0; - - f = frexp(x, &e); - - /* Normalize f to be in the range [1.0, 2.0) */ - if (0.5 <= f && f < 1.0) { - f *= 2.0; - e--; - } - else if (f == 0.0) - e = 0; - else { - PyErr_SetString(PyExc_SystemError, - "frexp() result out of range"); - return -1; - } - - if (e >= 128) - goto Overflow; - else if (e < -126) { - /* Gradual underflow */ - f = ldexp(f, 126 + e); - e = 0; - } - else if (!(e == 0 && f == 0.0)) { - e += 127; - f -= 1.0; /* Get rid of leading 1 */ - } - - f *= 8388608.0; /* 2**23 */ - fbits = (unsigned int)(f + 0.5); /* Round */ - assert(fbits <= 8388608); - if (fbits >> 23) { - /* The carry propagated out of a string of 23 1 bits. */ - fbits = 0; - ++e; - if (e >= 255) - goto Overflow; - } - - /* First byte */ - *p = (sign << 7) | (e >> 1); - p += incr; - - /* Second byte */ - *p = (char) (((e & 1) << 7) | (fbits >> 16)); - p += incr; - - /* Third byte */ - *p = (fbits >> 8) & 0xFF; - p += incr; - - /* Fourth byte */ - *p = fbits & 0xFF; - - /* Done */ - return 0; + float y = (float)x; + int i, incr = 1; + if (isinf(y) && !isinf(x)) { + PyErr_SetString(PyExc_OverflowError, + "float too large to pack with f format"); + return -1; } - else { - float y = (float)x; - int i, incr = 1; - if (isinf(y) && !isinf(x)) - goto Overflow; - - /* correct y if x was a sNaN, transformed to qNaN by conversion */ - if (isnan(x)) { - uint64_t v; + /* correct y if x was a sNaN, transformed to qNaN by conversion */ + if (isnan(x)) { + uint64_t v; - memcpy(&v, &x, 8); + memcpy(&v, &x, 8); #ifndef __riscv - if ((v & (1ULL << 51)) == 0) { - uint32_t u32; - memcpy(&u32, &y, 4); - /* if have payload, make sNaN */ - if (u32 & 0x3fffff) { - u32 &= ~(1 << 22); - } - memcpy(&y, &u32, 4); - } -#else + if ((v & (1ULL << 51)) == 0) { uint32_t u32; - memcpy(&u32, &y, 4); - /* 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)) { - u32 |= (1 << 31); /* set sign */ - } - /* add payload */ - u32 -= (u32 & 0x3fffff); - u32 += (uint32_t)((v & 0x7ffffffffffffULL) >> 29); /* if have payload, make sNaN */ - if ((v & (1ULL << 51)) == 0 && (u32 & 0x3fffff)) { + if (u32 & 0x3fffff) { u32 &= ~(1 << 22); } - memcpy(&y, &u32, 4); -#endif + } +#else + uint32_t u32; + + memcpy(&u32, &y, 4); + /* 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)) { + u32 |= (1 << 31); /* set sign */ + } + /* add payload */ + u32 -= (u32 & 0x3fffff); + u32 += (uint32_t)((v & 0x7ffffffffffffULL) >> 29); + /* if have payload, make sNaN */ + if ((v & (1ULL << 51)) == 0 && (u32 & 0x3fffff)) { + u32 &= ~(1 << 22); } - unsigned char s[sizeof(float)]; - memcpy(s, &y, sizeof(float)); + memcpy(&y, &u32, 4); +#endif + } - if ((float_format == ieee_little_endian_format && !le) - || (float_format == ieee_big_endian_format && le)) { - p += 3; - incr = -1; - } + unsigned char s[sizeof(float)]; + memcpy(s, &y, sizeof(float)); - for (i = 0; i < 4; i++) { - *p = s[i]; - p += incr; - } - return 0; + if ((float_format == ieee_little_endian_format && !le) + || (float_format == ieee_big_endian_format && le)) { + p += 3; + incr = -1; } - Overflow: - PyErr_SetString(PyExc_OverflowError, - "float too large to pack with f format"); - return -1; + + for (i = 0; i < 4; i++) { + *p = s[i]; + p += incr; + } + return 0; } int PyFloat_Pack8(double x, char *data, int le) { unsigned char *p = (unsigned char *)data; - if (double_format == unknown_format) { - unsigned char sign; - int e; - double f; - unsigned int fhi, flo; - int incr = 1; - - if (le) { - p += 7; - incr = -1; - } - - if (x < 0) { - sign = 1; - x = -x; - } - else - sign = 0; - - f = frexp(x, &e); - - /* Normalize f to be in the range [1.0, 2.0) */ - if (0.5 <= f && f < 1.0) { - f *= 2.0; - e--; - } - else if (f == 0.0) - e = 0; - else { - PyErr_SetString(PyExc_SystemError, - "frexp() result out of range"); - return -1; - } - - if (e >= 1024) - goto Overflow; - else if (e < -1022) { - /* Gradual underflow */ - f = ldexp(f, 1022 + e); - e = 0; - } - else if (!(e == 0 && f == 0.0)) { - e += 1023; - f -= 1.0; /* Get rid of leading 1 */ - } - - /* fhi receives the high 28 bits; flo the low 24 bits (== 52 bits) */ - f *= 268435456.0; /* 2**28 */ - fhi = (unsigned int)f; /* Truncate */ - assert(fhi < 268435456); - - f -= (double)fhi; - f *= 16777216.0; /* 2**24 */ - flo = (unsigned int)(f + 0.5); /* Round */ - assert(flo <= 16777216); - if (flo >> 24) { - /* The carry propagated out of a string of 24 1 bits. */ - flo = 0; - ++fhi; - if (fhi >> 28) { - /* And it also propagated out of the next 28 bits. */ - fhi = 0; - ++e; - if (e >= 2047) - goto Overflow; - } - } - - /* First byte */ - *p = (sign << 7) | (e >> 4); - p += incr; - - /* Second byte */ - *p = (unsigned char) (((e & 0xF) << 4) | (fhi >> 24)); - p += incr; - - /* Third byte */ - *p = (fhi >> 16) & 0xFF; - p += incr; - - /* Fourth byte */ - *p = (fhi >> 8) & 0xFF; - p += incr; - - /* Fifth byte */ - *p = fhi & 0xFF; - p += incr; - - /* Sixth byte */ - *p = (flo >> 16) & 0xFF; - p += incr; - - /* Seventh byte */ - *p = (flo >> 8) & 0xFF; - p += incr; - - /* Eighth byte */ - *p = flo & 0xFF; - /* p += incr; */ - - /* Done */ - return 0; - - Overflow: - PyErr_SetString(PyExc_OverflowError, - "float too large to pack with d format"); - return -1; + unsigned char as_bytes[8]; + memcpy(as_bytes, &x, 8); + const unsigned char *s = as_bytes; + int i, incr = 1; + + if ((double_format == ieee_little_endian_format && !le) + || (double_format == ieee_big_endian_format && le)) { + p += 7; + incr = -1; } - else { - unsigned char as_bytes[8]; - memcpy(as_bytes, &x, 8); - const unsigned char *s = as_bytes; - int i, incr = 1; - - if ((double_format == ieee_little_endian_format && !le) - || (double_format == ieee_big_endian_format && le)) { - p += 7; - incr = -1; - } - for (i = 0; i < 8; i++) { - *p = *s++; - p += incr; - } - return 0; + for (i = 0; i < 8; i++) { + *p = *s++; + p += incr; } + return 0; } double @@ -2426,208 +2193,81 @@ double PyFloat_Unpack4(const char *data, int le) { unsigned char *p = (unsigned char *)data; - if (float_format == unknown_format) { - unsigned char sign; - int e; - unsigned int f; - double x; - int incr = 1; - - if (le) { - p += 3; - incr = -1; - } - - /* First byte */ - sign = (*p >> 7) & 1; - e = (*p & 0x7F) << 1; - p += incr; - - /* Second byte */ - e |= (*p >> 7) & 1; - f = (*p & 0x7F) << 16; - p += incr; - - if (e == 255) { - PyErr_SetString( - PyExc_ValueError, - "can't unpack IEEE 754 special value " - "on non-IEEE platform"); - return -1; - } - - /* Third byte */ - f |= *p << 8; - p += incr; + float x; - /* Fourth byte */ - f |= *p; + if ((float_format == ieee_little_endian_format && !le) + || (float_format == ieee_big_endian_format && le)) { + char buf[4]; + char *d = &buf[3]; + int i; - x = (double)f / 8388608.0; - - /* XXX This sadly ignores Inf/NaN issues */ - if (e == 0) - e = -126; - else { - x += 1.0; - e -= 127; + for (i = 0; i < 4; i++) { + *d-- = *p++; } - x = ldexp(x, e); - - if (sign) - x = -x; - - return x; + memcpy(&x, buf, 4); } else { - float x; - - if ((float_format == ieee_little_endian_format && !le) - || (float_format == ieee_big_endian_format && le)) { - char buf[4]; - char *d = &buf[3]; - int i; - - for (i = 0; i < 4; i++) { - *d-- = *p++; - } - memcpy(&x, buf, 4); - } - else { - memcpy(&x, p, 4); - } + memcpy(&x, p, 4); + } - /* return sNaN double if x was sNaN float */ - if (isnan(x)) { - uint32_t v; - memcpy(&v, &x, 4); + /* return sNaN double if x was sNaN float */ + if (isnan(x)) { + uint32_t v; + memcpy(&v, &x, 4); #ifndef __riscv - if ((v & (1 << 22)) == 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; - } -#else - double y = x; + if ((v & (1 << 22)) == 0) { + double y = x; /* will make qNaN double */ uint64_t u64; - memcpy(&u64, &y, 8); - if ((v & (1 << 22)) == 0) { - u64 &= ~(1ULL << 51); - } - /* Workaround RISC-V, see PyFloat_Pack4() */ - if (v & (1 << 31)) { - u64 |= (1ULL << 63); /* set sign */ - } - /* add payload */ - u64 -= (u64 & 0x7ffffffffffffULL); - u64 += ((v & 0x3fffffULL) << 29); - + u64 &= ~(1ULL << 51); /* make sNaN */ memcpy(&y, &u64, 8); return y; -#endif } +#else + double y = x; + uint64_t u64; + + memcpy(&u64, &y, 8); + if ((v & (1 << 22)) == 0) { + u64 &= ~(1ULL << 51); + } + /* Workaround RISC-V, see PyFloat_Pack4() */ + if (v & (1 << 31)) { + u64 |= (1ULL << 63); /* set sign */ + } + /* add payload */ + u64 -= (u64 & 0x7ffffffffffffULL); + u64 += ((v & 0x3fffffULL) << 29); - return x; + memcpy(&y, &u64, 8); + return y; +#endif } + + return x; } double PyFloat_Unpack8(const char *data, int le) { unsigned char *p = (unsigned char *)data; - if (double_format == unknown_format) { - unsigned char sign; - int e; - unsigned int fhi, flo; - double x; - int incr = 1; - - if (le) { - p += 7; - incr = -1; - } - - /* First byte */ - sign = (*p >> 7) & 1; - e = (*p & 0x7F) << 4; - - p += incr; - - /* Second byte */ - e |= (*p >> 4) & 0xF; - fhi = (*p & 0xF) << 24; - p += incr; - - if (e == 2047) { - PyErr_SetString( - PyExc_ValueError, - "can't unpack IEEE 754 special value " - "on non-IEEE platform"); - return -1.0; - } - - /* Third byte */ - fhi |= *p << 16; - p += incr; - - /* Fourth byte */ - fhi |= *p << 8; - p += incr; - - /* Fifth byte */ - fhi |= *p; - p += incr; - - /* Sixth byte */ - flo = *p << 16; - p += incr; - - /* Seventh byte */ - flo |= *p << 8; - p += incr; - - /* Eighth byte */ - flo |= *p; + double x; - x = (double)fhi + (double)flo / 16777216.0; /* 2**24 */ - x /= 268435456.0; /* 2**28 */ + if ((double_format == ieee_little_endian_format && !le) + || (double_format == ieee_big_endian_format && le)) { + char buf[8]; + char *d = &buf[7]; + int i; - if (e == 0) - e = -1022; - else { - x += 1.0; - e -= 1023; + for (i = 0; i < 8; i++) { + *d-- = *p++; } - x = ldexp(x, e); - - if (sign) - x = -x; - - return x; + memcpy(&x, buf, 8); } else { - double x; - - if ((double_format == ieee_little_endian_format && !le) - || (double_format == ieee_big_endian_format && le)) { - char buf[8]; - char *d = &buf[7]; - int i; - - for (i = 0; i < 8; i++) { - *d-- = *p++; - } - memcpy(&x, buf, 8); - } - else { - memcpy(&x, p, 8); - } - - return x; + memcpy(&x, p, 8); } + + return x; } diff --git a/Python/dtoa.c b/Python/dtoa.c index 3de150351a4ef8..89fadd33391cb4 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -139,8 +139,7 @@ #ifdef DOUBLE_IS_LITTLE_ENDIAN_IEEE754 # define IEEE_8087 #endif -#if defined(DOUBLE_IS_BIG_ENDIAN_IEEE754) || \ - defined(DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754) +#if defined(DOUBLE_IS_BIG_ENDIAN_IEEE754) # define IEEE_MC68k #endif #if defined(IEEE_8087) + defined(IEEE_MC68k) != 1 @@ -149,8 +148,7 @@ /* The code below assumes that the endianness of integers matches the endianness of the two 32-bit words of a double. Check this. */ -#if defined(WORDS_BIGENDIAN) && (defined(DOUBLE_IS_LITTLE_ENDIAN_IEEE754) || \ - defined(DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754)) +#if defined(WORDS_BIGENDIAN) && defined(DOUBLE_IS_LITTLE_ENDIAN_IEEE754) #error "doubles and ints have incompatible endianness" #endif diff --git a/configure b/configure index eca5f03cdcfb2d..4faf861b6ae96b 100755 --- a/configure +++ b/configure @@ -26202,20 +26202,7 @@ printf "%s\n" "#define DOUBLE_IS_BIG_ENDIAN_IEEE754 1" >>confdefs.h printf "%s\n" "#define DOUBLE_IS_LITTLE_ENDIAN_IEEE754 1" >>confdefs.h ;; *) - case $host_cpu in #( - *arm*) : - # Some ARM platforms use a mixed-endian representation for - # doubles. While Python doesn't currently have full support - # for these platforms (see e.g., issue 1762561), we can at - # least make sure that float <-> string conversions work. - # FLOAT_WORDS_BIGENDIAN doesn't actually detect this case, - # but if it's not big or little, then it must be this? - -printf "%s\n" "#define DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754 1" >>confdefs.h - ;; #( - *) : as_fn_error $? "Unknown float word ordering. You need to manually preset ax_cv_c_float_words_bigendian=no (or yes) according to your system." "$LINENO" 5 ;; -esac ;; esac diff --git a/configure.ac b/configure.ac index c21024a1e77433..e550c4e9a6888c 100644 --- a/configure.ac +++ b/configure.ac @@ -6168,21 +6168,11 @@ AX_C_FLOAT_WORDS_BIGENDIAN( [AC_DEFINE([DOUBLE_IS_LITTLE_ENDIAN_IEEE754], [1], [Define if C doubles are 64-bit IEEE 754 binary format, stored with the least significant byte first])], - [AS_CASE([$host_cpu], - [*arm*], [# Some ARM platforms use a mixed-endian representation for - # doubles. While Python doesn't currently have full support - # for these platforms (see e.g., issue 1762561), we can at - # least make sure that float <-> string conversions work. - # FLOAT_WORDS_BIGENDIAN doesn't actually detect this case, - # but if it's not big or little, then it must be this? - AC_DEFINE([DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754], [1], - [Define if C doubles are 64-bit IEEE 754 binary format, - stored in ARM mixed-endian order (byte order 45670123)])], - [AC_MSG_ERROR([m4_normalize([ + [AC_MSG_ERROR([m4_normalize([ Unknown float word ordering. You need to manually preset ax_cv_c_float_words_bigendian=no (or yes) according to your system. - ])])])]) + ])])]) # The short float repr introduced in Python 3.1 requires the # correctly-rounded string <-> double conversion functions from diff --git a/pyconfig.h.in b/pyconfig.h.in index fbd5d4d625908e..9da33c954a52f8 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -32,10 +32,6 @@ /* The Android API level. */ #undef ANDROID_API_LEVEL -/* Define if C doubles are 64-bit IEEE 754 binary format, stored in ARM - mixed-endian order (byte order 45670123) */ -#undef DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754 - /* Define if C doubles are 64-bit IEEE 754 binary format, stored with the most significant byte first */ #undef DOUBLE_IS_BIG_ENDIAN_IEEE754 From b6ba0021e9c0876ba65d3427720a646bb675e22b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 8 Mar 2026 06:42:40 +0300 Subject: [PATCH 2/9] +1 --- Objects/clinic/floatobject.c.h | 4 ++-- Objects/floatobject.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/clinic/floatobject.c.h b/Objects/clinic/floatobject.c.h index 4051131f480ccb..c0ae9d3ff9b8d3 100644 --- a/Objects/clinic/floatobject.c.h +++ b/Objects/clinic/floatobject.c.h @@ -290,7 +290,7 @@ PyDoc_STRVAR(float___getformat____doc__, "\n" "It exists mainly to be used in Python\'s test suite.\n" "\n" -"This function returns whichever of \'unknown\', \'IEEE, big-endian\' or \'IEEE,\n" +"This function returns whichever of \'IEEE, big-endian\' or \'IEEE,\n" "little-endian\' best describes the format of floating-point numbers used by the\n" "C type named by typestr."); @@ -353,4 +353,4 @@ float___format__(PyObject *self, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=927035897ea3573f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f0b2af257213c8b0 input=a9049054013a1b77]*/ diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 9b953e86076e57..8d4b29fd42d957 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1695,7 +1695,7 @@ C type named by typestr. static PyObject * float___getformat___impl(PyTypeObject *type, const char *typestr) -/*[clinic end generated code: output=2bfb987228cc9628 input=d2735823bfe8e81e]*/ +/*[clinic end generated code: output=2bfb987228cc9628 input=0ae1ba35d192f704]*/ { float_format_type r; From 94d706199b0a7debdef4002391885324ad6755c8 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 8 Mar 2026 06:49:11 +0300 Subject: [PATCH 3/9] Remove non-IEEE notes in docs --- Doc/c-api/float.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Doc/c-api/float.rst b/Doc/c-api/float.rst index dcd545478277a8..ca8d44c25c1ece 100644 --- a/Doc/c-api/float.rst +++ b/Doc/c-api/float.rst @@ -224,11 +224,6 @@ endian processor, or ``0`` on little endian processor. Return value: ``0`` if all is OK, ``-1`` if error (and an exception is set, most likely :exc:`OverflowError`). -There are two problems on non-IEEE platforms: - -* What this does is undefined if *x* is a NaN or infinity. -* ``-0.0`` and ``+0.0`` produce the same bytes string. - .. c:function:: int PyFloat_Pack2(double x, char *p, int le) Pack a C double as the IEEE 754 binary16 half-precision format. @@ -256,9 +251,6 @@ Return value: The unpacked double. On error, this is ``-1.0`` and :c:func:`PyErr_Occurred` is true (and an exception is set, most likely :exc:`OverflowError`). -Note that on a non-IEEE platform this will refuse to unpack a bytes string that -represents a NaN or infinity. - .. c:function:: double PyFloat_Unpack2(const char *p, int le) Unpack the IEEE 754 binary16 half-precision format as a C double. From f37db78feda98d7a39a09a4d5c176c7a799d3470 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 9 Mar 2026 14:38:47 +0300 Subject: [PATCH 4/9] address review: get rid of _py_float_format_unknown in the rest use _py_float_format_ieee_little_endian as a default. --- Include/internal/pycore_runtime_init.h | 8 ++++---- Include/internal/pycore_runtime_structs.h | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index b182f7825a2326..9b0fc1c747708d 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -13,7 +13,7 @@ extern "C" { #include "pycore_debug_offsets.h" // _Py_DebugOffsets_INIT() #include "pycore_dtoa.h" // _dtoa_state_INIT() #include "pycore_faulthandler.h" // _faulthandler_runtime_state_INIT -#include "pycore_floatobject.h" // _py_float_format_unknown +#include "pycore_floatobject.h" // _py_float_format_* #include "pycore_function.h" #include "pycore_hamt.h" // _PyHamt_BitmapNode_Type #include "pycore_import.h" // IMPORTS_INIT @@ -85,8 +85,8 @@ extern PyTypeObject _PyExc_MemoryError; .is_global = 1, \ }, \ .float_state = { \ - .float_format = _py_float_format_unknown, \ - .double_format = _py_float_format_unknown, \ + .float_format = _py_float_format_ieee_little_endian, \ + .double_format = _py_float_format_ieee_little_endian, \ }, \ .types = { \ .next_version_tag = _Py_TYPE_VERSION_NEXT, \ @@ -233,4 +233,4 @@ extern PyTypeObject _PyExc_MemoryError; #ifdef __cplusplus } #endif -#endif /* !Py_INTERNAL_RUNTIME_INIT_H */ \ No newline at end of file +#endif /* !Py_INTERNAL_RUNTIME_INIT_H */ diff --git a/Include/internal/pycore_runtime_structs.h b/Include/internal/pycore_runtime_structs.h index 90e6625ad1fc9c..9b6b70d94ba8d3 100644 --- a/Include/internal/pycore_runtime_structs.h +++ b/Include/internal/pycore_runtime_structs.h @@ -36,7 +36,6 @@ struct _pymem_allocators { }; enum _py_float_format_type { - _py_float_format_unknown, _py_float_format_ieee_big_endian, _py_float_format_ieee_little_endian, }; From 2941f6ddf1f78255543d3c41033a152411201111 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Mar 2026 04:46:16 +0300 Subject: [PATCH 5/9] address review: replace float_state by macros --- Include/internal/pycore_floatobject.h | 9 ++++ Include/internal/pycore_runtime_init.h | 4 -- Include/internal/pycore_runtime_structs.h | 11 ----- Objects/floatobject.c | 54 +++-------------------- 4 files changed, 16 insertions(+), 62 deletions(-) diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index 317f984188bad8..d8b76a161320ec 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -42,6 +42,15 @@ extern double _Py_parse_inf_or_nan(const char *p, char **endptr); extern int _Py_convert_int_to_double(PyObject **v, double *dbl); +/* Should match endianness of the platform in most (all?) cases. */ + +#ifdef DOUBLE_IS_BIG_ENDIAN_IEEE754 +# define _PY_FLOAT_BIG_ENDIAN 1 +# define _PY_FLOAT_LITTLE_ENDIAN 0 +#else +# define _PY_FLOAT_BIG_ENDIAN 0 +# define _PY_FLOAT_LITTLE_ENDIAN 1 +#endif #ifdef __cplusplus } diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 9b0fc1c747708d..e8d1098c2078fc 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -84,10 +84,6 @@ extern PyTypeObject _PyExc_MemoryError; .stoptheworld = { \ .is_global = 1, \ }, \ - .float_state = { \ - .float_format = _py_float_format_ieee_little_endian, \ - .double_format = _py_float_format_ieee_little_endian, \ - }, \ .types = { \ .next_version_tag = _Py_TYPE_VERSION_NEXT, \ }, \ diff --git a/Include/internal/pycore_runtime_structs.h b/Include/internal/pycore_runtime_structs.h index 9b6b70d94ba8d3..05369ef9f009e6 100644 --- a/Include/internal/pycore_runtime_structs.h +++ b/Include/internal/pycore_runtime_structs.h @@ -35,16 +35,6 @@ struct _pymem_allocators { PyObjectArenaAllocator obj_arena; }; -enum _py_float_format_type { - _py_float_format_ieee_big_endian, - _py_float_format_ieee_little_endian, -}; - -struct _Py_float_runtime_state { - enum _py_float_format_type float_format; - enum _py_float_format_type double_format; -}; - struct pyhash_runtime_state { struct { #ifndef MS_WINDOWS @@ -269,7 +259,6 @@ struct pyruntimestate { } audit_hooks; struct _py_object_runtime_state object_state; - struct _Py_float_runtime_state float_state; struct _Py_unicode_runtime_state unicode_state; struct _types_runtime_state types; struct _Py_time_runtime_state time; diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 8d4b29fd42d957..beb8c1c0d25038 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1699,29 +1699,14 @@ float___getformat___impl(PyTypeObject *type, const char *typestr) { float_format_type r; - if (strcmp(typestr, "double") == 0) { - r = double_format; - } - else if (strcmp(typestr, "float") == 0) { - r = float_format; - } - else { + if (strcmp(typestr, "double") != 0 || strcmp(typestr, "float") != 0) { PyErr_SetString(PyExc_ValueError, "__getformat__() argument 1 must be " "'double' or 'float'"); return NULL; } - - switch (r) { - case ieee_little_endian_format: - return PyUnicode_FromString("IEEE, little-endian"); - case ieee_big_endian_format: - return PyUnicode_FromString("IEEE, big-endian"); - default: - PyErr_SetString(PyExc_RuntimeError, - "insane float_format or double_format"); - return NULL; - } + return PyUnicode_FromString(_PY_FLOAT_LITTLE_ENDIAN ? + "IEEE, little-endian" : "IEEE, big-endian"); } static PyObject * @@ -1874,27 +1859,6 @@ PyTypeObject PyFloat_Type = { .tp_version_tag = _Py_TYPE_VERSION_FLOAT, }; -static void -_init_global_state(void) -{ -#ifdef DOUBLE_IS_BIG_ENDIAN_IEEE754 - double_format = ieee_big_endian_format; - float_format = ieee_big_endian_format; -#else - double_format = ieee_little_endian_format; - float_format = ieee_little_endian_format; -#endif -} - -void -_PyFloat_InitState(PyInterpreterState *interp) -{ - if (!_Py_IsMainInterpreter(interp)) { - return; - } - _init_global_state(); -} - PyStatus _PyFloat_InitTypes(PyInterpreterState *interp) { @@ -2098,8 +2062,7 @@ PyFloat_Pack4(double x, char *data, int le) unsigned char s[sizeof(float)]; memcpy(s, &y, sizeof(float)); - if ((float_format == ieee_little_endian_format && !le) - || (float_format == ieee_big_endian_format && le)) { + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { p += 3; incr = -1; } @@ -2120,8 +2083,7 @@ PyFloat_Pack8(double x, char *data, int le) const unsigned char *s = as_bytes; int i, incr = 1; - if ((double_format == ieee_little_endian_format && !le) - || (double_format == ieee_big_endian_format && le)) { + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { p += 7; incr = -1; } @@ -2195,8 +2157,7 @@ PyFloat_Unpack4(const char *data, int le) unsigned char *p = (unsigned char *)data; float x; - if ((float_format == ieee_little_endian_format && !le) - || (float_format == ieee_big_endian_format && le)) { + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { char buf[4]; char *d = &buf[3]; int i; @@ -2254,8 +2215,7 @@ PyFloat_Unpack8(const char *data, int le) unsigned char *p = (unsigned char *)data; double x; - if ((double_format == ieee_little_endian_format && !le) - || (double_format == ieee_big_endian_format && le)) { + if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) { char buf[8]; char *d = &buf[7]; int i; From 0a84d161566cda7732b0bed170b573c7e9b08622 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Mar 2026 04:50:24 +0300 Subject: [PATCH 6/9] +1 --- Objects/floatobject.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index beb8c1c0d25038..3eea4b66df148f 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1666,14 +1666,6 @@ float___getnewargs___impl(PyObject *self) return Py_BuildValue("(d)", ((PyFloatObject *)self)->ob_fval); } -/* this is for the benefit of the pack/unpack routines below */ -typedef enum _py_float_format_type float_format_type; -#define ieee_big_endian_format _py_float_format_ieee_big_endian -#define ieee_little_endian_format _py_float_format_ieee_little_endian - -#define float_format (_PyRuntime.float_state.float_format) -#define double_format (_PyRuntime.float_state.double_format) - /*[clinic input] @permit_long_docstring_body @@ -1697,8 +1689,6 @@ static PyObject * float___getformat___impl(PyTypeObject *type, const char *typestr) /*[clinic end generated code: output=2bfb987228cc9628 input=0ae1ba35d192f704]*/ { - float_format_type r; - if (strcmp(typestr, "double") != 0 || strcmp(typestr, "float") != 0) { PyErr_SetString(PyExc_ValueError, "__getformat__() argument 1 must be " From 06beb8bd43531075d4bfe01d5fec95f55ef04ac2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Mar 2026 04:50:54 +0300 Subject: [PATCH 7/9] address review: move news --- .../2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core_and_Builtins => Build}/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst (100%) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst b/Misc/NEWS.d/next/Build/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst similarity index 100% rename from Misc/NEWS.d/next/Core_and_Builtins/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst rename to Misc/NEWS.d/next/Build/2026-03-08-06-18-26.gh-issue-145633.Ogu-RF.rst From eeca436e09fed9b7acbf85bd2b1c08894e31f6f2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Mar 2026 05:08:59 +0300 Subject: [PATCH 8/9] +last remnants --- Include/internal/pycore_floatobject.h | 1 - Python/pylifecycle.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index d8b76a161320ec..62501cdaf44f07 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -12,7 +12,6 @@ extern "C" { /* runtime lifecycle */ -extern void _PyFloat_InitState(PyInterpreterState *); extern PyStatus _PyFloat_InitTypes(PyInterpreterState *); extern void _PyFloat_FiniType(PyInterpreterState *); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 711e7bc89b71c0..21d1e036d31540 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -706,8 +706,6 @@ pycore_init_global_objects(PyInterpreterState *interp) { PyStatus status; - _PyFloat_InitState(interp); - status = _PyUnicode_InitGlobalObjects(interp); if (_PyStatus_EXCEPTION(status)) { return status; From 650b666ff3fcce83f3b0acb26704f157cba9663d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 10 Mar 2026 05:13:14 +0300 Subject: [PATCH 9/9] amend __getformat__ --- Objects/floatobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 3eea4b66df148f..18871a4f3c51a9 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1689,7 +1689,7 @@ static PyObject * float___getformat___impl(PyTypeObject *type, const char *typestr) /*[clinic end generated code: output=2bfb987228cc9628 input=0ae1ba35d192f704]*/ { - if (strcmp(typestr, "double") != 0 || strcmp(typestr, "float") != 0) { + if (strcmp(typestr, "double") != 0 && strcmp(typestr, "float") != 0) { PyErr_SetString(PyExc_ValueError, "__getformat__() argument 1 must be " "'double' or 'float'");