Skip to content

Commit e6a076d

Browse files
committed
Issue #1869 (and 4707, 5118, 5473, 1456775): use the new
string <-> float conversion routines to make round(x, n) correctly rounded for floats x, so that it always agrees with format(x, '.<n>f'). Also fix some other round nuisances, like round(123.456, 1-2**31) giving an integer rather than a float.
1 parent 60fd099 commit e6a076d

File tree

3 files changed

+230
-24
lines changed

3 files changed

+230
-24
lines changed

Lib/test/test_float.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,88 @@ def test_short_repr(self):
389389
self.assertEqual(s, repr(float(s)))
390390
self.assertEqual(negs, repr(float(negs)))
391391

392+
class RoundTestCase(unittest.TestCase):
393+
@unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
394+
"test requires IEEE 754 doubles")
395+
def test_inf_nan(self):
396+
self.assertRaises(OverflowError, round, INF)
397+
self.assertRaises(OverflowError, round, -INF)
398+
self.assertRaises(ValueError, round, NAN)
399+
400+
@unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
401+
"test requires IEEE 754 doubles")
402+
def test_large_n(self):
403+
for n in [324, 325, 400, 2**31-1, 2**31, 2**32, 2**100]:
404+
self.assertEqual(round(123.456, n), 123.456)
405+
self.assertEqual(round(-123.456, n), -123.456)
406+
self.assertEqual(round(1e300, n), 1e300)
407+
self.assertEqual(round(1e-320, n), 1e-320)
408+
self.assertEqual(round(1e150, 300), 1e150)
409+
self.assertEqual(round(1e300, 307), 1e300)
410+
self.assertEqual(round(-3.1415, 308), -3.1415)
411+
self.assertEqual(round(1e150, 309), 1e150)
412+
self.assertEqual(round(1.4e-315, 315), 1e-315)
413+
414+
@unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
415+
"test requires IEEE 754 doubles")
416+
def test_small_n(self):
417+
for n in [-308, -309, -400, 1-2**31, -2**31, -2**31-1, -2**100]:
418+
self.assertEqual(round(123.456, n), 0.0)
419+
self.assertEqual(round(-123.456, n), -0.0)
420+
self.assertEqual(round(1e300, n), 0.0)
421+
self.assertEqual(round(1e-320, n), 0.0)
422+
423+
@unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
424+
"test requires IEEE 754 doubles")
425+
def test_overflow(self):
426+
self.assertRaises(OverflowError, round, 1.6e308, -308)
427+
self.assertRaises(OverflowError, round, -1.7e308, -308)
428+
429+
@unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
430+
"applies only when using short float repr style")
431+
def test_previous_round_bugs(self):
432+
# particular cases that have occurred in bug reports
433+
self.assertEqual(round(562949953421312.5, 1),
434+
562949953421312.5)
435+
self.assertEqual(round(56294995342131.5, 3),
436+
56294995342131.5)
437+
# round-half-even
438+
self.assertEqual(round(25.0, -1), 20.0)
439+
self.assertEqual(round(35.0, -1), 40.0)
440+
self.assertEqual(round(45.0, -1), 40.0)
441+
self.assertEqual(round(55.0, -1), 60.0)
442+
self.assertEqual(round(65.0, -1), 60.0)
443+
self.assertEqual(round(75.0, -1), 80.0)
444+
self.assertEqual(round(85.0, -1), 80.0)
445+
self.assertEqual(round(95.0, -1), 100.0)
446+
447+
@unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
448+
"applies only when using short float repr style")
449+
def test_matches_float_format(self):
450+
# round should give the same results as float formatting
451+
for i in range(500):
452+
x = i/1000.
453+
self.assertEqual(float(format(x, '.0f')), round(x, 0))
454+
self.assertEqual(float(format(x, '.1f')), round(x, 1))
455+
self.assertEqual(float(format(x, '.2f')), round(x, 2))
456+
self.assertEqual(float(format(x, '.3f')), round(x, 3))
457+
458+
for i in range(5, 5000, 10):
459+
x = i/1000.
460+
self.assertEqual(float(format(x, '.0f')), round(x, 0))
461+
self.assertEqual(float(format(x, '.1f')), round(x, 1))
462+
self.assertEqual(float(format(x, '.2f')), round(x, 2))
463+
self.assertEqual(float(format(x, '.3f')), round(x, 3))
464+
465+
for i in range(500):
466+
x = random.random()
467+
self.assertEqual(float(format(x, '.0f')), round(x, 0))
468+
self.assertEqual(float(format(x, '.1f')), round(x, 1))
469+
self.assertEqual(float(format(x, '.2f')), round(x, 2))
470+
self.assertEqual(float(format(x, '.3f')), round(x, 3))
471+
472+
473+
392474
# Beginning with Python 2.6 float has cross platform compatible
393475
# ways to create and represent inf and nan
394476
class InfNanTest(unittest.TestCase):
@@ -878,6 +960,7 @@ def test_main():
878960
IEEEFormatTestCase,
879961
FormatTestCase,
880962
ReprTestCase,
963+
RoundTestCase,
881964
InfNanTest,
882965
HexFloatTestCase,
883966
)

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ What's New in Python 3.1 beta 1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #1869 (and many duplicates): make round(x, n) correctly
16+
rounded for a float x, by using the decimal <-> binary conversions
17+
from Python/dtoa.c. As a consequence, (e.g.) round(x, 2) now
18+
consistently agrees with format(x, '.2f').
19+
1520
- Issue #5772: format(1e100, '<') produces '1e+100', not '1.0e+100'.
1621

