aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/numeric.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2000-03-13 02:31:13 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2000-03-13 02:31:13 +0000
commite631df3c1beb5609ba9aab1515a3e38f619f43df (patch)
tree1a135da892a9dd5f565abb8de1d4ac6e776fac75 /src/backend/utils/adt/numeric.c
parent57cf09591b4140773433f1b3ed5dc998ba2668a4 (diff)
downloadpostgresql-e631df3c1beb5609ba9aab1515a3e38f619f43df.tar.gz
postgresql-e631df3c1beb5609ba9aab1515a3e38f619f43df.zip
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.
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r--src/backend/utils/adt/numeric.c99
1 files changed, 79 insertions, 20 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index a3e9a7d54c3..4d46992e403 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -5,7 +5,7 @@
*
* 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)
/* ----------
* 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_round(Numeric num, int32 scale)
{
- int32 typmod;
- int precision;
+ Numeric res;
+ NumericVar arg;
+ int i;
/* ----------
* Handle NULL
@@ -515,27 +518,79 @@ numeric_round(Numeric num, int32 scale)
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)
- elog(ERROR, "illegal numeric scale %d - must be between 0 and %d",
- scale, NUMERIC_MAX_DISPLAY_SCALE);
+ scale = MIN(NUMERIC_MAX_RESULT_SCALE,
+ MAX(-NUMERIC_MAX_RESULT_SCALE, 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;
- typmod = (((precision + 2) << 16) | scale) + VARHDRSZ;
- return numeric(num, typmod);
+ init_var(&arg);
+ set_var_from_num(num, &arg);
+
+ 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() -
*
- * 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
@@ -559,25 +614,29 @@ numeric_trunc(Numeric num, int32 scale)
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)
- elog(ERROR, "illegal numeric scale %d - must be between 0 and %d",
- scale, NUMERIC_MAX_DISPLAY_SCALE);
+ scale = MIN(NUMERIC_MAX_RESULT_SCALE,
+ MAX(-NUMERIC_MAX_RESULT_SCALE, scale));
/* ----------
- * Unpack the argument and truncate it
+ * Unpack the argument and truncate it at the proper digit position
* ----------
*/
init_var(&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.dscale = scale;
- arg.ndigits = MIN(arg.ndigits, MAX(0, arg.weight + scale + 1));
-
/* ----------
* Return the truncated result
* ----------