mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	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.
This commit is contained in:
		@@ -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
 | 
			
		||||
	 * ----------
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user