From 5f00a02aab8f0a60e2ff31abe8113962d6a85851 Mon Sep 17 00:00:00 2001 From: Yusuke Hayashi Date: Wed, 29 Jul 2026 12:34:36 +0900 Subject: [PATCH 1/2] gh-70450: Fix MSVC /fp:strict builds Replace floating-point expressions that MSVC rejects in static initializers under strict semantics. Initialize cmath NaN table entries once because NAN is not a constant expression in MSVC C mode. --- ...07-29-12-10-00.gh-issue-70450.fpstrict.rst | 1 + Modules/cmathmodule.c | 48 +++++++++++++++++-- Modules/mathmodule.c | 4 +- Python/dtoa.c | 2 +- 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2026-07-29-12-10-00.gh-issue-70450.fpstrict.rst diff --git a/Misc/NEWS.d/next/Build/2026-07-29-12-10-00.gh-issue-70450.fpstrict.rst b/Misc/NEWS.d/next/Build/2026-07-29-12-10-00.gh-issue-70450.fpstrict.rst new file mode 100644 index 000000000000000..b6b80b035191be1 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-07-29-12-10-00.gh-issue-70450.fpstrict.rst @@ -0,0 +1 @@ +Fix the Windows build when using the MSVC ``/fp:strict`` option. diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index f6e1475b00ecfbb..db0bbe173623218 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -8,6 +8,7 @@ #include "Python.h" #include "pycore_complexobject.h" // _Py_c_neg() +#include "pycore_lock.h" // _PyOnceFlag_CallOnce() #include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR /* 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. */ @@ -147,11 +148,11 @@ special_type(double d) } #define P Py_MATH_PI -#define P14 0.25*Py_MATH_PI -#define P12 0.5*Py_MATH_PI -#define P34 0.75*Py_MATH_PI +#define P14 0.78539816339744830962 +#define P12 1.57079632679489661923 +#define P34 2.35619449019234492885 #define INF INFINITY -#define N Py_NAN +#define N 9.5426319407711027e33 /* finite placeholder for NaN */ #define U -9.5426319407711027e33 /* unlikely value, used as placeholder */ /* First, the C functions that do the real work. Each of the c_* @@ -1230,6 +1231,41 @@ PyDoc_STRVAR(module_doc, "This module provides access to mathematical functions for complex\n" "numbers."); +static _PyOnceFlag special_values_init_once = {0}; + +static int +init_special_values(void *Py_UNUSED(arg)) +{ + Py_complex *tables[] = { + acos_special_values[0], + acosh_special_values[0], + asinh_special_values[0], + atanh_special_values[0], + cosh_special_values[0], + exp_special_values[0], + log_special_values[0], + sinh_special_values[0], + sqrt_special_values[0], + tanh_special_values[0], + rect_special_values[0], + }; + + /* MSVC with /fp:strict does not treat NAN as a constant expression. + Replace its finite placeholders once all static initialization is + complete. */ + for (size_t i = 0; i < Py_ARRAY_LENGTH(tables); i++) { + for (size_t j = 0; j < 7 * 7; j++) { + if (tables[i][j].real == N) { + tables[i][j].real = Py_NAN; + } + if (tables[i][j].imag == N) { + tables[i][j].imag = Py_NAN; + } + } + } + return 0; +} + static PyMethodDef cmath_methods[] = { CMATH_ACOS_METHODDEF CMATH_ACOSH_METHODDEF @@ -1260,6 +1296,10 @@ static PyMethodDef cmath_methods[] = { static int cmath_exec(PyObject *mod) { + /* init cannot fail */ + (void)_PyOnceFlag_CallOnce( + &special_values_init_once, init_special_values, NULL); + if (PyModule_Add(mod, "pi", PyFloat_FromDouble(Py_MATH_PI)) < 0) { return -1; } diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index eaa1850b8aee1ac..73cc9eebb42b8b8 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2610,8 +2610,8 @@ math_pow_impl(PyObject *module, double x, double y) } -static const double degToRad = Py_MATH_PI / 180.0; -static const double radToDeg = 180.0 / Py_MATH_PI; +static const double degToRad = 0.017453292519943295769236907684886; +static const double radToDeg = 57.295779513082320876798154814105; /*[clinic input] math.degrees diff --git a/Python/dtoa.c b/Python/dtoa.c index 89fadd33391cb42..b2834b3bdf298c6 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -1108,7 +1108,7 @@ tens[] = { static const double bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; static const double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, - 9007199254740992.*9007199254740992.e-256 + 8.112963841460668e-225 /* = 2^106 * 1e-256 */ }; /* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ From 941b1bb0d653e304f83db714dd0e1ffcf24374da Mon Sep 17 00:00:00 2001 From: yusuke8h Date: Wed, 29 Jul 2026 14:07:19 +0900 Subject: [PATCH 2/2] gh-70450: Avoid floating-point exceptions during imports --- Lib/test/test_cmath.py | 14 +++++ Lib/test/test_math.py | 2 + Modules/cmathmodule.c | 131 ++++++++++++++++++----------------------- Modules/mathmodule.c | 6 +- Python/dtoa.c | 4 +- 5 files changed, 82 insertions(+), 75 deletions(-) diff --git a/Lib/test/test_cmath.py b/Lib/test/test_cmath.py index a986fd6b892bd27..293de0c98d0f35b 100644 --- a/Lib/test/test_cmath.py +++ b/Lib/test/test_cmath.py @@ -124,6 +124,20 @@ def test_constants(self): self.assertAlmostEqual(cmath.e, e_expected, places=9, msg="cmath.e is {}; should be {}".format(cmath.e, e_expected)) + def test_precomputed_constants(self): + self.assertEqual( + cmath.acos(complex(math.inf, math.inf)).real, + math.pi / 4, + ) + self.assertEqual( + cmath.acos(complex(0.0, math.inf)).real, + math.pi / 2, + ) + self.assertEqual( + cmath.acos(complex(-math.inf, math.inf)).real, + math.pi * 3 / 4, + ) + def test_infinity_and_nan_constants(self): self.assertEqual(cmath.inf.real, math.inf) self.assertEqual(cmath.inf.imag, 0.0) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index cc37697189af3e5..deb406ad2ee0352 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -479,6 +479,7 @@ def testCosh(self): def testDegrees(self): self.assertRaises(TypeError, math.degrees) + self.assertEqual(math.degrees(1.0), 180.0 / math.pi) self.ftest('degrees(pi)', math.degrees(math.pi), 180.0) self.ftest('degrees(pi/2)', math.degrees(math.pi/2), 90.0) self.ftest('degrees(-pi/4)', math.degrees(-math.pi/4), -45.0) @@ -1748,6 +1749,7 @@ def testPow(self): def testRadians(self): self.assertRaises(TypeError, math.radians) + self.assertEqual(math.radians(1.0), math.pi / 180.0) self.ftest('radians(180)', math.radians(180), math.pi) self.ftest('radians(90)', math.radians(90), math.pi/2) self.ftest('radians(-45)', math.radians(-45), -math.pi/4) diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index db0bbe173623218..30ce7799daa6c58 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -8,7 +8,6 @@ #include "Python.h" #include "pycore_complexobject.h" // _Py_c_neg() -#include "pycore_lock.h" // _PyOnceFlag_CallOnce() #include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR /* 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. */ @@ -140,21 +139,40 @@ special_type(double d) return ST_NINF; } +#define P Py_MATH_PI +/* Precomputed for static initialization with MSVC /fp:strict. */ +#define P14 0.78539816339744830962 /* P/4: 0x3fe921fb54442d18 */ +#define P12 1.57079632679489661923 /* P/2: 0x3ff921fb54442d18 */ +#define P34 2.35619449019234492885 /* P*3/4: 0x4002d97c7f3321d2 */ +#define INF INFINITY +/* MSVC /fp:strict does not accept NAN in static initializers. This finite + value does not otherwise occur in the tables and is decoded when read. */ +#define N 9.5426319407711027e33 +#define U -9.5426319407711027e33 /* unlikely value, used as placeholder */ + +static Py_complex +special_value(const Py_complex table[7][7], + enum special_types real_type, + enum special_types imag_type) +{ + Py_complex value = table[real_type][imag_type]; + if (value.real == N) { + value.real = fabs(nan("")); + } + if (value.imag == N) { + value.imag = fabs(nan("")); + } + return value; +} + #define SPECIAL_VALUE(z, table) \ if (!isfinite((z).real) || !isfinite((z).imag)) { \ errno = 0; \ - return table[special_type((z).real)] \ - [special_type((z).imag)]; \ + return special_value( \ + table, special_type((z).real), \ + special_type((z).imag)); \ } -#define P Py_MATH_PI -#define P14 0.78539816339744830962 -#define P12 1.57079632679489661923 -#define P34 2.35619449019234492885 -#define INF INFINITY -#define N 9.5426319407711027e33 /* finite placeholder for NaN */ -#define U -9.5426319407711027e33 /* unlikely value, used as placeholder */ - /* First, the C functions that do the real work. Each of the c_* functions computes and returns the C99 Annex G recommended result and also sets errno as follows: errno = 0 if no floating-point @@ -164,7 +182,7 @@ special_type(double d) raised. */ -static Py_complex acos_special_values[7][7] = { +static const Py_complex acos_special_values[7][7] = { { {P34,INF}, {P,INF}, {P,INF}, {P,-INF}, {P,-INF}, {P34,-INF}, {N,INF} }, { {P12,INF}, {U,U}, {U,U}, {U,U}, {U,U}, {P12,-INF}, {N,N} }, { {P12,INF}, {U,U}, {P12,0.}, {P12,-0.}, {U,U}, {P12,-INF}, {P12,N} }, @@ -210,7 +228,7 @@ cmath_acos_impl(PyObject *module, Py_complex z) } -static Py_complex acosh_special_values[7][7] = { +static const Py_complex acosh_special_values[7][7] = { { {INF,-P34}, {INF,-P}, {INF,-P}, {INF,P}, {INF,P}, {INF,P34}, {INF,N} }, { {INF,-P12}, {U,U}, {U,U}, {U,U}, {U,U}, {INF,P12}, {N,N} }, { {INF,-P12}, {U,U}, {0.,-P12}, {0.,P12}, {U,U}, {INF,P12}, {N,P12} }, @@ -273,7 +291,7 @@ cmath_asin_impl(PyObject *module, Py_complex z) } -static Py_complex asinh_special_values[7][7] = { +static const Py_complex asinh_special_values[7][7] = { { {-INF,-P14}, {-INF,-0.}, {-INF,-0.}, {-INF,0.}, {-INF,0.}, {-INF,P14}, {-INF,N} }, { {-INF,-P12}, {U,U}, {U,U}, {U,U}, {U,U}, {-INF,P12}, {N,N} }, { {-INF,-P12}, {U,U}, {-0.,-0.}, {-0.,0.}, {U,U}, {-INF,P12}, {N,N} }, @@ -342,7 +360,7 @@ cmath_atan_impl(PyObject *module, Py_complex z) } -static Py_complex atanh_special_values[7][7] = { +static const Py_complex atanh_special_values[7][7] = { { {-0.,-P12}, {-0.,-P12}, {-0.,-P12}, {-0.,P12}, {-0.,P12}, {-0.,P12}, {-0.,N} }, { {-0.,-P12}, {U,U}, {U,U}, {U,U}, {U,U}, {-0.,P12}, {N,N} }, { {-0.,-P12}, {U,U}, {-0.,-0.}, {-0.,0.}, {U,U}, {-0.,P12}, {-0.,N} }, @@ -423,7 +441,7 @@ cmath_cos_impl(PyObject *module, Py_complex z) /* cosh(infinity + i*y) needs to be dealt with specially */ -static Py_complex cosh_special_values[7][7] = { +static const Py_complex cosh_special_values[7][7] = { { {INF,N}, {U,U}, {INF,0.}, {INF,-0.}, {U,U}, {INF,N}, {INF,N} }, { {N,N}, {U,U}, {U,U}, {U,U}, {U,U}, {N,N}, {N,N} }, { {N,0.}, {U,U}, {1.,0.}, {1.,-0.}, {U,U}, {N,0.}, {N,0.} }, @@ -460,8 +478,9 @@ cmath_cosh_impl(PyObject *module, Py_complex z) } } else { - r = cosh_special_values[special_type(z.real)] - [special_type(z.imag)]; + r = special_value(cosh_special_values, + special_type(z.real), + special_type(z.imag)); } /* need to set errno = EDOM if y is +/- infinity and x is not a NaN */ @@ -493,7 +512,7 @@ cmath_cosh_impl(PyObject *module, Py_complex z) /* exp(infinity + i*y) and exp(-infinity + i*y) need special treatment for finite y */ -static Py_complex exp_special_values[7][7] = { +static const Py_complex exp_special_values[7][7] = { { {0.,0.}, {U,U}, {0.,-0.}, {0.,0.}, {U,U}, {0.,0.}, {0.,0.} }, { {N,N}, {U,U}, {U,U}, {U,U}, {U,U}, {N,N}, {N,N} }, { {N,N}, {U,U}, {1.,-0.}, {1.,0.}, {U,U}, {N,N}, {N,N} }, @@ -529,8 +548,9 @@ cmath_exp_impl(PyObject *module, Py_complex z) } } else { - r = exp_special_values[special_type(z.real)] - [special_type(z.imag)]; + r = special_value(exp_special_values, + special_type(z.real), + special_type(z.imag)); } /* need to set errno = EDOM if y is +/- infinity and x is not a NaN and not -infinity */ @@ -560,7 +580,7 @@ cmath_exp_impl(PyObject *module, Py_complex z) return r; } -static Py_complex log_special_values[7][7] = { +static const Py_complex log_special_values[7][7] = { { {INF,-P34}, {INF,-P}, {INF,-P}, {INF,P}, {INF,P}, {INF,P34}, {INF,N} }, { {INF,-P12}, {U,U}, {U,U}, {U,U}, {U,U}, {INF,P12}, {N,N} }, { {INF,-P12}, {U,U}, {-INF,-P}, {-INF,P}, {U,U}, {INF,P12}, {N,N} }, @@ -684,7 +704,7 @@ cmath_sin_impl(PyObject *module, Py_complex z) /* sinh(infinity + i*y) needs to be dealt with specially */ -static Py_complex sinh_special_values[7][7] = { +static const Py_complex sinh_special_values[7][7] = { { {INF,N}, {U,U}, {-INF,-0.}, {-INF,0.}, {U,U}, {INF,N}, {INF,N} }, { {N,N}, {U,U}, {U,U}, {U,U}, {U,U}, {N,N}, {N,N} }, { {0.,N}, {U,U}, {-0.,-0.}, {-0.,0.}, {U,U}, {0.,N}, {0.,N} }, @@ -722,8 +742,9 @@ cmath_sinh_impl(PyObject *module, Py_complex z) } } else { - r = sinh_special_values[special_type(z.real)] - [special_type(z.imag)]; + r = special_value(sinh_special_values, + special_type(z.real), + special_type(z.imag)); } /* need to set errno = EDOM if y is +/- infinity and x is not a NaN */ @@ -751,7 +772,7 @@ cmath_sinh_impl(PyObject *module, Py_complex z) } -static Py_complex sqrt_special_values[7][7] = { +static const Py_complex sqrt_special_values[7][7] = { { {INF,-INF}, {0.,-INF}, {0.,-INF}, {0.,INF}, {0.,INF}, {INF,INF}, {N,INF} }, { {INF,-INF}, {U,U}, {U,U}, {U,U}, {U,U}, {INF,INF}, {N,N} }, { {INF,-INF}, {U,U}, {0.,-0.}, {0.,0.}, {U,U}, {INF,INF}, {N,N} }, @@ -858,7 +879,7 @@ cmath_tan_impl(PyObject *module, Py_complex z) /* tanh(infinity + i*y) needs to be dealt with specially */ -static Py_complex tanh_special_values[7][7] = { +static const Py_complex tanh_special_values[7][7] = { { {-1.,0.}, {U,U}, {-1.,-0.}, {-1.,0.}, {U,U}, {-1.,0.}, {-1.,0.} }, { {N,N}, {U,U}, {U,U}, {U,U}, {U,U}, {N,N}, {N,N} }, { {-0.0,N}, {U,U}, {-0.,-0.}, {-0.,0.}, {U,U}, {-0.0,N}, {-0.,N} }, @@ -910,8 +931,9 @@ cmath_tanh_impl(PyObject *module, Py_complex z) } } else { - r = tanh_special_values[special_type(z.real)] - [special_type(z.imag)]; + r = special_value(tanh_special_values, + special_type(z.real), + special_type(z.imag)); } /* need to set errno = EDOM if z.imag is +/-infinity and z.real is finite */ @@ -1050,7 +1072,7 @@ cmath_polar_impl(PyObject *module, Py_complex z) */ -static Py_complex rect_special_values[7][7] = { +static const Py_complex rect_special_values[7][7] = { { {INF,N}, {U,U}, {-INF,0.}, {-INF,-0.}, {U,U}, {INF,N}, {INF,N} }, { {N,N}, {U,U}, {U,U}, {U,U}, {U,U}, {N,N}, {N,N} }, { {0.,0.}, {U,U}, {-0.,0.}, {-0.,-0.}, {U,U}, {0.,0.}, {0.,0.} }, @@ -1094,8 +1116,9 @@ cmath_rect_impl(PyObject *module, double r, double phi) } } else { - z = rect_special_values[special_type(r)] - [special_type(phi)]; + z = special_value(rect_special_values, + special_type(r), + special_type(phi)); } /* need to set errno = EDOM if r is a nonzero number and phi is infinite */ @@ -1231,41 +1254,6 @@ PyDoc_STRVAR(module_doc, "This module provides access to mathematical functions for complex\n" "numbers."); -static _PyOnceFlag special_values_init_once = {0}; - -static int -init_special_values(void *Py_UNUSED(arg)) -{ - Py_complex *tables[] = { - acos_special_values[0], - acosh_special_values[0], - asinh_special_values[0], - atanh_special_values[0], - cosh_special_values[0], - exp_special_values[0], - log_special_values[0], - sinh_special_values[0], - sqrt_special_values[0], - tanh_special_values[0], - rect_special_values[0], - }; - - /* MSVC with /fp:strict does not treat NAN as a constant expression. - Replace its finite placeholders once all static initialization is - complete. */ - for (size_t i = 0; i < Py_ARRAY_LENGTH(tables); i++) { - for (size_t j = 0; j < 7 * 7; j++) { - if (tables[i][j].real == N) { - tables[i][j].real = Py_NAN; - } - if (tables[i][j].imag == N) { - tables[i][j].imag = Py_NAN; - } - } - } - return 0; -} - static PyMethodDef cmath_methods[] = { CMATH_ACOS_METHODDEF CMATH_ACOSH_METHODDEF @@ -1296,10 +1284,6 @@ static PyMethodDef cmath_methods[] = { static int cmath_exec(PyObject *mod) { - /* init cannot fail */ - (void)_PyOnceFlag_CallOnce( - &special_values_init_once, init_special_values, NULL); - if (PyModule_Add(mod, "pi", PyFloat_FromDouble(Py_MATH_PI)) < 0) { return -1; } @@ -1318,10 +1302,11 @@ cmath_exec(PyObject *mod) if (PyModule_Add(mod, "infj", PyComplex_FromCComplex(infj)) < 0) { return -1; } - if (PyModule_Add(mod, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) { + double nan_value = fabs(nan("")); + if (PyModule_Add(mod, "nan", PyFloat_FromDouble(nan_value)) < 0) { return -1; } - Py_complex nanj = {0.0, fabs(Py_NAN)}; + Py_complex nanj = {0.0, nan_value}; if (PyModule_Add(mod, "nanj", PyComplex_FromCComplex(nanj)) < 0) { return -1; } diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 73cc9eebb42b8b8..4a982485b51da7a 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2610,7 +2610,10 @@ math_pow_impl(PyObject *module, double x, double y) } +/* Precomputed for static initialization with MSVC /fp:strict. */ +/* Py_MATH_PI / 180: binary64 0x3f91df46a2529d39 */ static const double degToRad = 0.017453292519943295769236907684886; +/* 180 / Py_MATH_PI: binary64 0x404ca5dc1a63c1f8 */ static const double radToDeg = 57.295779513082320876798154814105; /*[clinic input] @@ -3190,7 +3193,8 @@ math_exec(PyObject *module) if (PyModule_Add(module, "inf", PyFloat_FromDouble(INFINITY)) < 0) { return -1; } - if (PyModule_Add(module, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) { + if (PyModule_Add(module, "nan", + PyFloat_FromDouble(fabs(nan("")))) < 0) { return -1; } diff --git a/Python/dtoa.c b/Python/dtoa.c index b2834b3bdf298c6..96ea00248509abf 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -1109,7 +1109,9 @@ static const double bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; static const double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, 8.112963841460668e-225 - /* = 2^106 * 1e-256 */ + /* 2^106 * 1e-256, precomputed for + MSVC /fp:strict; binary64 + 0x1168062864ac6f43 */ }; /* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ /* flag unnecessarily. It leads to a song and dance at the end of strtod. */