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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy