From 9bbdde218005f552304d9954bb97e3f9185edded Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 23 Feb 2022 18:16:23 +0100 Subject: bpo-45412: Add _PY_SHORT_FLOAT_REPR macro (GH-31171) Remove the HAVE_PY_SET_53BIT_PRECISION macro (moved to the internal C API). * Move HAVE_PY_SET_53BIT_PRECISION macro to pycore_pymath.h. * Replace PY_NO_SHORT_FLOAT_REPR macro with _PY_SHORT_FLOAT_REPR macro which is always defined. gcc -Wundef emits a warning when using _PY_SHORT_FLOAT_REPR but the macro is not defined, if pycore_pymath.h include was forgotten. --- Doc/whatsnew/3.11.rst | 4 ++ Include/internal/pycore_dtoa.h | 9 ++- Include/internal/pycore_pymath.h | 65 +++++++++++++++++++--- Include/pyport.h | 55 ------------------ .../C API/2022-02-06-20-14-21.bpo-45412.XJVaGW.rst | 2 + Modules/cmathmodule.c | 15 ++--- Modules/mathmodule.c | 9 +-- Objects/floatobject.c | 16 +++--- Python/dtoa.c | 8 +-- Python/pystrtod.c | 14 ++--- Python/sysmodule.c | 3 +- 11 files changed, 104 insertions(+), 96 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2022-02-06-20-14-21.bpo-45412.XJVaGW.rst diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 32f021f..34642e3 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -1019,3 +1019,7 @@ Removed public C API by mistake, it must only be used by Python internally. Use the ``PyTypeObject.tp_members`` member instead. (Contributed by Victor Stinner in :issue:`40170`.) + +* Remove the ``HAVE_PY_SET_53BIT_PRECISION`` macro (moved to the internal C + API). + (Contributed by Victor Stinner in :issue:`45412`.) diff --git a/Include/internal/pycore_dtoa.h b/Include/internal/pycore_dtoa.h index 3faf8cf..c77cf6e 100644 --- a/Include/internal/pycore_dtoa.h +++ b/Include/internal/pycore_dtoa.h @@ -1,4 +1,3 @@ -#ifndef PY_NO_SHORT_FLOAT_REPR #ifdef __cplusplus extern "C" { #endif @@ -7,6 +6,11 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR + + +#if _PY_SHORT_FLOAT_REPR == 1 + /* These functions are used by modules compiled as C extension like math: they must be exported. */ @@ -17,7 +21,8 @@ PyAPI_FUNC(void) _Py_dg_freedtoa(char *s); PyAPI_FUNC(double) _Py_dg_stdnan(int sign); PyAPI_FUNC(double) _Py_dg_infinity(int sign); +#endif // _PY_SHORT_FLOAT_REPR == 1 + #ifdef __cplusplus } #endif -#endif /* !PY_NO_SHORT_FLOAT_REPR */ diff --git a/Include/internal/pycore_pymath.h b/Include/internal/pycore_pymath.h index 395b714..1f54b3d 100644 --- a/Include/internal/pycore_pymath.h +++ b/Include/internal/pycore_pymath.h @@ -85,19 +85,34 @@ static inline void _Py_ADJUST_ERANGE2(double x, double y) (_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type)) -//--- Implementation of the HAVE_PY_SET_53BIT_PRECISION macro ------------- -//--- defined in pyport.h ------------------------------------------------- +//--- HAVE_PY_SET_53BIT_PRECISION macro ------------------------------------ // -// Give appropriate definitions for the following three macros: +// The functions _Py_dg_strtod() and _Py_dg_dtoa() in Python/dtoa.c (which are +// required to support the short float repr introduced in Python 3.1) require +// that the floating-point unit that's being used for arithmetic operations on +// C doubles is set to use 53-bit precision. It also requires that the FPU +// rounding mode is round-half-to-even, but that's less often an issue. // -// _Py_SET_53BIT_PRECISION_HEADER : any variable declarations needed to -// use the two macros below. -// _Py_SET_53BIT_PRECISION_START : store original FPU settings, and -// set FPU to 53-bit precision/round-half-to-even -// _Py_SET_53BIT_PRECISION_END : restore original FPU settings +// If your FPU isn't already set to 53-bit precision/round-half-to-even, and +// you want to make use of _Py_dg_strtod() and _Py_dg_dtoa(), then you should: +// +// #define HAVE_PY_SET_53BIT_PRECISION 1 +// +// and also give appropriate definitions for the following three macros: +// +// * _Py_SET_53BIT_PRECISION_HEADER: any variable declarations needed to +// use the two macros below. +// * _Py_SET_53BIT_PRECISION_START: store original FPU settings, and +// set FPU to 53-bit precision/round-half-to-even +// * _Py_SET_53BIT_PRECISION_END: restore original FPU settings +// +// The macros are designed to be used within a single C function: see +// Python/pystrtod.c for an example of their use. + // Get and set x87 control word for gcc/x86 #ifdef HAVE_GCC_ASM_FOR_X87 +#define HAVE_PY_SET_53BIT_PRECISION 1 // Functions defined in Python/pymath.c extern unsigned short _Py_get_387controlword(void); @@ -124,6 +139,7 @@ extern void _Py_set_387controlword(unsigned short); // Get and set x87 control word for VisualStudio/x86. // x87 is not supported in 64-bit or ARM. #if defined(_MSC_VER) && !defined(_WIN64) && !defined(_M_ARM) +#define HAVE_PY_SET_53BIT_PRECISION 1 #include // __control87_2() @@ -150,7 +166,10 @@ extern void _Py_set_387controlword(unsigned short); } while (0) #endif + +// MC68881 #ifdef HAVE_GCC_ASM_FOR_MC68881 +#define HAVE_PY_SET_53BIT_PRECISION 1 #define _Py_SET_53BIT_PRECISION_HEADER \ unsigned int old_fpcr, new_fpcr #define _Py_SET_53BIT_PRECISION_START \ @@ -178,6 +197,36 @@ extern void _Py_set_387controlword(unsigned short); #endif +//--- _PY_SHORT_FLOAT_REPR macro ------------------------------------------- + +// If we can't guarantee 53-bit precision, don't use the code +// in Python/dtoa.c, but fall back to standard code. This +// means that repr of a float will be long (17 significant digits). +// +// Realistically, there are two things that could go wrong: +// +// (1) doubles aren't IEEE 754 doubles, or +// (2) we're on x86 with the rounding precision set to 64-bits +// (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) +# define _PY_SHORT_FLOAT_REPR 0 +#endif + +// Double rounding is symptomatic of use of extended precision on x86. +// If we're seeing double rounding, and we don't have any mechanism available +// for changing the FPU rounding precision, then don't use Python/dtoa.c. +#if defined(X87_DOUBLE_ROUNDING) && !defined(HAVE_PY_SET_53BIT_PRECISION) +# define _PY_SHORT_FLOAT_REPR 0 +#endif + +#ifndef _PY_SHORT_FLOAT_REPR +# define _PY_SHORT_FLOAT_REPR 1 +#endif + + #ifdef __cplusplus } #endif diff --git a/Include/pyport.h b/Include/pyport.h index d27b3dd..62ac098 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -312,61 +312,6 @@ extern "C" { #define Py_SAFE_DOWNCAST(VALUE, WIDE, NARROW) (NARROW)(VALUE) #endif -/* The functions _Py_dg_strtod and _Py_dg_dtoa in Python/dtoa.c (which are - * required to support the short float repr introduced in Python 3.1) require - * that the floating-point unit that's being used for arithmetic operations - * on C doubles is set to use 53-bit precision. It also requires that the - * FPU rounding mode is round-half-to-even, but that's less often an issue. - * - * If your FPU isn't already set to 53-bit precision/round-half-to-even, and - * you want to make use of _Py_dg_strtod and _Py_dg_dtoa, then you should - * - * #define HAVE_PY_SET_53BIT_PRECISION 1 - * - * The macros are designed to be used within a single C function: see - * Python/pystrtod.c for an example of their use. - */ - -// HAVE_PY_SET_53BIT_PRECISION macro must be kept in sync with pycore_pymath.h -#ifdef HAVE_GCC_ASM_FOR_X87 - // Get and set x87 control word for gcc/x86 -# define HAVE_PY_SET_53BIT_PRECISION 1 -#endif -#if defined(_MSC_VER) && !defined(_WIN64) && !defined(_M_ARM) - // Get and set x87 control word for VisualStudio/x86. - // x87 not supported in 64-bit or ARM. -# define HAVE_PY_SET_53BIT_PRECISION 1 -#endif -#ifdef HAVE_GCC_ASM_FOR_MC68881 -# define HAVE_PY_SET_53BIT_PRECISION 1 -#endif - - -/* If we can't guarantee 53-bit precision, don't use the code - in Python/dtoa.c, but fall back to standard code. This - means that repr of a float will be long (17 sig digits). - - Realistically, there are two things that could go wrong: - - (1) doubles aren't IEEE 754 doubles, or - (2) we're on x86 with the rounding precision set to 64-bits - (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) -# define PY_NO_SHORT_FLOAT_REPR -#endif - -/* double rounding is symptomatic of use of extended precision on x86. If - we're seeing double rounding, and we don't have any mechanism available for - changing the FPU rounding precision, then don't use Python/dtoa.c. */ -#if defined(X87_DOUBLE_ROUNDING) && !defined(HAVE_PY_SET_53BIT_PRECISION) -# define PY_NO_SHORT_FLOAT_REPR -#endif - /* Py_DEPRECATED(version) * Declare a variable, type, or function deprecated. diff --git a/Misc/NEWS.d/next/C API/2022-02-06-20-14-21.bpo-45412.XJVaGW.rst b/Misc/NEWS.d/next/C API/2022-02-06-20-14-21.bpo-45412.XJVaGW.rst new file mode 100644 index 0000000..5c0cde1 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-02-06-20-14-21.bpo-45412.XJVaGW.rst @@ -0,0 +1,2 @@ +Remove the ``HAVE_PY_SET_53BIT_PRECISION`` macro (moved to the internal C API). +Patch by Victor Stinner. diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index 281d393..c0c0c35 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -7,7 +7,8 @@ #endif #include "Python.h" -#include "pycore_dtoa.h" +#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR +#include "pycore_dtoa.h" // _Py_dg_stdnan() /* we need DBL_MAX, DBL_MIN, DBL_EPSILON, DBL_MANT_DIG and FLT_RADIX from float.h. We assume that FLT_RADIX is either 2 or 16. */ #include @@ -89,14 +90,14 @@ else { /* Constants cmath.inf, cmath.infj, cmath.nan, cmath.nanj. cmath.nan and cmath.nanj are defined only when either - PY_NO_SHORT_FLOAT_REPR is *not* defined (which should be + _PY_SHORT_FLOAT_REPR is 1 (which should be the most common situation on machines using an IEEE 754 representation), or Py_NAN is defined. */ static double m_inf(void) { -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 return _Py_dg_infinity(0); #else return Py_HUGE_VAL; @@ -112,12 +113,12 @@ c_infj(void) return r; } -#if !defined(PY_NO_SHORT_FLOAT_REPR) || defined(Py_NAN) +#if _PY_SHORT_FLOAT_REPR == 1 || defined(Py_NAN) static double m_nan(void) { -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 return _Py_dg_stdnan(0); #else return Py_NAN; @@ -1281,7 +1282,7 @@ cmath_exec(PyObject *mod) PyComplex_FromCComplex(c_infj())) < 0) { return -1; } -#if !defined(PY_NO_SHORT_FLOAT_REPR) || defined(Py_NAN) +#if _PY_SHORT_FLOAT_REPR == 1 || defined(Py_NAN) if (PyModule_AddObject(mod, "nan", PyFloat_FromDouble(m_nan())) < 0) { return -1; } @@ -1426,4 +1427,4 @@ PyMODINIT_FUNC PyInit_cmath(void) { return PyModuleDef_Init(&cmathmodule); -} \ No newline at end of file +} diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 721c9a6..24ae146 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -62,6 +62,7 @@ raised for division by zero and mod by zero. #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_dtoa.h" // _Py_dg_infinity() #include "pycore_long.h" // _PyLong_GetZero() +#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR /* For DBL_EPSILON in _math.h */ #include /* For _Py_log1p with workarounds for buggy handling of zeros. */ @@ -272,7 +273,7 @@ lanczos_sum(double x) static double m_inf(void) { -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 return _Py_dg_infinity(0); #else return Py_HUGE_VAL; @@ -282,12 +283,12 @@ m_inf(void) /* Constant nan value, generated in the same way as float('nan'). */ /* We don't currently assume that Py_NAN is defined everywhere. */ -#if !defined(PY_NO_SHORT_FLOAT_REPR) || defined(Py_NAN) +#if _PY_SHORT_FLOAT_REPR == 1 || defined(Py_NAN) static double m_nan(void) { -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 return _Py_dg_stdnan(0); #else return Py_NAN; @@ -3837,7 +3838,7 @@ math_exec(PyObject *module) if (PyModule_AddObject(module, "inf", PyFloat_FromDouble(m_inf())) < 0) { return -1; } -#if !defined(PY_NO_SHORT_FLOAT_REPR) || defined(Py_NAN) +#if _PY_SHORT_FLOAT_REPR == 1 || defined(Py_NAN) if (PyModule_AddObject(module, "nan", PyFloat_FromDouble(m_nan())) < 0) { return -1; } diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 79fbdab..64d4c3e 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -10,7 +10,7 @@ #include "pycore_interp.h" // _PyInterpreterState.float_state #include "pycore_long.h" // _PyLong_GetOne() #include "pycore_object.h" // _PyObject_Init() -#include "pycore_pymath.h" // _Py_ADJUST_ERANGE1() +#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_structseq.h" // _PyStructSequence_FiniType() @@ -932,7 +932,7 @@ float___ceil___impl(PyObject *self) ndigits <= 323). Returns a Python float, or sets a Python error and returns NULL on failure (OverflowError and memory errors are possible). */ -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 /* version of double_round that uses the correctly-rounded string<->double conversions from Python/dtoa.c */ @@ -989,7 +989,7 @@ double_round(double x, int ndigits) { return result; } -#else /* PY_NO_SHORT_FLOAT_REPR */ +#else // _PY_SHORT_FLOAT_REPR == 0 /* fallback version, to be used when correctly rounded binary<->decimal conversions aren't available */ @@ -1039,7 +1039,7 @@ double_round(double x, int ndigits) { return PyFloat_FromDouble(z); } -#endif /* PY_NO_SHORT_FLOAT_REPR */ +#endif // _PY_SHORT_FLOAT_REPR == 0 /* round a Python float v to the closest multiple of 10**-ndigits */ @@ -2479,7 +2479,7 @@ _PyFloat_Unpack2(const unsigned char *p, int le) f |= *p; if (e == 0x1f) { -#ifdef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 0 if (f == 0) { /* Infinity */ return sign ? -Py_HUGE_VAL : Py_HUGE_VAL; @@ -2494,9 +2494,9 @@ _PyFloat_Unpack2(const unsigned char *p, int le) "can't unpack IEEE 754 NaN " "on platform that does not support NaNs"); return -1; -#endif /* #ifdef Py_NAN */ +#endif // !defined(Py_NAN) } -#else +#else // _PY_SHORT_FLOAT_REPR == 1 if (f == 0) { /* Infinity */ return _Py_dg_infinity(sign); @@ -2505,7 +2505,7 @@ _PyFloat_Unpack2(const unsigned char *p, int le) /* NaN */ return _Py_dg_stdnan(sign); } -#endif /* #ifdef PY_NO_SHORT_FLOAT_REPR */ +#endif // _PY_SHORT_FLOAT_REPR == 1 } x = (double)f / 1024.0; diff --git a/Python/dtoa.c b/Python/dtoa.c index 6c44f68..733e70b 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -118,12 +118,12 @@ /* Linking of Python's #defines to Gay's #defines starts here. */ #include "Python.h" -#include "pycore_dtoa.h" +#include "pycore_dtoa.h" // _PY_SHORT_FLOAT_REPR #include // exit() -/* if PY_NO_SHORT_FLOAT_REPR is defined, then don't even try to compile +/* if _PY_SHORT_FLOAT_REPR == 0, then don't even try to compile the following code */ -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 #include "float.h" @@ -2857,4 +2857,4 @@ _Py_dg_dtoa(double dd, int mode, int ndigits, } #endif -#endif /* PY_NO_SHORT_FLOAT_REPR */ +#endif // _PY_SHORT_FLOAT_REPR == 1 diff --git a/Python/pystrtod.c b/Python/pystrtod.c index ab5814d..7469d62 100644 --- a/Python/pystrtod.c +++ b/Python/pystrtod.c @@ -1,8 +1,8 @@ /* -*- Mode: C; c-file-style: "python" -*- */ #include -#include "pycore_dtoa.h" -#include "pycore_pymath.h" // _Py_SET_53BIT_PRECISION_START +#include "pycore_dtoa.h" // _Py_dg_strtod() +#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR #include /* Case-insensitive string match used for nan and inf detection; t should be @@ -24,7 +24,7 @@ case_insensitive_match(const char *s, const char *t) the successfully parsed portion of the string. On failure, return -1.0 and set *endptr to point to the start of the string. */ -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 double _Py_parse_inf_or_nan(const char *p, char **endptr) @@ -127,7 +127,7 @@ _Py_parse_inf_or_nan(const char *p, char **endptr) * Return value: the #gdouble value. **/ -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 static double _PyOS_ascii_strtod(const char *nptr, char **endptr) @@ -441,7 +441,7 @@ _Py_string_to_number_with_underscores( return NULL; } -#ifdef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 0 /* Given a string that may have a decimal point in the current locale, change it back to a dot. Since the string cannot get @@ -942,7 +942,7 @@ char * PyOS_double_to_string(double val, return buf; } -#else +#else // _PY_SHORT_FLOAT_REPR == 1 /* _Py_dg_dtoa is available. */ @@ -1305,4 +1305,4 @@ char * PyOS_double_to_string(double val, flags & Py_DTSF_ALT, float_strings, type); } -#endif /* ifdef PY_NO_SHORT_FLOAT_REPR */ +#endif // _PY_SHORT_FLOAT_REPR == 1 diff --git a/Python/sysmodule.c b/Python/sysmodule.c index e23b879..342e48e 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -25,6 +25,7 @@ Data members: #include "pycore_pathconfig.h" // _PyPathConfig_ComputeSysPath0() #include "pycore_pyerrors.h" // _PyErr_Fetch() #include "pycore_pylifecycle.h" // _PyErr_WriteUnraisableDefaultHook() +#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_structseq.h" // _PyStructSequence_InitType() @@ -2837,7 +2838,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) #endif /* float repr style: 0.03 (short) vs 0.029999999999999999 (legacy) */ -#ifndef PY_NO_SHORT_FLOAT_REPR +#if _PY_SHORT_FLOAT_REPR == 1 SET_SYS_FROM_STRING("float_repr_style", "short"); #else SET_SYS_FROM_STRING("float_repr_style", "legacy"); -- cgit v0.12