Thread: pgsql: Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^

pgsql: Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^

From
momjian@postgresql.org (Bruce Momjian)
Date:
Log Message:
-----------
Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^ 0.0
return 1, rather than error.

This was already the float8 behavior.

Modified Files:
--------------
    pgsql/src/backend/utils/adt:
        numeric.c (r1.110 -> r1.111)
        (http://anoncvs.postgresql.org/cvsweb.cgi/pgsql/src/backend/utils/adt/numeric.c?r1=1.110&r2=1.111)

On Thu, 2008-05-08 at 19:25 +0000, Bruce Momjian wrote:
> Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^ 0.0
> return 1, rather than error.

A regression test for this behavior would be useful, I think.

-Neil



Neil Conway wrote:
> On Thu, 2008-05-08 at 19:25 +0000, Bruce Momjian wrote:
> > Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^ 0.0
> > return 1, rather than error.
>
> A regression test for this behavior would be useful, I think.

Done, plus I wasn't happy with the original patch so I redid it to be
more modular, also attached.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: src/backend/utils/adt/numeric.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v
retrieving revision 1.111
diff -c -c -r1.111 numeric.c
*** src/backend/utils/adt/numeric.c    8 May 2008 19:25:38 -0000    1.111
--- src/backend/utils/adt/numeric.c    8 May 2008 22:16:55 -0000
***************
*** 5170,5190 ****
      int            local_rscale;
      double        val;

-     /*
-      *    This avoids log(0) for cases of 0 raised to a non-integer.
-      *    Also, while 0 ^ 0 can be either 1 or indeterminate (error), we
-      *    treat it as one because most programming languages do this.
-      *    http://en.wikipedia.org/wiki/Exponentiation#Zero_to_the_zero_power
-      */
-     if (cmp_var(base, &const_zero) == 0)
-     {
-         if (cmp_var(exp, &const_zero) == 0)
-             set_var_from_var(&const_one, result);
-         else
-             set_var_from_var(&const_zero, result);
-         return;
-     }
-
      /* If exp can be represented as an integer, use power_var_int */
      if (exp->ndigits == 0 || exp->ndigits <= exp->weight + 1)
      {
--- 5170,5175 ----
***************
*** 5217,5222 ****
--- 5202,5218 ----
          free_var(&x);
      }

+     /*
+      *    This avoids log(0) for cases of 0 raised to a non-integer.
+      *    0 ^ 0 handled by power_var_int().
+      */
+     if (cmp_var(base, &const_zero) == 0)
+     {
+         set_var_from_var(&const_zero, result);
+         result->dscale = NUMERIC_MIN_SIG_DIGITS;    /* no need to round */
+         return;
+     }
+
      init_var(&ln_base);
      init_var(&ln_num);

***************
*** 5284,5289 ****
--- 5280,5290 ----
      switch (exp)
      {
          case 0:
+             /*
+              *    While 0 ^ 0 can be either 1 or indeterminate (error), we
+              *    treat it as 1 because most programming languages do this.
+              *    http://en.wikipedia.org/wiki/Exponentiation#Zero_to_the_zero_power
+              */
              set_var_from_var(&const_one, result);
              result->dscale = rscale;    /* no need to round */
              return;
Index: src/test/regress/expected/float8.out
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/expected/float8.out,v
retrieving revision 1.25
diff -c -c -r1.25 float8.out
*** src/test/regress/expected/float8.out    2 Jan 2007 20:00:50 -0000    1.25
--- src/test/regress/expected/float8.out    8 May 2008 22:16:56 -0000
***************
*** 349,354 ****
--- 349,360 ----
  ERROR:  value out of range: overflow
  SELECT '' AS bad, f.f1 ^ '1e200' from FLOAT8_TBL f;
  ERROR:  value out of range: overflow
+ SELECT 0 ^ 0 + 0 ^ 1 + 0 ^ 0.0 + 0 ^ 0.5;
+  ?column?
+ ----------
+         2
+ (1 row)
+
  SELECT '' AS bad, ln(f.f1) from FLOAT8_TBL f where f.f1 = '0.0' ;
  ERROR:  cannot take logarithm of zero
  SELECT '' AS bad, ln(f.f1) from FLOAT8_TBL f where f.f1 < '0.0' ;
Index: src/test/regress/sql/float8.sql
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/sql/float8.sql,v
retrieving revision 1.15
diff -c -c -r1.15 float8.sql
*** src/test/regress/sql/float8.sql    8 Jun 2005 21:15:29 -0000    1.15
--- src/test/regress/sql/float8.sql    8 May 2008 22:16:56 -0000
***************
*** 129,134 ****
--- 129,136 ----

  SELECT '' AS bad, f.f1 ^ '1e200' from FLOAT8_TBL f;

+ SELECT 0 ^ 0 + 0 ^ 1 + 0 ^ 0.0 + 0 ^ 0.5;
+
  SELECT '' AS bad, ln(f.f1) from FLOAT8_TBL f where f.f1 = '0.0' ;

  SELECT '' AS bad, ln(f.f1) from FLOAT8_TBL f where f.f1 < '0.0' ;

momjian@postgresql.org (Bruce Momjian) writes:
> Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^ 0.0
> return 1, rather than error.

This is wrongly described, and the implementation is still not correct
either, because it should throw an error for negative exponents.
Would you please *read* that wikipedia page you keep citing?
http://en.wikipedia.org/wiki/Exponentiation#Powers_of_zero

            regards, tom lane

Tom Lane wrote:
> momjian@postgresql.org (Bruce Momjian) writes:
> > Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^ 0.0
> > return 1, rather than error.
>
> This is wrongly described, and the implementation is still not correct
> either, because it should throw an error for negative exponents.
> Would you please *read* that wikipedia page you keep citing?
> http://en.wikipedia.org/wiki/Exponentiation#Powers_of_zero

I think this is fixed in the version I just committed:

    test=> select 0 ^ (-1);
    ERROR:  invalid argument for power function
    test=> select 0 ^ (-1.0);
    ERROR:  invalid argument for power function

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

On Thu, 2008-05-08 at 18:34 -0400, Bruce Momjian wrote:
> Tom Lane wrote:
> > momjian@postgresql.org (Bruce Momjian) writes:
> > > Have numeric 0 ^ 4.3 return 1, rather than an error, and have 0 ^ 0.0
> > > return 1, rather than error.
> >
> > This is wrongly described, and the implementation is still not correct
> > either, because it should throw an error for negative exponents.
> > Would you please *read* that wikipedia page you keep citing?
> > http://en.wikipedia.org/wiki/Exponentiation#Powers_of_zero
>
> I think this is fixed in the version I just committed:
>
>     test=> select 0 ^ (-1);
>     ERROR:  invalid argument for power function
>     test=> select 0 ^ (-1.0);
>     ERROR:  invalid argument for power function

Hopefully this only occurs for 0 ^ (n)? A negative exponent isn't a
problem for y ^ x when y <> 0 and x < 0. Just checking you don't just
throw out an error for any negative exponent, which is what "invalid
argument" sounds like, to me.

Wikipedia says that exponentiation of zero to a negative power implies
division by zero, so shouldn't we throw a "division by zero" error?

--
  Simon Riggs
  2ndQuadrant  http://www.2ndQuadrant.com


Simon Riggs <simon@2ndquadrant.com> writes:
> Wikipedia says that exponentiation of zero to a negative power implies
> division by zero, so shouldn't we throw a "division by zero" error?

I think it should be a specific message like "zero raised to a negative
power is undefined".  It's not like it's going to take us any extra code
to know that we are faced with that case.

BTW, I realized that SQL:2003 spells it all out for us in explicit
detail:

12)If <power function> is specified, then let NVEB be the <numeric value
expression base>, then let VB be the value of NVEB, let NVEE be the
<numeric value expression exponent>, and let VE be the value of NVEE.

Case:

a) If either VB or VE is the null value, then the result is the null value.

