Add functions min_scale(numeric) and trim_scale(numeric).

These allow better control of trailing zeroes in numeric values.

Pavel Stehule, based on an old proposal of Marko Tiikkaja's;
review by Karl Pinc

Discussion: https://postgr.es/m/CAFj8pRDjs-navGASeF0Wk74N36YGFJ+v=Ok9_knRa7vDc-qugg@mail.gmail.com
This commit is contained in:
Tom Lane 2020-01-06 12:13:53 -05:00
parent b9c130a1fd
commit 20d6225d16
6 changed files with 285 additions and 3 deletions

View File

@ -918,6 +918,20 @@
<entry><literal>6.0000000000</literal></entry>
</row>
<row>
<entry>
<indexterm>
<primary>min_scale</primary>
</indexterm>
<literal><function>min_scale(<type>numeric</type>)</function></literal>
</entry>
<entry><type>integer</type></entry>
<entry>minimum scale (number of fractional decimal digits) needed
to represent the supplied value</entry>
<entry><literal>min_scale(8.4100)</literal></entry>
<entry><literal>2</literal></entry>
</row>
<row>
<entry>
<indexterm>
@ -1011,8 +1025,8 @@
</entry>
<entry><type>integer</type></entry>
<entry>scale of the argument (the number of decimal digits in the fractional part)</entry>
<entry><literal>scale(8.41)</literal></entry>
<entry><literal>2</literal></entry>
<entry><literal>scale(8.4100)</literal></entry>
<entry><literal>4</literal></entry>
</row>
<row>
@ -1041,6 +1055,20 @@
<entry><literal>1.4142135623731</literal></entry>
</row>
<row>
<entry>
<indexterm>
<primary>trim_scale</primary>
</indexterm>
<literal><function>trim_scale(<type>numeric</type>)</function></literal>
</entry>
<entry><type>numeric</type></entry>
<entry>reduce the scale (number of fractional decimal digits) by
removing trailing zeroes</entry>
<entry><literal>trim_scale(8.4100)</literal></entry>
<entry><literal>8.41</literal></entry>
</row>
<row>
<entry>
<indexterm>

View File

