Skip to content

Commit 7a3b035

Browse files
sebergmdickinson
andauthored
gh-104263: Rely on Py_NAN and introduce Py_INFINITY (GH-104202)
This PR removes `_Py_dg_stdnan` and `_Py_dg_infinity` in favour of using the standard `NAN` and `INFINITY` macros provided by C99. This change has the side-effect of fixing a bug on MIPS where the hard-coded value used by `_Py_dg_stdnan` gave a signalling NaN rather than a quiet NaN. --------- Co-authored-by: Mark Dickinson <[email protected]>
1 parent a7a2dbb commit 7a3b035

File tree

12 files changed

+44
-198
lines changed

12 files changed

+44
-198
lines changed

Include/internal/pycore_dtoa.h

-2
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ PyAPI_FUNC(double) _Py_dg_strtod(const char *str, char **ptr);
6464
PyAPI_FUNC(char *) _Py_dg_dtoa(double d, int mode, int ndigits,
6565
int *decpt, int *sign, char **rve);
6666
PyAPI_FUNC(void) _Py_dg_freedtoa(char *s);
67-
PyAPI_FUNC(double) _Py_dg_stdnan(int sign);
68-
PyAPI_FUNC(double) _Py_dg_infinity(int sign);
6967

7068
#endif // _PY_SHORT_FLOAT_REPR == 1
7169

Include/pymath.h

+11-14
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,24 @@
3939
// Return 1 if float or double arg is neither infinite nor NAN, else 0.
4040
#define Py_IS_FINITE(X) isfinite(X)
4141

42-
/* HUGE_VAL is supposed to expand to a positive double infinity. Python
43-
* uses Py_HUGE_VAL instead because some platforms are broken in this
44-
* respect. We used to embed code in pyport.h to try to worm around that,
45-
* but different platforms are broken in conflicting ways. If you're on
46-
* a platform where HUGE_VAL is defined incorrectly, fiddle your Python
47-
* config to #define Py_HUGE_VAL to something that works on your platform.
42+
// Py_INFINITY: Value that evaluates to a positive double infinity.
43+
#ifndef Py_INFINITY
44+
# define Py_INFINITY ((double)INFINITY)
45+
#endif
46+
47+
/* Py_HUGE_VAL should always be the same as Py_INFINITY. But historically
48+
* this was not reliable and Python did not require IEEE floats and C99
49+
* conformity. Prefer Py_INFINITY for new code.
4850
*/
4951
#ifndef Py_HUGE_VAL
5052
# define Py_HUGE_VAL HUGE_VAL
5153
#endif
5254

53-
// Py_NAN: Value that evaluates to a quiet Not-a-Number (NaN).
55+
/* Py_NAN: Value that evaluates to a quiet Not-a-Number (NaN). The sign is
56+
* undefined and normally not relevant, but e.g. fixed for float("nan").
57+
*/
5458
#if !defined(Py_NAN)
55-
# if _Py__has_builtin(__builtin_nan)
56-
// Built-in implementation of the ISO C99 function nan(): quiet NaN.
57-
# define Py_NAN (__builtin_nan(""))
58-
#else
59-
// Use C99 NAN constant: quiet Not-A-Number.
60-
// NAN is a float, Py_NAN is a double: cast to double.
6159
# define Py_NAN ((double)NAN)
62-
# endif
6360
#endif
6461

6562
#endif /* Py_PYMATH_H */

Lib/test/test_cmath.py

+5
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ def test_infinity_and_nan_constants(self):
166166
self.assertEqual(cmath.nan.imag, 0.0)
167167
self.assertEqual(cmath.nanj.real, 0.0)
168168
self.assertTrue(math.isnan(cmath.nanj.imag))
169+
# Also check that the sign of all of these is positive:
170+
self.assertEqual(math.copysign(1., cmath.nan.real), 1.)
171+
self.assertEqual(math.copysign(1., cmath.nan.imag), 1.)
172+
self.assertEqual(math.copysign(1., cmath.nanj.real), 1.)
173+
self.assertEqual(math.copysign(1., cmath.nanj.imag), 1.)
169174

170175
# Check consistency with reprs.
171176
self.assertEqual(repr(cmath.inf), "inf")

Lib/test/test_complex.py

