Index: Objects/longobject.c =================================================================== --- Objects/longobject.c (revision 64438) +++ Objects/longobject.c (working copy) @@ -6,6 +6,7 @@ #include "Python.h" #include "longintrepr.h" +#include "float.h" #include @@ -723,33 +724,60 @@ #undef NBITS_WANTED } -/* Get a C double from a long int object. */ +/* Get a C double from a long int object. Rounds to the nearest double, + using the round-half-to-even rule in the case of a tie. */ double PyLong_AsDouble(PyObject *vv) { - int e = -1; - double x; + PyLongObject *v = (PyLongObject *)vv; + Py_ssize_t ndigits, e, rnd_bit; + size_t numbits; + digit dig, rmask, pmask; + double x = 0.0; if (vv == NULL || !PyLong_Check(vv)) { PyErr_BadInternalCall(); - return -1; + return -1.0; } - x = _PyLong_AsScaledDouble(vv, &e); - if (x == -1.0 && PyErr_Occurred()) - return -1.0; - /* 'e' initialized to -1 to silence gcc-4.0.x, but it should be - set correctly after a successful _PyLong_AsScaledDouble() call */ - assert(e >= 0); - if (e > INT_MAX / PyLong_SHIFT) + + numbits = _PyLong_NumBits(vv); + if (numbits > DBL_MAX_EXP) goto overflow; - errno = 0; - x = ldexp(x, e * PyLong_SHIFT); - if (Py_OVERFLOWED(x)) - goto overflow; - return x; -overflow: + /* rnd_bit = first bit of v that will be rounded away */ + rnd_bit = numbits - DBL_MANT_DIG - 1; + e = rnd_bit < 0 ? -1 : rnd_bit/PyLong_SHIFT; + + ndigits = ABS(Py_SIZE(v)); + while (ndigits > e+1) + x = x*PyLong_BASE + v->ob_digit[--ndigits]; + + /* decide which way to round */ + if (ndigits != 0) { + rmask = 1 << rnd_bit%PyLong_SHIFT; + pmask = 2*rmask; + dig = v->ob_digit[--ndigits]; + x = x * PyLong_BASE + (dig & (PyLong_BASE - pmask)); + if (dig & rmask) { + if ((dig & (rmask-1)) || + (pmask != PyLong_BASE ? dig & pmask : + v->ob_digit[ndigits+1] & 1)) + x += pmask; + else + while (ndigits > 0) + if (v->ob_digit[--ndigits]) { + x += pmask; + break; + } + } + errno = 0; + x = ldexp(x, e * PyLong_SHIFT); + if (Py_OVERFLOWED(x)) + goto overflow; + } + return Py_SIZE(v) < 0 ? -x : x; + overflow: PyErr_SetString(PyExc_OverflowError, "long int too large to convert to float"); return -1.0; Index: Lib/test/test_long.py =================================================================== --- Lib/test/test_long.py (revision 64438) +++ Lib/test/test_long.py (working copy) @@ -611,6 +611,45 @@ else: self.assertRaises(TypeError, pow,longx, longy, long(z)) + def test_float_conversion(self): + # the tests are IEEE-754 specific; skip them on non IEEE machines + if not float.__getformat__("double").startswith("IEEE"): + return + + exact_values = [-2L, -1L, 0L, 1L, 2L, + long(2**53-3), + long(2**53-2), + long(2**53-1), + long(2**53), + long(2**53+2), + long(2**54-4), + long(2**54-2), + long(2**54), + long(2**54+4)] + for x in exact_values: + self.assertEqual(long(float(x)), x) + self.assertEqual(long(float(-x)), -x) + + # test round-half-even + for x, y in [(1, 0), (2, 2), (3, 4), (4, 4), (5, 4), (6, 6), (7, 8)]: + for p in xrange(15): + self.assertEqual(long(float(2L**p*(2**53+x))), 2L**p*(2**53+y)) + + for x, y in [(0, 0), (1, 0), (2, 0), (3, 4), (4, 4), (5, 4), (6, 8), + (7, 8), (8, 8), (9, 8), (10, 8), (11, 12), (12, 12), + (13, 12), (14, 16), (15, 16)]: + for p in xrange(15): + self.assertEqual(long(float(2L**p*(2**54+x))), 2L**p*(2**54+y)) + + for p in xrange(100): + x = long(2**p * (2**53 + 1) + 1) + y = long(2**p * (2**53+ 2)) + self.assertEqual(long(float(x)), y) + + x = long(2**p * (2**53 + 1)) + y = long(2**p * 2**53) + self.assertEqual(long(float(x)), y) + def test_float_overflow(self): import math