From decbe2bfb1051c5ab6c382b19e1d909e34227a22 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 14 Jun 2020 11:00:07 -0400 Subject: [PATCH] Fix behavior of exp() and power() for infinity inputs. 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 | 43 ++++++-- src/test/regress/expected/float8.out | 159 +++++++++++++++++++++++++++ src/test/regress/sql/float8.sql | 29 +++++ 3 files changed, 221 insertions(+), 10 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 6a717f19bba..84d37de9304 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -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); } diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out index aaef20bcfdc..3957fb58d84 100644 --- a/src/test/regress/expected/float8.out +++ b/src/test/regress/expected/float8.out @@ -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 diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql index e540f03b072..3a8c737fb28 100644 --- a/src/test/regress/sql/float8.sql +++ b/src/test/regress/sql/float8.sql @@ -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;