b) If VB is 0 (zero) and VE is negative, then an exception condition is
raised: data exception � invalid argument for power function.

c) If VB is 0 (zero) and VE is 0 (zero), then the result is 1 (one).

d) If VB is 0 (zero) and VE is positive, then the result is 0 (zero).

e) If VB is negative and VE is not equal to an exact numeric value with
scale 0 (zero), then an exception condition is raised: data exception
� invalid argument for power function.

f) If VB is negative and VE is equal to an exact numeric value with
scale 0 (zero) that is an even number, then the result is the result of
    EXP(NVEE*LN(-NVEB))

g) If VB is negative and VE is equal to an exact numeric value with
scale 0 (zero) that is an odd number, then the result is the result of
    -EXP(NVEE*LN(-NVEB))

h) Otherwise, the result is the result of
    EXP(NVEE*LN(NVEB))


            regards, tom lane

Tom Lane wrote:
> Simon Riggs <simon@2ndquadrant.com> writes:
> > Wikipedia says that exponentiation of zero to a negative power implies
> > division by zero, so shouldn't we throw a "division by zero" error?
>
> I think it should be a specific message like "zero raised to a negative
> power is undefined".  It's not like it's going to take us any extra code
> to know that we are faced with that case.
>
> BTW, I realized that SQL:2003 spells it all out for us in explicit
> detail:

...

> b) If VB is 0 (zero) and VE is negative, then an exception condition is
> raised: data exception � invalid argument for power function.

Well, this indicates we shouldn't return "zero raised to a negative
power is undefined", but rather the power error we are giving now, or
are you saying we should return the "power" error code but an error
message mentioning zero?

> c) If VB is 0 (zero) and VE is 0 (zero), then the result is 1 (one).

I have updated the C comments to mention the spec also requires we
return 1 in this case.

C comment updated attached and applied.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: src/backend/utils/adt/float.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/adt/float.c,v
retrieving revision 1.155
diff -c -c -r1.155 float.c
*** src/backend/utils/adt/float.c    21 Apr 2008 00:26:45 -0000    1.155
--- src/backend/utils/adt/float.c    9 May 2008 15:34:53 -0000
***************
*** 1331,1337 ****

      /*
       * The SQL spec requires that we emit a particular SQLSTATE error code for
!      * certain error conditions.
       */
      if ((arg1 == 0 && arg2 < 0) ||
          (arg1 < 0 && floor(arg2) != arg2))
--- 1331,1338 ----

      /*
       * The SQL spec requires that we emit a particular SQLSTATE error code for
!      * certain error conditions.  Specifically, we don't return a divide-by-zero
!      * error code for 0 ^ -1.
       */
      if ((arg1 == 0 && arg2 < 0) ||
          (arg1 < 0 && floor(arg2) != arg2))
Index: src/backend/utils/adt/numeric.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v
retrieving revision 1.112
diff -c -c -r1.112 numeric.c
*** src/backend/utils/adt/numeric.c    8 May 2008 22:17:54 -0000    1.112
--- src/backend/utils/adt/numeric.c    9 May 2008 15:34:53 -0000
***************
*** 1893,1900 ****
      trunc_var(&arg2_trunc, 0);

      /*
!      * Return special SQLSTATE error codes for a few conditions mandated by
!      * the standard.
       */
      if ((cmp_var(&arg1, &const_zero) == 0 &&
           cmp_var(&arg2, &const_zero) < 0) ||
--- 1893,1901 ----
      trunc_var(&arg2_trunc, 0);

      /*
!      * The SQL spec requires that we emit a particular SQLSTATE error code for
!      * certain error conditions.  Specifically, we don't return a divide-by-zero
!      * error code for 0 ^ -1.
       */
      if ((cmp_var(&arg1, &const_zero) == 0 &&
           cmp_var(&arg2, &const_zero) < 0) ||
***************
*** 5283,5288 ****
--- 5284,5290 ----
              /*
               *    While 0 ^ 0 can be either 1 or indeterminate (error), we
               *    treat it as 1 because most programming languages do this.
+              *    SQL:2003 also requires a return value of 1.
               *    http://en.wikipedia.org/wiki/Exponentiation#Zero_to_the_zero_power
               */
              set_var_from_var(&const_one, result);

Bruce Momjian <bruce@momjian.us> writes:
>> b) If VB is 0 (zero) and VE is negative, then an exception condition is
>> raised: data exception � invalid argument for power function.

> Well, this indicates we shouldn't return "zero raised to a negative
> power is undefined", but rather the power error we are giving now, or
> are you saying we should return the "power" error code but an error
> message mentioning zero?

The spec says what the SQLSTATE code should be.   We have always felt
free to word the message text more specifically than that, though.

            regards, tom lane

Tom Lane wrote:
> Bruce Momjian <bruce@momjian.us> writes:
> >> b) If VB is 0 (zero) and VE is negative, then an exception condition is
> >> raised: data exception � invalid argument for power function.
>
> > Well, this indicates we shouldn't return "zero raised to a negative
> > power is undefined", but rather the power error we are giving now, or
> > are you saying we should return the "power" error code but an error
> > message mentioning zero?
>
> The spec says what the SQLSTATE code should be.   We have always felt
> free to word the message text more specifically than that, though.

