Re: Bug in numeric multiplication - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Bug in numeric multiplication
Date
Msg-id 17848.1447804663@sss.pgh.pa.us
Whole thread Raw
In response to Re: Bug in numeric multiplication  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Bug in numeric multiplication  (Dean Rasheed <dean.a.rasheed@gmail.com>)
List pgsql-hackers
I wrote:
> I pushed this, but while looking at it, my attention was drawn to this
> bit down near the end of the loop:

>         /*
>          * The dividend digit we are about to replace might still be nonzero.
>          * Fold it into the next digit position.  We don't need to worry about
>          * overflow here since this should nearly cancel with the subtraction
>          * of the divisor.
>          */
>         div[qi + 1] += div[qi] * NBASE;

> In the first place, I'm not feeling especially convinced by that handwave
> about there being no risk of overflow.  In the second place, this is
> indisputably failing to consider whether maxdiv might need to increase.
> If I add
>         Assert(Abs(div[qi + 1]) <= (maxdiv * (NBASE-1)));
> right after this, the regression tests blow up.  So it looks to me like
> we've got some more work to do.

After thinking some more about what this is doing, it seems to me that
we could avoid changing div[qi + 1] at all here, and instead deal with
leftover dividend digits by shoving them into the floating-point part of
the calculation.  All that we really need to have happen as a consequence
of the above is to inject an additional "div[qi] * NBASE" term into future
calculations of fdividend.  So we can do that out-of-band and avoid
mucking up the maxdiv invariant.

Also, I realized that this bit:

                /*
                 * All the div[] digits except possibly div[qi] are now in the
                 * range 0..NBASE-1.
                 */
                maxdiv = Abs(newdig) / (NBASE - 1);
                maxdiv = Max(maxdiv, 1);

is unduly conservative, because, while indeed div[qi] might be out of
range, there is no need to consider it in maxdiv anymore, because the new
maxdiv value only determines what will happen in future loop iterations
where the current div[qi] will be irrelevant.  So we should just reset
maxdiv to 1 unconditionally here.

