diff options
Diffstat (limited to 'Objects/floatobject.c')
| -rw-r--r-- | Objects/floatobject.c | 166 | 
1 files changed, 142 insertions, 24 deletions
diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 2fbe810..b7b5220 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -899,43 +899,161 @@ float_trunc(PyObject *v)  	return PyLong_FromDouble(wholepart);  } +/* double_round: rounds a finite double to the closest multiple of +   10**-ndigits; here ndigits is within reasonable bounds (typically, -308 <= +   ndigits <= 323).  Returns a Python float, or sets a Python error and +   returns NULL on failure (OverflowError and memory errors are possible). */ + +#ifndef PY_NO_SHORT_FLOAT_REPR +/* version of double_round that uses the correctly-rounded string<->double +   conversions from Python/dtoa.c */ +  static PyObject * -float_round(PyObject *v, PyObject *args) -{ -#define UNDEF_NDIGITS (-0x7fffffff) /* Unlikely ndigits value */ -	double x; -	double f = 1.0; -	double flr, cil; +double_round(double x, int ndigits) { +  	double rounded; -	int ndigits = UNDEF_NDIGITS; +	Py_ssize_t buflen, mybuflen=100; +	char *buf, *buf_end, shortbuf[100], *mybuf=shortbuf; +	int decpt, sign; +	PyObject *result = NULL; -	if (!PyArg_ParseTuple(args, "|i", &ndigits)) +	/* round to a decimal string */ +	buf = _Py_dg_dtoa(x, 3, ndigits, &decpt, &sign, &buf_end); +	if (buf == NULL) { +		PyErr_NoMemory();  		return NULL; +	} -	x = PyFloat_AsDouble(v); +	/* Get new buffer if shortbuf is too small.  Space needed <= buf_end - +	buf + 8: (1 extra for '0', 1 for sign, 5 for exp, 1 for '\0').  */ +	buflen = buf_end - buf; +	if (buflen + 8 > mybuflen) { +		mybuflen = buflen+8; +		mybuf = (char *)PyMem_Malloc(mybuflen); +		if (mybuf == NULL) { +			PyErr_NoMemory(); +			goto exit; +		} +	} +	/* copy buf to mybuf, adding exponent, sign and leading 0 */ +	PyOS_snprintf(mybuf, mybuflen, "%s0%se%d", (sign ? "-" : ""), +		      buf, decpt - (int)buflen); -	if (ndigits != UNDEF_NDIGITS) { -		f = pow(10.0, ndigits); -		x *= f; +	/* and convert the resulting string back to a double */ +	errno = 0; +	rounded = _Py_dg_strtod(mybuf, NULL); +	if (errno == ERANGE && fabs(rounded) >= 1.) +		PyErr_SetString(PyExc_OverflowError, +				"rounded value too large to represent"); +	else +		result = PyFloat_FromDouble(rounded); + +	/* done computing value;  now clean up */ +	if (mybuf != shortbuf) +		PyMem_Free(mybuf); +  exit: +	_Py_dg_freedtoa(buf); +	return result; +} + +#else /* PY_NO_SHORT_FLOAT_REPR */ + +/* fallback version, to be used when correctly rounded binary<->decimal +   conversions aren't available */ + +static PyObject * +double_round(double x, int ndigits) { +	double pow1, pow2, y, z; +	if (ndigits >= 0) { +		if (ndigits > 22) { +			/* pow1 and pow2 are each safe from overflow, but +			   pow1*pow2 ~= pow(10.0, ndigits) might overflow */ +			pow1 = pow(10.0, (double)(ndigits-22)); +			pow2 = 1e22; +		} +		else { +			pow1 = pow(10.0, (double)ndigits); +			pow2 = 1.0; +		} +		y = (x*pow1)*pow2; +		/* if y overflows, then rounded value is exactly x */ +		if (!Py_IS_FINITE(y)) +			return PyFloat_FromDouble(x); +	} +	else { +		pow1 = pow(10.0, (double)-ndigits); +		pow2 = 1.0; /* unused; silences a gcc compiler warning */ +		y = x / pow1;  	} -	flr = floor(x); -	cil = ceil(x); +	z = round(y); +	if (fabs(y-z) == 0.5) +		/* halfway between two integers; use round-half-even */ +		z = 2.0*round(y/2.0); -	if (x-flr > 0.5) -		rounded = cil; -	else if (x-flr == 0.5) -		rounded = fmod(flr, 2) == 0 ? flr : cil; +	if (ndigits >= 0) +		z = (z / pow2) / pow1;  	else -		rounded = flr; +		z *= pow1; -	if (ndigits != UNDEF_NDIGITS) { -		rounded /= f; -		return PyFloat_FromDouble(rounded); +	/* if computation resulted in overflow, raise OverflowError */ +	if (!Py_IS_FINITE(z)) { +		PyErr_SetString(PyExc_OverflowError, +				"overflow occurred during round"); +		return NULL;  	} -	return PyLong_FromDouble(rounded); -#undef UNDEF_NDIGITS +	return PyFloat_FromDouble(z); +} + +#endif /* PY_NO_SHORT_FLOAT_REPR */ + +/* round a Python float v to the closest multiple of 10**-ndigits */ + +static PyObject * +float_round(PyObject *v, PyObject *args) +{ +	double x, rounded; +	PyObject *o_ndigits = NULL; +	Py_ssize_t ndigits; + +	x = PyFloat_AsDouble(v); +	if (!PyArg_ParseTuple(args, "|O", &o_ndigits)) +		return NULL; +	if (o_ndigits == NULL) { +		/* single-argument round: round to nearest integer */ +		rounded = round(x); +		if (fabs(x-rounded) == 0.5) +			/* halfway case: round to even */ +			rounded = 2.0*round(x/2.0); +		return PyLong_FromDouble(rounded); +	} + +	/* interpret second argument as a Py_ssize_t; clips on overflow */ +	ndigits = PyNumber_AsSsize_t(o_ndigits, NULL); +	if (ndigits == -1 && PyErr_Occurred()) +		return NULL; + +	/* nans and infinities round to themselves */ +	if (!Py_IS_FINITE(x)) +		return PyFloat_FromDouble(x); + +	/* Deal with extreme values for ndigits. For ndigits > NDIGITS_MAX, x +	   always rounds to itself.  For ndigits < NDIGITS_MIN, x always +	   rounds to +-0.0.  Here 0.30103 is an upper bound for log10(2). */ +#define NDIGITS_MAX ((int)((DBL_MANT_DIG-DBL_MIN_EXP) * 0.30103)) +#define NDIGITS_MIN (-(int)((DBL_MAX_EXP + 1) * 0.30103)) +	if (ndigits > NDIGITS_MAX) +		/* return x */ +		return PyFloat_FromDouble(x); +	else if (ndigits < NDIGITS_MIN) +		/* return 0.0, but with sign of x */ +		return PyFloat_FromDouble(0.0*x); +	else +		/* finite x, and ndigits is not unreasonably large */ +		return double_round(x, (int)ndigits); +#undef NDIGITS_MAX +#undef NDIGITS_MIN  }  static PyObject *  | 
