Skip to content

Commit e631df3

Browse files
committed
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.
1 parent 57cf095 commit e631df3

File tree

1 file changed

+79
-20
lines changed

1 file changed

+79
-20
lines changed

src/backend/utils/adt/numeric.c

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*
66
* 1998 Jan Wieck
77
*
8-
* $Header: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v 1.25 2000/02/24 02:05:30 tgl Exp $
8+
* $Header: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v 1.26 2000/03/13 02:31:13 tgl Exp $
99
*
1010
* ----------
1111
*/
@@ -491,14 +491,17 @@ numeric_sign(Numeric num)
491491
/* ----------
492492
* numeric_round() -
493493
*
494-
* Modify rscale and dscale of a number and round it if required.
494+
* Round a value to have 'scale' digits after the decimal point.
495+
* We allow negative 'scale', implying rounding before the decimal
496+
* point --- Oracle interprets rounding that way.
495497
* ----------
496498
*/
497499
Numeric
498500
numeric_round(Numeric num, int32 scale)
499501
{
500-
int32 typmod;
501-
int precision;
502+
Numeric res;
503+
NumericVar arg;
504+
int i;
502505

503506
/* ----------
504507
* Handle NULL
@@ -515,27 +518,79 @@ numeric_round(Numeric num, int32 scale)
515518
return make_result(&const_nan);
516519

517520
/* ----------
518-
* Check that the requested scale is valid
521+
* Limit the scale value to avoid possible overflow in calculations below.
519522
* ----------
520523
*/
521-
if (scale < 0 || scale > NUMERIC_MAX_DISPLAY_SCALE)
522-
elog(ERROR, "illegal numeric scale %d - must be between 0 and %d",
523-
scale, NUMERIC_MAX_DISPLAY_SCALE);
524+
scale = MIN(NUMERIC_MAX_RESULT_SCALE,
525+
MAX(-NUMERIC_MAX_RESULT_SCALE, scale));
524526

525527
/* ----------
526-
* Let numeric() and in turn apply_typmod() do the job
528+
* Unpack the argument and round it at the proper digit position
527529
* ----------
528530
*/
529-
precision = MAX(0, num->n_weight) + scale;
530-
typmod = (((precision + 2) << 16) | scale) + VARHDRSZ;
531-
return numeric(num, typmod);
531+
init_var(&arg);
532+
set_var_from_num(num, &arg);
533+
534+
i = arg.weight + scale + 1;
535+
536+
if (i < arg.ndigits)
537+
{
538+
/* If i = 0, the value loses all digits, but could round up if its
539+
* first digit is more than 4. If i < 0 the result must be 0.
540+
*/
541+
if (i < 0)
542+
{
543+
arg.ndigits = 0;
544+
}
545+
else
546+
{
547+
int carry = (arg.digits[i] > 4) ? 1 : 0;
548+
549+
arg.ndigits = i;
550+
551+
while (carry)
552+
{
553+
carry += arg.digits[--i];
554+
arg.digits[i] = carry % 10;
555+
carry /= 10;
556+
}
557+
558+
if (i < 0)
559+
{
560+
Assert(i == -1); /* better not have added more than 1 digit */
561+
Assert(arg.digits > arg.buf);
562+
arg.digits--;
563+
arg.ndigits++;
564+
arg.weight++;
565+
}
566+
}
567+
}
568+
569+
/* ----------
570+
* Set result's scale to something reasonable.
571+
* ----------
572+
*/
573+
scale = MIN(NUMERIC_MAX_DISPLAY_SCALE, MAX(0, scale));
574+
arg.rscale = scale;
575+
arg.dscale = scale;
576+
577+
/* ----------
578+
* Return the rounded result
579+
* ----------
580+
*/
581+
res = make_result(&arg);
582+
583+
free_var(&arg);
584+
return res;
532585
}
533586

534587

535588
/* ----------
536589
* numeric_trunc() -
537590
*
538-
* Modify rscale and dscale of a number and cut it if required.
591+
* Truncate a value to have 'scale' digits after the decimal point.
592+
* We allow negative 'scale', implying a truncation before the decimal
593+
* point --- Oracle interprets truncation that way.
539594
* ----------
540595
*/
541596
Numeric
@@ -559,25 +614,29 @@ numeric_trunc(Numeric num, int32 scale)
559614
return make_result(&const_nan);
560615

561616
/* ----------
562-
* Check that the requested scale is valid
617+
* Limit the scale value to avoid possible overflow in calculations below.
563618
* ----------
564619
*/
565-
if (scale < 0 || scale > NUMERIC_MAX_DISPLAY_SCALE)
566-
elog(ERROR, "illegal numeric scale %d - must be between 0 and %d",
567-
scale, NUMERIC_MAX_DISPLAY_SCALE);
620+
scale = MIN(NUMERIC_MAX_RESULT_SCALE,
621+
MAX(-NUMERIC_MAX_RESULT_SCALE, scale));
568622

569623
/* ----------
570-
* Unpack the argument and truncate it
624+
* Unpack the argument and truncate it at the proper digit position
571625
* ----------
572626
*/
573627
init_var(&arg);
574628
set_var_from_num(num, &arg);
575629

630+
arg.ndigits = MIN(arg.ndigits, MAX(0, arg.weight + scale + 1));
631+
632+
/* ----------
633+
* Set result's scale to something reasonable.
634+
* ----------
635+
*/
636+
scale = MIN(NUMERIC_MAX_DISPLAY_SCALE, MAX(0, scale));
576637
arg.rscale = scale;
577638
arg.dscale = scale;
578639

579-
arg.ndigits = MIN(arg.ndigits, MAX(0, arg.weight + scale + 1));
580-
581640
/* ----------
582641
* Return the truncated result
583642
* ----------

0 commit comments

Comments
 (0)