Skip to content
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

gh-104263: Rely on Py_NAN and introduce Py_INFINITY #104202

Merged
merged 10 commits into from
May 10, 2023
2 changes: 0 additions & 2 deletions Include/internal/pycore_dtoa.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ PyAPI_FUNC(double) _Py_dg_strtod(const char *str, char **ptr);
PyAPI_FUNC(char *) _Py_dg_dtoa(double d, int mode, int ndigits,
int *decpt, int *sign, char **rve);
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

Expand Down
25 changes: 11 additions & 14 deletions Include/pymath.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,24 @@
// Return 1 if float or double arg is neither infinite nor NAN, else 0.
#define Py_IS_FINITE(X) isfinite(X)

/* HUGE_VAL is supposed to expand to a positive double infinity. Python
* uses Py_HUGE_VAL instead because some platforms are broken in this
* respect. We used to embed code in pyport.h to try to worm around that,
* but different platforms are broken in conflicting ways. If you're on
* a platform where HUGE_VAL is defined incorrectly, fiddle your Python
* config to #define Py_HUGE_VAL to something that works on your platform.
// Py_INFINITY: Value that evaluates to a positive double infinity.
#ifndef Py_INFINITY
# define Py_INFINITY ((double)INFINITY)
#endif

/* Py_HUGE_VAL should always be the same as Py_INFINITY. But historically
* this was not reliable and Python did not require IEEE floats and C99
* conformity. Prefer Py_INFINITY for new code.
*/
#ifndef Py_HUGE_VAL
# define Py_HUGE_VAL HUGE_VAL
#endif

// Py_NAN: Value that evaluates to a quiet Not-a-Number (NaN).
/* Py_NAN: Value that evaluates to a quiet Not-a-Number (NaN). The sign is
* undefined and normally not relevant, but e.g. fixed for float("nan").
*/
#if !defined(Py_NAN)
# if _Py__has_builtin(__builtin_nan)
// Built-in implementation of the ISO C99 function nan(): quiet NaN.
# define Py_NAN (__builtin_nan(""))
#else
// Use C99 NAN constant: quiet Not-A-Number.
// NAN is a float, Py_NAN is a double: cast to double.
# define Py_NAN ((double)NAN)
# endif
Copy link
Contributor Author

@seberg seberg May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, did the minimal thing and added fabs specifically where it seems possibly interesting (there is the Unpack2 for float16 unpacking that doesn't test yet, but used -Py_NAN explicitly).

I was assuming the __builtin_nan is just predating NAN in C99 requirement, but can undo.

For what its worth, NumPy creates NaNs all over with the same system dependent sign bit I suspect, and nobody ever complained.

EDIT/NOTE: Buildbot tests should be rerun probably.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, agreed that getting rid of that use of __builtin_nan looks like the right thing to do - I would have done that in a followup PR if you hadn't done it here.

#endif

#endif /* Py_PYMATH_H */
5 changes: 5 additions & 0 deletions Lib/test/test_cmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ def test_infinity_and_nan_constants(self):
self.assertEqual(cmath.nan.imag, 0.0)
self.assertEqual(cmath.nanj.real, 0.0)
self.assertTrue(math.isnan(cmath.nanj.imag))
# Also check that the sign of all of these is positive:
self.assertEqual(math.copysign(1., cmath.nan.real), 1.)
self.assertEqual(math.copysign(1., cmath.nan.imag), 1.)
self.assertEqual(math.copysign(1., cmath.nanj.real), 1.)
self.assertEqual(math.copysign(1., cmath.nanj.imag), 1.)

# Check consistency with reprs.
self.assertEqual(repr(cmath.inf), "inf")
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ class complex2(complex):
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)

def test_constructor_negative_nans_from_string(self):
self.assertEqual(copysign(1., complex("-nan").real), -1.)
self.assertEqual(copysign(1., complex("-nanj").imag), -1.)
self.assertEqual(copysign(1., complex("-nan-nanj").real), -1.)
self.assertEqual(copysign(1., complex("-nan-nanj").imag), -1.)

def test_underscores(self):
# check underscores
for lit in VALID_UNDERSCORE_LITERALS:
Expand Down Expand Up @@ -569,6 +575,7 @@ def test(v, expected, test_fn=self.assertEqual):
test(complex(NAN, 1), "(nan+1j)")
test(complex(1, NAN), "(1+nanj)")
test(complex(NAN, NAN), "(nan+nanj)")
test(complex(-NAN, -NAN), "(nan+nanj)")

