Fix behavior of exp() and power() for infinity inputs.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 14 Jun 2020 15:00:07 +0000 (11:00 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 14 Jun 2020 15:00:07 +0000 (11:00 -0400)
Previously, these functions tended to throw underflow errors for
negative-infinity exponents.  The correct thing per POSIX is to
return 0, so let's do that instead.  (Note that the SQL standard
is silent on such issues, as it lacks the concepts of either Inf
or NaN; so our practice is to follow POSIX whenever a corresponding
C-library function exists.)

Also, add a bunch of test cases verifying that exp() and power()
actually do follow POSIX for Inf and NaN inputs.  While this patch
should guarantee that exp() passes the tests, power() will not unless
the platform's pow(3) is fully POSIX-compliant.  I already know that
gaur fails some of the tests, and I am suspicious that the Windows
animals will too; the extent of compliance of other old platforms
remains to be seen.  We might choose to drop failing test cases, or
to work harder at overriding pow(3) for these cases, but first let's
see just how good or bad the situation is.

Discussion: https://postgr.es/m/582552.1591917752@sss.pgh.pa.us

src/backend/utils/adt/float.c
src/test/regress/expected/float8.out
src/test/regress/sql/float8.sql

index 6a717f19bba0b0eb183af2a9ed50d6b5b5c96e9f..84d37de93047580be178c83f34f595936a796c7e 100644 (file)
@@ -1565,7 +1565,7 @@ dpow(PG_FUNCTION_ARGS)
 
    if (unlikely(isinf(result)) && !isinf(arg1) && !isinf(arg2))
        float_overflow_error();
-   if (unlikely(result == 0.0) && arg1 != 0.0)
+   if (unlikely(result == 0.0) && arg1 != 0.0 && !isinf(arg1) && !isinf(arg2))
        float_underflow_error();
 
    PG_RETURN_FLOAT8(result);
@@ -1581,15 +1581,38 @@ dexp(PG_FUNCTION_ARGS)
    float8      arg1 = PG_GETARG_FLOAT8(0);
    float8      result;
 
-   errno = 0;
-   result = exp(arg1);
-   if (errno == ERANGE && result != 0 && !isinf(result))
-       result = get_float8_infinity();
-
-   if (unlikely(isinf(result)) && !isinf(arg1))
-       float_overflow_error();
-   if (unlikely(result == 0.0))
-       float_underflow_error();
+   /*
+    * Handle NaN and Inf cases explicitly.  This avoids needing to assume
+    * that the platform's exp() conforms to POSIX for these cases, and it
+    * removes some edge cases for the overflow checks below.
+    */
+   if (isnan(arg1))
+       result = arg1;
+   else if (isinf(arg1))
+   {
+       /* Per POSIX, exp(-Inf) is 0 */
+       result = (arg1 > 0.0) ? arg1 : 0;
+   }
+   else
+   {
+       /*
+        * On some platforms, exp() will not set errno but just return Inf or
+        * zero to report overflow/underflow; therefore, test both cases.
+        */
+       errno = 0;
+       result = exp(arg1);
+       if (unlikely(errno == ERANGE))
+       {
+           if (result != 0.0)
+               float_overflow_error();
+           else
+               float_underflow_error();
+       }
+       else if (unlikely(isinf(result)))
+           float_overflow_error();
+       else if (unlikely(result == 0.0))
+           float_underflow_error();
+   }
 
    PG_RETURN_FLOAT8(result);
 }
index aaef20bcfdce60b19f2bed60be7e2a0ba2232c13..3957fb58d842c9c027f6822843d7d3e09f9b783e 100644 (file)
@@ -385,6 +385,158 @@ SELECT power(float8 'NaN', float8 '0');
      1
 (1 row)
 
+SELECT power(float8 'inf', float8 '0');
+ power 
+-------
+     1
+(1 row)
+
+SELECT power(float8 '-inf', float8 '0');
+ power 
+-------
+     1
+(1 row)
+
+SELECT power(float8 '0', float8 'inf');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 '0', float8 '-inf');
+ERROR:  zero raised to a negative power is undefined
+SELECT power(float8 '1', float8 'inf');
+ power 
+-------
+     1
+(1 row)
+
+SELECT power(float8 '1', float8 '-inf');
+ power 
+-------
+     1
+(1 row)
+
+SELECT power(float8 '-1', float8 'inf');
+ power 
+-------
+     1
+(1 row)
+
+SELECT power(float8 '-1', float8 '-inf');
+ power 
+-------
+     1
+(1 row)
+
+SELECT power(float8 '0.1', float8 'inf');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 '-0.1', float8 'inf');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 '1.1', float8 'inf');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-1.1', float8 'inf');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '0.1', float8 '-inf');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-0.1', float8 '-inf');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '1.1', float8 '-inf');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 '-1.1', float8 '-inf');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 'inf', float8 '-2');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 'inf', float8 '2');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 'inf', float8 'inf');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 'inf', float8 '-inf');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 '-inf', float8 '-2');
+ power 
+-------
+     0
+(1 row)
+
+SELECT power(float8 '-inf', float8 '-3');
+ power 
+-------
+    -0
+(1 row)
+
+SELECT power(float8 '-inf', float8 '2');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-inf', float8 '3');
+   power   
+-----------
+ -Infinity
+(1 row)
+
+SELECT power(float8 '-inf', float8 'inf');
+  power   
+----------
+ Infinity
+(1 row)
+
+SELECT power(float8 '-inf', float8 '-inf');
+ power 
+-------
+     0
+(1 row)
+
 -- take exp of ln(f.f1)
 SELECT '' AS three, f.f1, exp(ln(f.f1)) AS exp_ln_f1
    FROM FLOAT8_TBL f
@@ -396,6 +548,13 @@ SELECT '' AS three, f.f1, exp(ln(f.f1)) AS exp_ln_f1
        | 1.2345678901234e-200 | 1.23456789012339e-200
 (3 rows)
 
+-- check edge cases for exp
+SELECT exp('inf'::float8), exp('-inf'::float8), exp('nan'::float8);
+   exp    | exp | exp 
+----------+-----+-----
+ Infinity |   0 | NaN
+(1 row)
+
 -- cube root
 SELECT ||/ float8 '27' AS three;
  three 
index e540f03b072cba9715345be357b8f3e8fa2ca644..3a8c737fb280bf59f6edf51c067c37fd488d19c4 100644 (file)
@@ -120,12 +120,41 @@ SELECT power(float8 'NaN', float8 'NaN');
 SELECT power(float8 '-1', float8 'NaN');
 SELECT power(float8 '1', float8 'NaN');
 SELECT power(float8 'NaN', float8 '0');
+SELECT power(float8 'inf', float8 '0');
+SELECT power(float8 '-inf', float8 '0');
+SELECT power(float8 '0', float8 'inf');
+SELECT power(float8 '0', float8 '-inf');
+SELECT power(float8 '1', float8 'inf');
+SELECT power(float8 '1', float8 '-inf');
+SELECT power(float8 '-1', float8 'inf');
+SELECT power(float8 '-1', float8 '-inf');
+SELECT power(float8 '0.1', float8 'inf');
+SELECT power(float8 '-0.1', float8 'inf');
+SELECT power(float8 '1.1', float8 'inf');
+SELECT power(float8 '-1.1', float8 'inf');
+SELECT power(float8 '0.1', float8 '-inf');
+SELECT power(float8 '-0.1', float8 '-inf');
+SELECT power(float8 '1.1', float8 '-inf');
+SELECT power(float8 '-1.1', float8 '-inf');
+SELECT power(float8 'inf', float8 '-2');
+SELECT power(float8 'inf', float8 '2');
+SELECT power(float8 'inf', float8 'inf');
+SELECT power(float8 'inf', float8 '-inf');
+SELECT power(float8 '-inf', float8 '-2');
+SELECT power(float8 '-inf', float8 '-3');
+SELECT power(float8 '-inf', float8 '2');
+SELECT power(float8 '-inf', float8 '3');
+SELECT power(float8 '-inf', float8 'inf');
+SELECT power(float8 '-inf', float8 '-inf');
 
 -- take exp of ln(f.f1)
 SELECT '' AS three, f.f1, exp(ln(f.f1)) AS exp_ln_f1
    FROM FLOAT8_TBL f
    WHERE f.f1 > '0.0';
 
+-- check edge cases for exp
+SELECT exp('inf'::float8), exp('-inf'::float8), exp('nan'::float8);
+
 -- cube root
 SELECT ||/ float8 '27' AS three;