Skip to content

bpo-45412: Add _PY_SHORT_FLOAT_REPR macro #31171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -989,3 +989,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`.)
9 changes: 7 additions & 2 deletions Include/internal/pycore_dtoa.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#ifndef PY_NO_SHORT_FLOAT_REPR
#ifdef __cplusplus
extern "C" {
#endif
Expand All @@ -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. */

Expand All @@ -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 */
65 changes: 57 additions & 8 deletions Include/internal/pycore_pymath.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 <float.h> // __control87_2()

Expand All @@ -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 \
Expand Down Expand Up @@ -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
Expand Down
55 changes: 0 additions & 55 deletions Include/pyport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove the ``HAVE_PY_SET_53BIT_PRECISION`` macro (moved to the internal C API).
Patch by Victor Stinner.
15 changes: 8 additions & 7 deletions Modules/cmathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <float.h>
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1426,4 +1427,4 @@ PyMODINIT_FUNC
PyInit_cmath(void)
{
return PyModuleDef_Init(&cmathmodule);
}
}
9 changes: 5 additions & 4 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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 <float.h>
/* For _Py_log1p with workarounds for buggy handling of zeros. */
Expand Down Expand Up @@ -271,7 +272,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;
Expand All @@ -281,12 +282,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;
Expand Down Expand Up @@ -3836,7 +3837,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;
}
Expand Down
16 changes: 8 additions & 8 deletions Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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 */

Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */

Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions Python/dtoa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdlib.h> // 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"

Expand Down Expand Up @@ -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
Loading