Commit e631df3c authored by Tom Lane's avatar Tom Lane

Extend numeric_round and numeric_trunc to accept negative scale inputs

(ie, allow rounding to occur at a digit position left of the decimal
point).  Apparently this is how Oracle handles it, and there are
precedents in other programming languages as well.
parent 57cf0959
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* *
* 1998 Jan Wieck * 1998 Jan Wieck
* *
* $Header: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v 1.25 2000/02/24 02:05:30 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v 1.26 2000/03/13 02:31:13 tgl Exp $
* *
* ---------- * ----------
*/ */
...@@ -491,14 +491,17 @@ numeric_sign(Numeric num) ...@@ -491,14 +491,17 @@ numeric_sign(Numeric num)
/* ---------- /* ----------
* numeric_round() - * numeric_round() -
* *
* Modify rscale and dscale of a number and round it if required. * Round a value to have 'scale' digits after the decimal point.
* We allow negative 'scale', implying rounding before the decimal
* point --- Oracle interprets rounding that way.
* ---------- * ----------
*/ */
Numeric Numeric
numeric_round(Numeric num, int32 scale) numeric_round(Numeric num, int32 scale)
{ {
int32 typmod; Numeric res;
int precision; NumericVar arg;
int i;
/* ---------- /* ----------
* Handle NULL * Handle NULL
...@@ -515,27 +518,79 @@ numeric_round(Numeric num, int32 scale) ...@@ -515,27 +518,79 @@ numeric_round(Numeric num, int32 scale)
return make_result(&const_nan); return make_result(&const_nan);
/* ---------- /* ----------
* Check that the requested scale is valid * Limit the scale value to avoid possible overflow in calculations below.
* ---------- * ----------
*/ */
if (scale < 0 || scale > NUMERIC_MAX_DISPLAY_SCALE) scale = MIN(NUMERIC_MAX_RESULT_SCALE,
elog(ERROR, "illegal numeric scale %d - must be between 0 and %d", MAX(-NUMERIC_MAX_RESULT_SCALE, scale));
scale, NUMERIC_MAX_DISPLAY_SCALE);
/* ---------- /* ----------
* Let numeric() and in turn apply_typmod() do the job * Unpack the argument and round it at the proper digit position
* ---------- * ----------
*/ */
precision = MAX(0, num->n_weight) + scale; init_var(&arg);
typmod = (((precision + 2) << 16) | scale) + VARHDRSZ; set_var_from_num(num, &arg);
return numeric(num, typmod);
i = arg.weight + scale + 1;
if (i < arg.ndigits)
{
/* If i = 0, the value loses all digits, but could round up if its
* first digit is more than 4. If i < 0 the result must be 0.
*/
if (i < 0)
{
arg.ndigits = 0;
}
else
{
int carry = (arg.digits[i] > 4) ? 1 : 0;
arg.ndigits = i;
while (carry)
{
carry += arg.digits[--i];
arg.digits[i] = carry % 10;
carry /= 10;
}
if (i < 0)
{
Assert(i == -1); /* better not have added more than 1 digit */
Assert(arg.digits > arg.buf);
arg.digits--;
arg.ndigits++;
arg.weight++;
}
}
}
/* ----------
* Set result's scale to something reasonable.
* ----------
*/
scale = MIN(NUMERIC_MAX_DISPLAY_SCALE, MAX(0, scale));
arg.rscale = scale;
arg.dscale = scale;
/* ----------
* Return the rounded result
* ----------
*/
res = make_result(&arg);
free_var(&arg);
return res;
} }
/* ---------- /* ----------
* numeric_trunc() - * numeric_trunc() -
* *
* Modify rscale and dscale of a number and cut it if required. * Truncate a value to have 'scale' digits after the decimal point.
* We allow negative 'scale', implying a truncation before the decimal
* point --- Oracle interprets truncation that way.
* ---------- * ----------
*/ */
Numeric Numeric
...@@ -559,25 +614,29 @@ numeric_trunc(Numeric num, int32 scale) ...@@ -559,25 +614,29 @@ numeric_trunc(Numeric num, int32 scale)
return make_result(&const_nan); return make_result(&const_nan);
/* ---------- /* ----------
* Check that the requested scale is valid * Limit the scale value to avoid possible overflow in calculations below.
* ---------- * ----------
*/ */
if (scale < 0 || scale > NUMERIC_MAX_DISPLAY_SCALE) scale = MIN(NUMERIC_MAX_RESULT_SCALE,
elog(ERROR, "illegal numeric scale %d - must be between 0 and %d", MAX(-NUMERIC_MAX_RESULT_SCALE, scale));
scale, NUMERIC_MAX_DISPLAY_SCALE);
/* ---------- /* ----------
* Unpack the argument and truncate it * Unpack the argument and truncate it at the proper digit position
* ---------- * ----------
*/ */
init_var(&arg); init_var(&arg);
set_var_from_num(num, &arg); set_var_from_num(num, &arg);
arg.ndigits = MIN(arg.ndigits, MAX(0, arg.weight + scale + 1));
/* ----------
* Set result's scale to something reasonable.
* ----------
*/
scale = MIN(NUMERIC_MAX_DISPLAY_SCALE, MAX(0, scale));
arg.rscale = scale; arg.rscale = scale;
arg.dscale = scale; arg.dscale = scale;
arg.ndigits = MIN(arg.ndigits, MAX(0, arg.weight + scale + 1));
/* ---------- /* ----------
* Return the truncated result * Return the truncated result
* ---------- * ----------
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment