Add functions min_scale(numeric) and trim_scale(numeric).
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Jan 2020 17:13:53 +0000 (12:13 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Jan 2020 17:13:53 +0000 (12:13 -0500)
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

doc/src/sgml/func.sgml
src/backend/utils/adt/numeric.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/test/regress/expected/numeric.out
src/test/regress/sql/numeric.sql

index 57a1539506d00755dfcd49252ffc7edd6e8d53f4..4b42f12862587e9e033ff11cb4426acad31da2d4 100644 (file)
        <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>
        </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>
        <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>
index 14054272c89fcb6b3daeffd67c1100427f49b3eb..76a597e56fa4dad0995ddaeb5ca87d31126f7498 100644 (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);
+}
 
 
 /* ----------------------------------------------------------------------
index 8974625bb27920143470e6fe320959aaaaab9030..cbd36bcab4c3d97143a8c45ad4b73a64ce84097b 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201911241
+#define CATALOG_VERSION_NO     202001061
 
 #endif
index 0b6045acb1ea9eabb5a36ed189e6af9a54ad91e4..59f1ff01ab804f8b03fbb7bbe352a215763e0806 100644 (file)
 { 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' },
index 1cb3c3bfab717cfe09fc953aedf6213b6c8b868d..8acfa3942451d315f03a1a33f84b19147aa3838d 100644 (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()
 --
index a939412359625e2d910280f58bb24980f0995c35..e611cc4d8dc3b6eb3fba43b63ed2e1958fd58d2f 100644 (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()
 --