Ășt 10. 12. 2019 v 0:03 odesĂlatel Karl O. Pinc <[email protected]> napsal:
> On Mon, 9 Dec 2019 21:04:21 +0100
> Pavel Stehule <[email protected]> wrote:
>
> > I fixed almost all mentioned issues (that I understand)
>
> If you don't understand you might ask, or at least say.
> That way I know you've noticed my remarks and I don't
> have to repeat them.
>
> I have 2 remaining suggestions.
>
> 1) As previously suggested: Consider moving
> all the code you added to numeric.c to right after
> the scale() related code. This is equivalent to
> what was done in pg_proc.dat and regression tests
> where all the scale related stuff is in one
> place in the file.
>
> 2) Now that the function is called min_scale()
> it might be nice if your "minscale" variable
> in numeric.c was named "min_scale".
>
> I don't feel particularly strongly about either
> of the above but think them a slight improvement.
>
done
> I also wonder whether all the trim_scale() tests
> are now necessary, but not enough to make any suggestions.
> Especially because, well, tests are good.
>
I don't think so tests should be minimalistic - there can be some
redundancy to coverage some less probable size effects of some future
changes. More - there is a small symmetry with min_scale tests - and third
argument - some times I use tests (result part) as "documentation". But I
have not any problem to reduce tests if there will be requirement to do it.
Regards
Pavel
> Regards,
>
> Karl <[email protected]>
> Free Software: "You don't pay back, you pay forward."
> -- Robert A. Heinlein
>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 57a1539506..2359478792 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -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>minimal scale (number of decimal digits in the fractional part) needed
+ to store the supplied value without data loss</entry>
+ <entry><literal>min_scale(8.4100)</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+
<row>
<entry>
<indexterm>
@@ -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 (the number of decimal digits in the fractional part)
+ to the minimum needed to store the supplied value without data loss</entry>
+ <entry><literal>scale(8.4100)</literal></entry>
+ <entry><literal>8.41</literal></entry>
+ </row>
+
<row>
<entry>
<indexterm>
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index a00db3ce7a..546833cf8d 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -3179,6 +3179,92 @@ numeric_scale(PG_FUNCTION_ARGS)
PG_RETURN_INT32(NUMERIC_DSCALE(num));
}
+/*
+ * Calculate minimal scale. The var should be stripped already.
+ */
+static int
+get_min_scale(NumericVar *var)
+{
+ int min_scale = 0;
+
+ if (var->ndigits > 0)
+ {
+ NumericDigit last_digit;
+
+ /* maximal size of minscale, can be lower */
+ min_scale = (var->ndigits - var->weight - 1) * DEC_DIGITS;
+
+ /*
+ * When there are no digits after decimal point,
+ * the previous expression is negative. In this
+ * case the minscale must be zero.
+ */
+ if (min_scale > 0)
+ {
+ /*
+ * Reduce minscale if trailing digits in last numeric
+ * digits are zero.
+ */
+ last_digit = var->digits[var->ndigits - 1];
+
+ while (last_digit % 10 == 0)
+ {
+ min_scale--;
+ last_digit /= 10;
+ }
+ }
+ else
+ min_scale = 0;
+ }
+
+ return min_scale;
+}
+
+/*
+ * Returns minimal 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);
+ strip_var(&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);
+ strip_var(&result);
+
+ result.dscale = get_min_scale(&result);
+
+ res = make_result(&result);
+ free_var(&result);
+
+ PG_RETURN_NUMERIC(res);
+}
/* ----------------------------------------------------------------------
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ac8f64b219..71a9387652 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4254,6 +4254,14 @@
{ oid => '3281', descr => 'number of decimal digits in the fractional part',
proname => 'scale', prorettype => 'int4', proargtypes => 'numeric',
prosrc => 'numeric_scale' },
+{ oid => '3434',
+ descr => 'minimal scale needed to store the supplied value without data loss',
+ proname => 'min_scale', prorettype => 'int4', proargtypes => 'numeric',
+ prosrc => 'numeric_min_scale' },
+{ oid => '3435',
+ descr => 'numeric with minimal scale needed to represent the given 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' },
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 1cb3c3bfab..91fc90b80d 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2078,6 +2078,192 @@ 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(NULL::numeric) 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(1.1200); -- 2 pos in NDIGITS
+ min_scale
+-----------
+ 2
+(1 row)
+
+select min_scale(1.1230); -- 3 pos in NDIGITS
+ min_scale
+-----------
+ 3
+(1 row)
+
+select min_scale(1.1234); -- all pos in NDIGITS
+ min_scale
+-----------
+ 4
+(1 row)
+
+select min_scale(1.12345000); -- 2 NDIGITS
+ min_scale
+-----------
+ 5
+(1 row)
+
+select min_scale(1.123400000000); -- strip() required/done
+ min_scale
+-----------
+ 4
+(1 row)
+
+select min_scale(12345.123456789012345); -- "big" number
+ min_scale
+-----------
+ 15
+(1 row)
+
+select min_scale(-12345.12345); -- negative number
+ min_scale
+-----------
+ 5
+(1 row)
+
+select min_scale(1e100); -- very big number
+ min_scale
+-----------
+ 0
+(1 row)
+
+select min_scale(1e100::numeric + 0.1); -- big number with scale
+ min_scale
+-----------
+ 1
+(1 row)
+
+--
+-- Tests for trim_scale()
+--
+select trim_scale(numeric 'NaN') = 'NaN':: numeric; -- should be true
+ ?column?
+----------
+ t
+(1 row)
+
+select trim_scale(NULL::numeric) IS NULL; -- should be true
+ ?column?
+----------
+ t
+(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()
--
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index a939412359..c4fb23df3e 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1036,6 +1036,46 @@ 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(NULL::numeric) 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(1.1200); -- 2 pos in NDIGITS
+select min_scale(1.1230); -- 3 pos in NDIGITS
+select min_scale(1.1234); -- all pos in NDIGITS
+select min_scale(1.12345000); -- 2 NDIGITS
+select min_scale(1.123400000000); -- strip() required/done
+select min_scale(12345.123456789012345); -- "big" number
+select min_scale(-12345.12345); -- negative number
+select min_scale(1e100); -- very big number
+select min_scale(1e100::numeric + 0.1); -- big number with scale
+
+--
+-- Tests for trim_scale()
+--
+
+select trim_scale(numeric 'NaN') = 'NaN':: numeric; -- should be true
+select trim_scale(NULL::numeric) IS NULL; -- should be true
+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()
--