1722
- Issue #5515: str.format() type 'n' combined with commas and leading

Objects/floatobject.c

Lines changed: 142 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -899,43 +899,161 @@ float_trunc(PyObject *v)
899899
return PyLong_FromDouble(wholepart);
900900
}
901901

902+
/* double_round: rounds a finite double to the closest multiple of
903+
10**-ndigits; here ndigits is within reasonable bounds (typically, -308 <=
904+
ndigits <= 323). Returns a Python float, or sets a Python error and
905+
returns NULL on failure (OverflowError and memory errors are possible). */
906+
907+
#ifndef PY_NO_SHORT_FLOAT_REPR
908+
/* version of double_round that uses the correctly-rounded string<->double
909+
conversions from Python/dtoa.c */
910+
902911
static PyObject *
903-
float_round(PyObject *v, PyObject *args)
904-
{
905-
#define UNDEF_NDIGITS (-0x7fffffff) /* Unlikely ndigits value */
906-
double x;
907-
double f = 1.0;
908-
double flr, cil;
912+
double_round(double x, int ndigits) {
913+
909914
double rounded;
910-
int ndigits = UNDEF_NDIGITS;
915+
Py_ssize_t buflen, mybuflen=100;
916+
char *buf, *buf_end, shortbuf[100], *mybuf=shortbuf;
917+
int decpt, sign;
918+
PyObject *result = NULL;
911919

912-
if (!PyArg_ParseTuple(args, "|i", &ndigits))
920+
/* round to a decimal string */
921+
buf = _Py_dg_dtoa(x, 3, ndigits, &decpt, &sign, &buf_end);
922+
if (buf == NULL) {
923+
PyErr_NoMemory();
913924
return NULL;
925+
}
914926

915-
x = PyFloat_AsDouble(v);
927+
/* Get new buffer if shortbuf is too small. Space needed <= buf_end -
928+
buf + 8: (1 extra for '0', 1 for sign, 5 for exp, 1 for '\0'). */
929+
buflen = buf_end - buf;
930+
if (buflen + 8 > mybuflen) {
931+
mybuflen = buflen+8;
932+
mybuf = (char *)PyMem_Malloc(mybuflen);
933+
if (mybuf == NULL) {
934+
PyErr_NoMemory();
935+
goto exit;
936+
}
937+
}
938+
/* copy buf to mybuf, adding exponent, sign and leading 0 */
939+
PyOS_snprintf(mybuf, mybuflen, "%s0%se%d", (sign ? "-" : ""),
940+
buf, decpt - (int)buflen);
916941

917-
if (ndigits != UNDEF_NDIGITS) {
918-
f = pow(10.0, ndigits);
919-
x *= f;
942+
/* and convert the resulting string back to a double */
943+
errno = 0;
944+
rounded = _Py_dg_strtod(mybuf, NULL);
945+
if (errno == ERANGE && fabs(rounded) >= 1.)
946+
PyErr_SetString(PyExc_OverflowError,
947+
"rounded value too large to represent");
948+
else
949+
result = PyFloat_FromDouble(rounded);
950+
951+
/* done computing value; now clean up */
952+
if (mybuf != shortbuf)
953+
PyMem_Free(mybuf);
954+
exit:
955+
_Py_dg_freedtoa(buf);
956+
return result;
957+
}
958+
959+
#else /* PY_NO_SHORT_FLOAT_REPR */
960+
961+
/* fallback version, to be used when correctly rounded binary<->decimal
962+
conversions aren't available */
963+
964+
static PyObject *
965+
double_round(double x, int ndigits) {
966+
double pow1, pow2, y, z;
967+
if (ndigits >= 0) {
968+
if (ndigits > 22) {
969+
/* pow1 and pow2 are each safe from overflow, but
970+
pow1*pow2 ~= pow(10.0, ndigits) might overflow */
971+
pow1 = pow(10.0, (double)(ndigits-22));
972+
pow2 = 1e22;
973+
}
974+
else {
975+
pow1 = pow(10.0, (double)ndigits);
976+
pow2 = 1.0;
977+
}
978+
y = (x*pow1)*pow2;
979+
/* if y overflows, then rounded value is exactly x */
980+
if (!Py_IS_FINITE(y))
981+
return PyFloat_FromDouble(x);
982+
}
983+
else {
984+
pow1 = pow(10.0, (double)-ndigits);
985+
pow2 = 1.0; /* unused; silences a gcc compiler warning */
986+
y = x / pow1;
920987
}
921988

922-
flr = floor(x);
923-
cil = ceil(x);
989+
z = round(y);
990+
if (fabs(y-z) == 0.5)
991+
/* halfway between two integers; use round-half-even */
992+
z = 2.0*round(y/2.0);
924993

925-
if (x-flr > 0.5)
926-
rounded = cil;
927-
else if (x-flr == 0.5)
928-
rounded = fmod(flr, 2) == 0 ? flr : cil;
994+
if (ndigits >= 0)
995+
z = (z / pow2) / pow1;
929996
else
930-
rounded = flr;
997+
z *= pow1;
931998

932-
if (ndigits != UNDEF_NDIGITS) {
933-
rounded /= f;
934-
return PyFloat_FromDouble(rounded);
999+
/* if computation resulted in overflow, raise OverflowError */
1000+
if (!Py_IS_FINITE(z)) {
1001+
PyErr_SetString(PyExc_OverflowError,
1002+
"overflow occurred during round");
1003+
return NULL;
9351004
}
9361005

937-
return PyLong_FromDouble(rounded);
938-
#undef UNDEF_NDIGITS
1006+
return PyFloat_FromDouble(z);
1007+
}
1008+
1009+
#endif /* PY_NO_SHORT_FLOAT_REPR */
1010+
1011+
/* round a Python float v to the closest multiple of 10**-ndigits */
1012+
1013+
static PyObject *
1014+
float_round(PyObject *v, PyObject *args)
1015+
{
1016+
double x, rounded;
1017+
PyObject *o_ndigits = NULL;
1018+
Py_ssize_t ndigits;
1019+
1020+
x = PyFloat_AsDouble(v);
1021+
if (!PyArg_ParseTuple(args, "|O", &o_ndigits))
1022+
return NULL;
1023+
if (o_ndigits == NULL) {
1024+
/* single-argument round: round to nearest integer */
1025+
rounded = round(x);
1026+
if (fabs(x-rounded) == 0.5)
1027+
/* halfway case: round to even */
1028+
rounded = 2.0*round(x/2.0);
1029+
return PyLong_FromDouble(rounded);
1030+
}
1031+
1032+
/* interpret second argument as a Py_ssize_t; clips on overflow */
1033+
ndigits = PyNumber_AsSsize_t(o_ndigits, NULL);
1034+
if (ndigits == -1 && PyErr_Occurred())
1035+
return NULL;
1036+
1037+
/* nans and infinities round to themselves */
1038+
if (!Py_IS_FINITE(x))
1039+
return PyFloat_FromDouble(x);
1040+
1041+
/* Deal with extreme values for ndigits. For ndigits > NDIGITS_MAX, x
1042+
always rounds to itself. For ndigits < NDIGITS_MIN, x always
1043+
rounds to +-0.0. Here 0.30103 is an upper bound for log10(2). */
1044+
#define NDIGITS_MAX ((int)((DBL_MANT_DIG-DBL_MIN_EXP) * 0.30103))
1045+
#define NDIGITS_MIN (-(int)((DBL_MAX_EXP + 1) * 0.30103))
1046+
if (ndigits > NDIGITS_MAX)
1047+
/* return x */
1048+
return PyFloat_FromDouble(x);
1049+
else if (ndigits < NDIGITS_MIN)
1050+
/* return 0.0, but with sign of x */
1051+
return PyFloat_FromDouble(0.0*x);
1052+
else
1053+
/* finite x, and ndigits is not unreasonably large */
1054+
return double_round(x, (int)ndigits);
1055+
#undef NDIGITS_MAX
1056+
#undef NDIGITS_MIN
9391057
}
9401058

9411059
static PyObject *

0 commit comments

Comments
 (0)