So that led me to the attached patch, which passes regression tests fine.
I'm not sure that it's provably okay though.  The loose ends are to show
that div[qi] can't overflow an int during the divisor-subtraction step
and that "outercarry" remains bounded.   Testing suggests that outercarry
can't exceed INT_MAX/NBASE, but I don't see how to prove that.

            regards, tom lane

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index dcdc5cf..03a646b 100644
*** a/src/backend/utils/adt/numeric.c
--- b/src/backend/utils/adt/numeric.c
*************** div_var_fast(NumericVar *var1, NumericVa
*** 6186,6192 ****
      double        fdividend,
                  fdivisor,
                  fdivisorinverse,
!                 fquotient;
      int            qi;
      int            i;

--- 6186,6193 ----
      double        fdividend,
                  fdivisor,
                  fdivisorinverse,
!                 fquotient,
!                 outercarry;
      int            qi;
      int            i;

*************** div_var_fast(NumericVar *var1, NumericVa
*** 6274,6289 ****
       * To avoid overflow in maxdiv itself, it represents the max absolute
       * value divided by NBASE-1, ie, at the top of the loop it is known that
       * no div[] entry has an absolute value exceeding maxdiv * (NBASE-1).
       */
      maxdiv = 1;

      /*
!      * Outer loop computes next quotient digit, which will go into div[qi]
       */
      for (qi = 0; qi < div_ndigits; qi++)
      {
          /* Approximate the current dividend value */
!         fdividend = (double) div[qi];
          for (i = 1; i < 4; i++)
          {
              fdividend *= NBASE;
--- 6275,6297 ----
       * To avoid overflow in maxdiv itself, it represents the max absolute
       * value divided by NBASE-1, ie, at the top of the loop it is known that
       * no div[] entry has an absolute value exceeding maxdiv * (NBASE-1).
+      *
+      * Note that maxdiv only includes digit positions that are still part of
+      * the dividend, ie, strictly speaking the above holds only for div[i]
+      * where i >= qi, at the top of the loop.
       */
      maxdiv = 1;

      /*
!      * Outer loop computes next quotient digit, which will go into div[qi].
!      * "outercarry" represents high-order dividend digits carried across
!      * iterations.
       */
+     outercarry = 0;
      for (qi = 0; qi < div_ndigits; qi++)
      {
          /* Approximate the current dividend value */
!         fdividend = outercarry * NBASE + (double) div[qi];
          for (i = 1; i < 4; i++)
          {
              fdividend *= NBASE;
*************** div_var_fast(NumericVar *var1, NumericVa
*** 6320,6340 ****
                          carry = 0;
                      div[i] = newdig;
                  }
!                 newdig = div[qi] + carry;
!                 div[qi] = newdig;

                  /*
                   * All the div[] digits except possibly div[qi] are now in the
!                  * range 0..NBASE-1.
                   */
!                 maxdiv = Abs(newdig) / (NBASE - 1);
!                 maxdiv = Max(maxdiv, 1);

                  /*
                   * Recompute the quotient digit since new info may have
                   * propagated into the top four dividend digits
                   */
!                 fdividend = (double) div[qi];
                  for (i = 1; i < 4; i++)
                  {
                      fdividend *= NBASE;
--- 6328,6347 ----
                          carry = 0;
                      div[i] = newdig;
                  }
!                 div[qi] += carry;

                  /*
                   * All the div[] digits except possibly div[qi] are now in the
!                  * range 0..NBASE-1; and we don't care about including div[qi]
!                  * anymore, so we can reset maxdiv to 1.
                   */
!                 maxdiv = 1;

                  /*
                   * Recompute the quotient digit since new info may have
                   * propagated into the top four dividend digits
                   */
!                 fdividend = outercarry * NBASE + (double) div[qi];
                  for (i = 1; i < 4; i++)
                  {
                      fdividend *= NBASE;
*************** div_var_fast(NumericVar *var1, NumericVa
*** 6360,6370 ****

          /*
           * The dividend digit we are about to replace might still be nonzero.
!          * Fold it into the next digit position.  We don't need to worry about
!          * overflow here since this should nearly cancel with the subtraction
!          * of the divisor.
           */
!         div[qi + 1] += div[qi] * NBASE;

          div[qi] = qdigit;
      }
--- 6367,6382 ----

          /*
           * The dividend digit we are about to replace might still be nonzero.
!          * Carry it forward to future division steps in "outercarry".
!          *
!          * Although it might appear that outercarry would soon grow large
!          * enough to lose precision, this is not actually possible because
!          * each divisor-subtraction step will drive the accumulated value
!          * towards zero.  We could probably store it in an int without
!          * overflow problems, but since the value is only used in floating
!          * calculations, we might as well store it as a double.
           */
!         outercarry = outercarry * NBASE + (double) div[qi];

          div[qi] = qdigit;
      }
*************** div_var_fast(NumericVar *var1, NumericVa
*** 6372,6378 ****
      /*
       * Approximate and store the last quotient digit (div[div_ndigits])
       */
!     fdividend = (double) div[qi];
      for (i = 1; i < 4; i++)
          fdividend *= NBASE;
      fquotient = fdividend * fdivisorinverse;
--- 6384,6390 ----
      /*
       * Approximate and store the last quotient digit (div[div_ndigits])
       */
!     fdividend = outercarry * NBASE + (double) div[qi];
      for (i = 1; i < 4; i++)
          fdividend *= NBASE;
      fquotient = fdividend * fdivisorinverse;

pgsql-hackers by date:

Previous
From: Kouhei Kaigai
Date:
Subject: Re: Foreign join pushdown vs EvalPlanQual
Next
From: Michael Paquier
Date:
Subject: Re: [COMMITTERS] pgsql: Cause TestLib.pm to define $windows_os in all branches.