test(complex(0, INF), "infj")
test(complex(0, -INF), "-infj")
Expand Down
5 changes: 1 addition & 4 deletions Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -1040,11 +1040,8 @@ def test_inf_signs(self):
self.assertEqual(copysign(1.0, float('inf')), 1.0)
self.assertEqual(copysign(1.0, float('-inf')), -1.0)

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

Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -1881,11 +1881,11 @@ def testIsinf(self):
self.assertFalse(math.isinf(0.))
self.assertFalse(math.isinf(1.))

@requires_IEEE_754
def test_nan_constant(self):
# `math.nan` must be a quiet NaN with positive sign bit
self.assertTrue(math.isnan(math.nan))
self.assertEqual(math.copysign(1., math.nan), 1.)

@requires_IEEE_754
def test_inf_constant(self):
self.assertTrue(math.isinf(math.inf))
self.assertGreater(math.inf, 0.0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fix ``float("nan")`` to produce a quiet NaN on platforms (like MIPS) where
the meaning of the signalling / quiet bit is inverted from its usual
meaning. Also introduce a new macro ``Py_INFINITY`` matching C99's
``INFINITY``, and refactor internals to rely on C99's ``NAN`` and
``INFINITY`` macros instead of hard-coding bit patterns for infinities and
NaNs. Thanks Sebastian Berg.
61 changes: 6 additions & 55 deletions Modules/cmathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#include "Python.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 @@ -88,53 +87,6 @@ else {
#endif
#define CM_SCALE_DOWN (-(CM_SCALE_UP+1)/2)

/* Constants cmath.inf, cmath.infj, cmath.nan, cmath.nanj.
cmath.nan and cmath.nanj are defined only when either
_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)
{
#if _PY_SHORT_FLOAT_REPR == 1
return _Py_dg_infinity(0);
#else
return Py_HUGE_VAL;
#endif
}

static Py_complex
c_infj(void)
{
Py_complex r;
r.real = 0.0;
r.imag = m_inf();
return r;
}

#if _PY_SHORT_FLOAT_REPR == 1

static double
m_nan(void)
{
#if _PY_SHORT_FLOAT_REPR == 1
return _Py_dg_stdnan(0);
#else
return Py_NAN;
#endif
}

static Py_complex
c_nanj(void)
{
Py_complex r;
r.real = 0.0;
r.imag = m_nan();
return r;
}

#endif

/* forward declarations */
static Py_complex cmath_asinh_impl(PyObject *, Py_complex);
Expand Down Expand Up @@ -1274,23 +1226,22 @@ cmath_exec(PyObject *mod)
if (PyModule_AddObject(mod, "tau", PyFloat_FromDouble(Py_MATH_TAU)) < 0) {
return -1;
}
if (PyModule_AddObject(mod, "inf", PyFloat_FromDouble(m_inf())) < 0) {
if (PyModule_AddObject(mod, "inf", PyFloat_FromDouble(Py_INFINITY)) < 0) {
return -1;
}

Py_complex infj = {0.0, Py_INFINITY};
if (PyModule_AddObject(mod, "infj",
PyComplex_FromCComplex(c_infj())) < 0) {
PyComplex_FromCComplex(infj)) < 0) {
return -1;
}
#if _PY_SHORT_FLOAT_REPR == 1
if (PyModule_AddObject(mod, "nan", PyFloat_FromDouble(m_nan())) < 0) {
if (PyModule_AddObject(mod, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) {
return -1;
}
if (PyModule_AddObject(mod, "nanj",
PyComplex_FromCComplex(c_nanj())) < 0) {
Py_complex nanj = {0.0, fabs(Py_NAN)};
if (PyModule_AddObject(mod, "nanj", PyComplex_FromCComplex(nanj)) < 0) {
return -1;
}
#endif

/* initialize special value tables */

Expand Down
39 changes: 4 additions & 35 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ raised for division by zero and mod by zero.
#include "Python.h"
#include "pycore_bitutils.h" // _Py_bit_length()
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_dtoa.h" // _Py_dg_infinity()
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_object.h" // _PyObject_LookupSpecial()
Expand Down Expand Up @@ -389,34 +388,6 @@ lanczos_sum(double x)
return num/den;
}

/* Constant for +infinity, generated in the same way as float('inf'). */

static double
m_inf(void)
{
#if _PY_SHORT_FLOAT_REPR == 1
return _Py_dg_infinity(0);
#else
return Py_HUGE_VAL;
#endif
}

/* Constant nan value, generated in the same way as float('nan'). */
/* We don't currently assume that Py_NAN is defined everywhere. */

#if _PY_SHORT_FLOAT_REPR == 1

static double
m_nan(void)
{
#if _PY_SHORT_FLOAT_REPR == 1
return _Py_dg_stdnan(0);
#else
return Py_NAN;
#endif
}

#endif

static double
m_tgamma(double x)
Expand All @@ -435,7 +406,7 @@ m_tgamma(double x)
if (x == 0.0) {
errno = EDOM;
/* tgamma(+-0.0) = +-inf, divide-by-zero */
return copysign(Py_HUGE_VAL, x);
return copysign(Py_INFINITY, x);
}

/* integer arguments */
Expand Down Expand Up @@ -3938,7 +3909,7 @@ math_ulp_impl(PyObject *module, double x)
if (Py_IS_INFINITY(x)) {
return x;
}
double inf = m_inf();
double inf = Py_INFINITY;
double x2 = nextafter(x, inf);
if (Py_IS_INFINITY(x2)) {
/* special case: x is the largest positive representable float */
Expand Down Expand Up @@ -3975,14 +3946,12 @@ math_exec(PyObject *module)
if (PyModule_AddObject(module, "tau", PyFloat_FromDouble(Py_MATH_TAU)) < 0) {
return -1;
}
if (PyModule_AddObject(module, "inf", PyFloat_FromDouble(m_inf())) < 0) {
if (PyModule_AddObject(module, "inf", PyFloat_FromDouble(Py_INFINITY)) < 0) {
return -1;
}
#if _PY_SHORT_FLOAT_REPR == 1
if (PyModule_AddObject(module, "nan", PyFloat_FromDouble(m_nan())) < 0) {
if (PyModule_AddObject(module, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) {
return -1;
}
#endif
return 0;
}

Expand Down
13 changes: 1 addition & 12 deletions Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2424,25 +2424,14 @@ PyFloat_Unpack2(const char *data, int le)
f |= *p;

if (e == 0x1f) {
#if _PY_SHORT_FLOAT_REPR == 0
if (f == 0) {
/* Infinity */
return sign ? -Py_HUGE_VAL : Py_HUGE_VAL;
}
else {
/* NaN */
return sign ? -Py_NAN : Py_NAN;
return sign ? -fabs(Py_NAN) : fabs(Py_NAN);
}
#else // _PY_SHORT_FLOAT_REPR == 1
if (f == 0) {
/* Infinity */
return _Py_dg_infinity(sign);
}
else {
/* NaN */
return _Py_dg_stdnan(sign);
}
#endif // _PY_SHORT_FLOAT_REPR == 1
}

x = (double)f / 1024.0;
Expand Down
34 changes: 0 additions & 34 deletions Python/dtoa.c
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,6 @@ typedef union { double d; ULong L[2]; } U;
#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1))
#define Big1 0xffffffff

/* Standard NaN used by _Py_dg_stdnan. */

#define NAN_WORD0 0x7ff80000
#define NAN_WORD1 0

/* Bits of the representation of positive infinity. */

#define POSINF_WORD0 0x7ff00000
Expand Down Expand Up @@ -1399,35 +1394,6 @@ bigcomp(U *rv, const char *s0, BCinfo *bc)
return 0;
}

/* Return a 'standard' NaN value.

There are exactly two quiet NaNs that don't arise by 'quieting' signaling
NaNs (see IEEE 754-2008, section 6.2.1). If sign == 0, return the one whose
sign bit is cleared. Otherwise, return the one whose sign bit is set.
*/

double
_Py_dg_stdnan(int sign)
{
U rv;
word0(&rv) = NAN_WORD0;
word1(&rv) = NAN_WORD1;
if (sign)
word0(&rv) |= Sign_bit;
return dval(&rv);
}

/* Return positive or negative infinity, according to the given sign (0 for
* positive infinity, 1 for negative infinity). */

double
_Py_dg_infinity(int sign)
{
U rv;
word0(&rv) = POSINF_WORD0;
word1(&rv) = POSINF_WORD1;
return sign ? -dval(&rv) : dval(&rv);
}

double
_Py_dg_strtod(const char *s00, char **se)
Expand Down
Loading