From e383e82e0484aed79f2c78516e3f223345408d4b Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 29 Apr 2012 15:31:56 +0100 Subject: [PATCH] Issue #14521: Make result of float('nan') and float('-nan') more consistent across platforms. Further, don't rely on Py_HUGE_VAL for float('inf'). --- Include/dtoa.h | 2 ++ Lib/test/test_float.py | 13 +++++++++++ Misc/NEWS | 3 +++ Python/dtoa.c | 50 +++++++++++++++++++++++++++++++++++++----- Python/pystrtod.c | 39 ++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/Include/dtoa.h b/Include/dtoa.h index 819bd0f2070..9bfb6251db8 100644 --- a/Include/dtoa.h +++ b/Include/dtoa.h @@ -8,6 +8,8 @@ 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); #ifdef __cplusplus diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index dc0c2912be2..3cee3836a35 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -870,6 +870,19 @@ class InfNanTest(unittest.TestCase): self.assertFalse(NAN.is_inf()) self.assertFalse((0.).is_inf()) + 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. + self.assertEqual(copysign(1.0, float('nan')), 1.0) + self.assertEqual(copysign(1.0, float('-nan')), -1.0) + + fromHex = float.fromhex toHex = float.hex class HexFloatTestCase(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS index 83f2e3ea04e..fbc8b5f2537 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.3.0 Alpha 3? Core and Builtins ----------------- +- Issue #14521: Make result of float('nan') and float('-nan') more + consistent across platforms. + - Issue #14646: __import__() sets __loader__ if the loader did not. - Issue #14605: No longer have implicit entries in sys.meta_path. If diff --git a/Python/dtoa.c b/Python/dtoa.c index 82b6faa80c2..83861ace180 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -265,6 +265,16 @@ 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 +#define POSINF_WORD1 0 + /* struct BCinfo is used to pass information from _Py_dg_strtod to bigcomp */ typedef struct BCinfo BCinfo; @@ -1486,6 +1496,36 @@ 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) { @@ -1886,20 +1926,20 @@ _Py_dg_strtod(const char *s00, char **se) bd2++; /* At this stage bd5 - bb5 == e == bd2 - bb2 + bbe, bb2 - bs2 == 1, - and bs == 1, so: + and bs == 1, so: tdv == bd * 10**e = bd * 2**(bbe - bb2 + bd2) * 5**(bd5 - bb5) srv == bb * 2**bbe = bb * 2**(bbe - bb2 + bb2) - 0.5 ulp(srv) == 2**(bbe-1) = bs * 2**(bbe - bb2 + bs2) + 0.5 ulp(srv) == 2**(bbe-1) = bs * 2**(bbe - bb2 + bs2) - It follows that: + It follows that: M * tdv = bd * 2**bd2 * 5**bd5 M * srv = bb * 2**bb2 * 5**bb5 M * 0.5 ulp(srv) = bs * 2**bs2 * 5**bb5 - for some constant M. (Actually, M == 2**(bb2 - bbe) * 5**bb5, but - this fact is not needed below.) + for some constant M. (Actually, M == 2**(bb2 - bbe) * 5**bb5, but + this fact is not needed below.) */ /* Remove factor of 2**i, where i = min(bb2, bd2, bs2). */ diff --git a/Python/pystrtod.c b/Python/pystrtod.c index 7bf21c0bc1a..4ab8f08d22b 100644 --- a/Python/pystrtod.c +++ b/Python/pystrtod.c @@ -22,6 +22,43 @@ 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 + +double +_Py_parse_inf_or_nan(const char *p, char **endptr) +{ + double retval; + const char *s; + int negate = 0; + + s = p; + if (*s == '-') { + negate = 1; + s++; + } + else if (*s == '+') { + s++; + } + if (case_insensitive_match(s, "inf")) { + s += 3; + if (case_insensitive_match(s, "inity")) + s += 5; + retval = _Py_dg_infinity(negate); + } + else if (case_insensitive_match(s, "nan")) { + s += 3; + retval = _Py_dg_stdnan(negate); + } + else { + s = p; + retval = -1.0; + } + *endptr = (char *)s; + return retval; +} + +#else + double _Py_parse_inf_or_nan(const char *p, char **endptr) { @@ -57,6 +94,8 @@ _Py_parse_inf_or_nan(const char *p, char **endptr) return retval; } +#endif + /** * _PyOS_ascii_strtod: * @nptr: the string to convert to a numeric value.