+7
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,12 @@ class complex2(complex):
529529
self.assertFloatsAreIdentical(z.real, x)
530530
self.assertFloatsAreIdentical(z.imag, y)
531531

532+
def test_constructor_negative_nans_from_string(self):
533+
self.assertEqual(copysign(1., complex("-nan").real), -1.)
534+
self.assertEqual(copysign(1., complex("-nanj").imag), -1.)
535+
self.assertEqual(copysign(1., complex("-nan-nanj").real), -1.)
536+
self.assertEqual(copysign(1., complex("-nan-nanj").imag), -1.)
537+
532538
def test_underscores(self):
533539
# check underscores
534540
for lit in VALID_UNDERSCORE_LITERALS:
@@ -569,6 +575,7 @@ def test(v, expected, test_fn=self.assertEqual):
569575
test(complex(NAN, 1), "(nan+1j)")
570576
test(complex(1, NAN), "(1+nanj)")
571577
test(complex(NAN, NAN), "(nan+nanj)")
578+
test(complex(-NAN, -NAN), "(nan+nanj)")
572579

573580
test(complex(0, INF), "infj")
574581
test(complex(0, -INF), "-infj")

Lib/test/test_float.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1040,11 +1040,8 @@ def test_inf_signs(self):
10401040
self.assertEqual(copysign(1.0, float('inf')), 1.0)
10411041
self.assertEqual(copysign(1.0, float('-inf')), -1.0)
10421042

1043-
@unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
1044-
"applies only when using short float repr style")
10451043
def test_nan_signs(self):
1046-
# When using the dtoa.c code, the sign of float('nan') should
1047-
# be predictable.
1044+
# The sign of float('nan') should be predictable.
10481045
self.assertEqual(copysign(1.0, float('nan')), 1.0)
10491046
self.assertEqual(copysign(1.0, float('-nan')), -1.0)
10501047

Lib/test/test_math.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1881,11 +1881,11 @@ def testIsinf(self):
18811881
self.assertFalse(math.isinf(0.))
18821882
self.assertFalse(math.isinf(1.))
18831883

1884-
@requires_IEEE_754
18851884
def test_nan_constant(self):
1885+
# `math.nan` must be a quiet NaN with positive sign bit
18861886
self.assertTrue(math.isnan(math.nan))
1887+
self.assertEqual(math.copysign(1., math.nan), 1.)
18871888

1888-
@requires_IEEE_754
18891889
def test_inf_constant(self):
18901890
self.assertTrue(math.isinf(math.inf))
18911891
self.assertGreater(math.inf, 0.0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix ``float("nan")`` to produce a quiet NaN on platforms (like MIPS) where
2+
the meaning of the signalling / quiet bit is inverted from its usual
3+
meaning. Also introduce a new macro ``Py_INFINITY`` matching C99's
4+
``INFINITY``, and refactor internals to rely on C99's ``NAN`` and
5+
``INFINITY`` macros instead of hard-coding bit patterns for infinities and
6+
NaNs. Thanks Sebastian Berg.

Modules/cmathmodule.c

+6-55
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
#include "Python.h"
1010
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
11-
#include "pycore_dtoa.h" // _Py_dg_stdnan()
1211
/* we need DBL_MAX, DBL_MIN, DBL_EPSILON, DBL_MANT_DIG and FLT_RADIX from
1312
float.h. We assume that FLT_RADIX is either 2 or 16. */
1413
#include <float.h>
@@ -88,53 +87,6 @@ else {
8887
#endif
8988
#define CM_SCALE_DOWN (-(CM_SCALE_UP+1)/2)
9089

91-
/* Constants cmath.inf, cmath.infj, cmath.nan, cmath.nanj.
92-
cmath.nan and cmath.nanj are defined only when either
93-
_PY_SHORT_FLOAT_REPR is 1 (which should be
94-
the most common situation on machines using an IEEE 754
95-
representation), or Py_NAN is defined. */
96-
97-
static double
98-
m_inf(void)
99-
{
100-
#if _PY_SHORT_FLOAT_REPR == 1
101-
return _Py_dg_infinity(0);
102-
#else
103-
return Py_HUGE_VAL;
104-
#endif
105-
}
106-
107-
static Py_complex
108-
c_infj(void)
109-
{
110-
Py_complex r;
111-
r.real = 0.0;
112-
r.imag = m_inf();
113-
return r;
114-
}
115-
116-
#if _PY_SHORT_FLOAT_REPR == 1
117-
118-
static double
119-
m_nan(void)
120-
{
121-
#if _PY_SHORT_FLOAT_REPR == 1
122-
return _Py_dg_stdnan(0);
123-
#else
124-
return Py_NAN;
125-
#endif
126-
}
127-
128-
static Py_complex
129-
c_nanj(void)
130-
{
131-
Py_complex r;
132-
r.real = 0.0;
133-
r.imag = m_nan();
134-
return r;
135-
}
136-
137-
#endif
13890

13991
/* forward declarations */
14092
static Py_complex cmath_asinh_impl(PyObject *, Py_complex);
@@ -1274,23 +1226,22 @@ cmath_exec(PyObject *mod)
12741226
if (PyModule_AddObject(mod, "tau", PyFloat_FromDouble(Py_MATH_TAU)) < 0) {
12751227
return -1;
12761228
}
1277-
if (PyModule_AddObject(mod, "inf", PyFloat_FromDouble(m_inf())) < 0) {
1229+
if (PyModule_AddObject(mod, "inf", PyFloat_FromDouble(Py_INFINITY)) < 0) {
12781230
return -1;
12791231
}
12801232

1233+
Py_complex infj = {0.0, Py_INFINITY};
12811234
if (PyModule_AddObject(mod, "infj",
1282-
PyComplex_FromCComplex(c_infj())) < 0) {
1235+
PyComplex_FromCComplex(infj)) < 0) {
12831236
return -1;
12841237
}
1285-
#if _PY_SHORT_FLOAT_REPR == 1
1286-
if (PyModule_AddObject(mod, "nan", PyFloat_FromDouble(m_nan())) < 0) {
1238+
if (PyModule_AddObject(mod, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) {
12871239
return -1;
12881240
}
1289-
if (PyModule_AddObject(mod, "nanj",
1290-
PyComplex_FromCComplex(c_nanj())) < 0) {
1241+
Py_complex nanj = {0.0, fabs(Py_NAN)};
1242+
if (PyModule_AddObject(mod, "nanj", PyComplex_FromCComplex(nanj)) < 0) {
12911243
return -1;
12921244
}
1293-
#endif
12941245

12951246
/* initialize special value tables */
12961247

Modules/mathmodule.c

+4-35
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ raised for division by zero and mod by zero.
5959
#include "Python.h"
6060
#include "pycore_bitutils.h" // _Py_bit_length()
6161
#include "pycore_call.h" // _PyObject_CallNoArgs()
62-
#include "pycore_dtoa.h" // _Py_dg_infinity()
6362
#include "pycore_long.h" // _PyLong_GetZero()
6463
#include "pycore_moduleobject.h" // _PyModule_GetState()
6564
#include "pycore_object.h" // _PyObject_LookupSpecial()
@@ -389,34 +388,6 @@ lanczos_sum(double x)
389388
return num/den;
390389
}
391390

392-
/* Constant for +infinity, generated in the same way as float('inf'). */
393-
394-
static double
395-
m_inf(void)
396-
{
397-
#if _PY_SHORT_FLOAT_REPR == 1
398-
return _Py_dg_infinity(0);
399-
#else
400-
return Py_HUGE_VAL;
401-
#endif
402-
}
403-
404-
/* Constant nan value, generated in the same way as float('nan'). */
405-
/* We don't currently assume that Py_NAN is defined everywhere. */
406-
407-
#if _PY_SHORT_FLOAT_REPR == 1
408-
409-
static double
410-
m_nan(void)
411-
{
412-
#if _PY_SHORT_FLOAT_REPR == 1
413-
return _Py_dg_stdnan(0);
414-
#else
415-
return Py_NAN;
416-
#endif
417-
}
418-
419-
#endif
420391

421392
static double
422393
m_tgamma(double x)
@@ -435,7 +406,7 @@ m_tgamma(double x)
435406
if (x == 0.0) {
436407
errno = EDOM;
437408
/* tgamma(+-0.0) = +-inf, divide-by-zero */
438-
return copysign(Py_HUGE_VAL, x);
409+
return copysign(Py_INFINITY, x);
439410
}
440411

441412
/* integer arguments */
@@ -3938,7 +3909,7 @@ math_ulp_impl(PyObject *module, double x)
39383909
if (Py_IS_INFINITY(x)) {
39393910
return x;
39403911
}
3941-
double inf = m_inf();
3912+
double inf = Py_INFINITY;
39423913
double x2 = nextafter(x, inf);
39433914
if (Py_IS_INFINITY(x2)) {
39443915
/* special case: x is the largest positive representable float */
@@ -3975,14 +3946,12 @@ math_exec(PyObject *module)
39753946
if (PyModule_AddObject(module, "tau", PyFloat_FromDouble(Py_MATH_TAU)) < 0) {
39763947
return -1;
39773948
}
3978-
if (PyModule_AddObject(module, "inf", PyFloat_FromDouble(m_inf())) < 0) {
3949+
if (PyModule_AddObject(module, "inf", PyFloat_FromDouble(Py_INFINITY)) < 0) {
39793950
return -1;
39803951
}
3981-
#if _PY_SHORT_FLOAT_REPR == 1
3982-
if (PyModule_AddObject(module, "nan", PyFloat_FromDouble(m_nan())) < 0) {
3952+
if (PyModule_AddObject(module, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) {
39833953
return -1;
39843954
}
3985-
#endif
39863955
return 0;
39873956
}
39883957

Objects/floatobject.c

+1-12
Original file line numberDiff line numberDiff line change
@@ -2424,25 +2424,14 @@ PyFloat_Unpack2(const char *data, int le)
24242424
f |= *p;
24252425

24262426
if (e == 0x1f) {
2427-
#if _PY_SHORT_FLOAT_REPR == 0
24282427
if (f == 0) {
24292428
/* Infinity */
24302429
return sign ? -Py_HUGE_VAL : Py_HUGE_VAL;
24312430
}
24322431
else {
24332432
/* NaN */
2434-
return sign ? -Py_NAN : Py_NAN;
2433+
return sign ? -fabs(Py_NAN) : fabs(Py_NAN);
24352434
}
2436-
#else // _PY_SHORT_FLOAT_REPR == 1
2437-
if (f == 0) {
2438-
/* Infinity */
2439-
return _Py_dg_infinity(sign);
2440-
}
2441-
else {
2442-
/* NaN */
2443-
return _Py_dg_stdnan(sign);
2444-
}
2445-
#endif // _PY_SHORT_FLOAT_REPR == 1
24462435
}
24472436

24482437
x = (double)f / 1024.0;

Python/dtoa.c

-34
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,6 @@ typedef union { double d; ULong L[2]; } U;
273273
#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1))
274274
#define Big1 0xffffffff
275275

276-
/* Standard NaN used by _Py_dg_stdnan. */
277-
278-
#define NAN_WORD0 0x7ff80000
279-
#define NAN_WORD1 0
280-
281276
/* Bits of the representation of positive infinity. */
282277

283278
#define POSINF_WORD0 0x7ff00000
@@ -1399,35 +1394,6 @@ bigcomp(U *rv, const char *s0, BCinfo *bc)
13991394
return 0;
14001395
}
14011396

1402-
/* Return a 'standard' NaN value.
1403-
1404-
There are exactly two quiet NaNs that don't arise by 'quieting' signaling
1405-
NaNs (see IEEE 754-2008, section 6.2.1). If sign == 0, return the one whose
1406-
sign bit is cleared. Otherwise, return the one whose sign bit is set.
1407-
*/
1408-
1409-
double
1410-
_Py_dg_stdnan(int sign)
1411-
{
1412-
U rv;
1413-
word0(&rv) = NAN_WORD0;
1414-
word1(&rv) = NAN_WORD1;
1415-
if (sign)
1416-
word0(&rv) |= Sign_bit;
1417-
return dval(&rv);
1418-
}
1419-
1420-
/* Return positive or negative infinity, according to the given sign (0 for
1421-
* positive infinity, 1 for negative infinity). */
1422-
1423-
double
1424-
_Py_dg_infinity(int sign)
1425-
{
1426-
U rv;
1427-
word0(&rv) = POSINF_WORD0;
1428-
word1(&rv) = POSINF_WORD1;
1429-
return sign ? -dval(&rv) : dval(&rv);
1430-
}
14311397

14321398
double
14331399
_Py_dg_strtod(const char *s00, char **se)

0 commit comments

Comments
 (0)