OK, I will work on it then.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +

Tom Lane wrote:
> Bruce Momjian <bruce@momjian.us> writes:
> >> b) If VB is 0 (zero) and VE is negative, then an exception condition is
> >> raised: data exception � invalid argument for power function.
>
> > Well, this indicates we shouldn't return "zero raised to a negative
> > power is undefined", but rather the power error we are giving now, or
> > are you saying we should return the "power" error code but an error
> > message mentioning zero?
>
> The spec says what the SQLSTATE code should be.   We have always felt
> free to word the message text more specifically than that, though.

OK, error wording updated, attached, and applied.

--
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + If your life is a hard drive, Christ can be your backup. +
Index: src/backend/utils/adt/float.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/adt/float.c,v
retrieving revision 1.156
diff -c -c -r1.156 float.c
*** src/backend/utils/adt/float.c    9 May 2008 15:36:06 -0000    1.156
--- src/backend/utils/adt/float.c    9 May 2008 21:29:37 -0000
***************
*** 1334,1344 ****
       * certain error conditions.  Specifically, we don't return a divide-by-zero
       * error code for 0 ^ -1.
       */
!     if ((arg1 == 0 && arg2 < 0) ||
!         (arg1 < 0 && floor(arg2) != arg2))
          ereport(ERROR,
                  (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
!                  errmsg("invalid argument for power function")));

      /*
       * pow() sets errno only on some platforms, depending on whether it
--- 1334,1347 ----
       * certain error conditions.  Specifically, we don't return a divide-by-zero
       * error code for 0 ^ -1.
       */
!     if (arg1 == 0 && arg2 < 0)
          ereport(ERROR,
                  (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
!                  errmsg("zero raised to a negative power is undefined")));
!     if (arg1 < 0 && floor(arg2) != arg2)
!         ereport(ERROR,
!                 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
!                  errmsg("a negative number raised to a non-integer power yields a complex result")));

      /*
       * pow() sets errno only on some platforms, depending on whether it
Index: src/backend/utils/adt/numeric.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v
retrieving revision 1.113
diff -c -c -r1.113 numeric.c
*** src/backend/utils/adt/numeric.c    9 May 2008 15:36:06 -0000    1.113
--- src/backend/utils/adt/numeric.c    9 May 2008 21:29:37 -0000
***************
*** 1897,1909 ****
       * certain error conditions.  Specifically, we don't return a divide-by-zero
       * error code for 0 ^ -1.
       */
!     if ((cmp_var(&arg1, &const_zero) == 0 &&
!          cmp_var(&arg2, &const_zero) < 0) ||
!         (cmp_var(&arg1, &const_zero) < 0 &&
!          cmp_var(&arg2, &arg2_trunc) != 0))
          ereport(ERROR,
                  (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
!                  errmsg("invalid argument for power function")));

      /*
       * Call power_var() to compute and return the result; note it handles
--- 1897,1913 ----
       * certain error conditions.  Specifically, we don't return a divide-by-zero
       * error code for 0 ^ -1.
       */
!     if (cmp_var(&arg1, &const_zero) == 0 &&
!         cmp_var(&arg2, &const_zero) < 0)
          ereport(ERROR,
                  (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
!                  errmsg("zero raised to a negative power is undefined")));
!
!     if (cmp_var(&arg1, &const_zero) < 0 &&
!         cmp_var(&arg2, &arg2_trunc) != 0)
!         ereport(ERROR,
!                 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
!                  errmsg("a negative number raised to a non-integer power yields a complex result")));

      /*
       * Call power_var() to compute and return the result; note it handles