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()
 --