@ -3179,6 +3179,97 @@ numeric_scale(PG_FUNCTION_ARGS)
PG_RETURN_INT32(NUMERIC_DSCALE(num));
}
/*
* Calculate minimum scale for value.
*/
static int
get_min_scale(NumericVar *var)
{
int min_scale;
int last_digit_pos;
/*
* Ordinarily, the input value will be "stripped" so that the last
* NumericDigit is nonzero. But we don't want to get into an infinite
* loop if it isn't, so explicitly find the last nonzero digit.
*/
last_digit_pos = var->ndigits - 1;
while (last_digit_pos >= 0 &&
var->digits[last_digit_pos] == 0)
last_digit_pos--;
if (last_digit_pos >= 0)
{
/* compute min_scale assuming that last ndigit has no zeroes */
min_scale = (last_digit_pos - var->weight) * DEC_DIGITS;
/*
* We could get a negative result if there are no digits after the
* decimal point. In this case the min_scale must be zero.
*/
if (min_scale > 0)
{
/*
* Reduce min_scale if trailing digit(s) in last NumericDigit are
* zero.
*/
NumericDigit last_digit = var->digits[last_digit_pos];
while (last_digit % 10 == 0)
{
min_scale--;
last_digit /= 10;
}
}
else
min_scale = 0;
}
else
min_scale = 0; /* result if input is zero */
return min_scale;
}
/*
* Returns minimum scale required to represent supplied value without loss.
*/
Datum
numeric_min_scale(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar arg;
int min_scale;
if (NUMERIC_IS_NAN(num))
PG_RETURN_NULL();
init_var_from_num(num, &arg);
min_scale = get_min_scale(&arg);
free_var(&arg);
PG_RETURN_INT32(min_scale);
}
/*
* Reduce scale of numeric value to represent supplied value without loss.
*/
Datum
numeric_trim_scale(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
NumericVar result;
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
init_var_from_num(num, &result);
result.dscale = get_min_scale(&result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/* ----------------------------------------------------------------------

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201911241
#define CATALOG_VERSION_NO 202001061
#endif

View File

@ -4254,6 +4254,13 @@
{ oid => '3281', descr => 'number of decimal digits in the fractional part',
proname => 'scale', prorettype => 'int4', proargtypes => 'numeric',
prosrc => 'numeric_scale' },
{ oid => '8389', descr => 'minimum scale needed to represent the value',
proname => 'min_scale', prorettype => 'int4', proargtypes => 'numeric',
prosrc => 'numeric_min_scale' },
{ oid => '8390',
descr => 'numeric with minimum scale needed to represent the value',
proname => 'trim_scale', prorettype => 'numeric', proargtypes => 'numeric',
prosrc => 'numeric_trim_scale' },
{ oid => '1740', descr => 'convert int4 to numeric',
proname => 'numeric', prorettype => 'numeric', proargtypes => 'int4',
prosrc => 'int4_numeric' },

View File

@ -2078,6 +2078,132 @@ select scale(-13.000000000000000);
15
(1 row)
--
-- Tests for min_scale()
--
select min_scale(numeric 'NaN') is NULL; -- should be true
?column?
----------
t
(1 row)
select min_scale(0); -- no digits
min_scale
-----------
0
(1 row)
select min_scale(0.00); -- no digits again
min_scale
-----------
0
(1 row)
select min_scale(1.0); -- no scale
min_scale
-----------
0
(1 row)
select min_scale(1.1); -- scale 1
min_scale
-----------
1
(1 row)
select min_scale(1.12); -- scale 2
min_scale
-----------
2
(1 row)
select min_scale(1.123); -- scale 3
min_scale
-----------
3
(1 row)
select min_scale(1.1234); -- scale 4, filled digit
min_scale
-----------
4
(1 row)
select min_scale(1.12345); -- scale 5, 2 NDIGITS
min_scale
-----------
5
(1 row)
select min_scale(1.1000); -- 1 pos in NDIGITS
min_scale
-----------
1
(1 row)
select min_scale(1e100); -- very big number
min_scale
-----------
0
(1 row)
--
-- Tests for trim_scale()
--
select trim_scale(numeric 'NaN');
trim_scale
------------
NaN
(1 row)
select trim_scale(1.120);
trim_scale
------------
1.12
(1 row)
select trim_scale(0);
trim_scale
------------
0
(1 row)
select trim_scale(0.00);
trim_scale
------------
0
(1 row)
select trim_scale(1.1234500);
trim_scale
------------
1.12345
(1 row)
select trim_scale(110123.12475871856128000);
trim_scale
-----------------------
110123.12475871856128
(1 row)
select trim_scale(-1123.124718561280000000);
trim_scale
-------------------
-1123.12471856128
(1 row)
select trim_scale(-13.00000000000000000000);
trim_scale
------------
-13
(1 row)
select trim_scale(1e100);
trim_scale
-------------------------------------------------------------------------------------------------------
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
(1 row)
--
-- Tests for SUM()
--

View File

@ -1036,6 +1036,36 @@ select scale(110123.12475871856128);
select scale(-1123.12471856128);
select scale(-13.000000000000000);
--
-- Tests for min_scale()
--
select min_scale(numeric 'NaN') is NULL; -- should be true
select min_scale(0); -- no digits
select min_scale(0.00); -- no digits again
select min_scale(1.0); -- no scale
select min_scale(1.1); -- scale 1
select min_scale(1.12); -- scale 2
select min_scale(1.123); -- scale 3
select min_scale(1.1234); -- scale 4, filled digit
select min_scale(1.12345); -- scale 5, 2 NDIGITS
select min_scale(1.1000); -- 1 pos in NDIGITS
select min_scale(1e100); -- very big number
--
-- Tests for trim_scale()
--
select trim_scale(numeric 'NaN');
select trim_scale(1.120);
select trim_scale(0);
select trim_scale(0.00);
select trim_scale(1.1234500);
select trim_scale(110123.12475871856128000);
select trim_scale(-1123.124718561280000000);
select trim_scale(-13.00000000000000000000);
select trim_scale(1e100);
--
-- Tests for SUM()
--