> > > > For most places it'd probably end up being easier to read and to > > > > optimize if we just wrote them as > > > > if (unlikely(isinf(result)) && !isinf(arg)) > > > > float_overflow_error(); > > > > and when needed added a > > > > else if (unlikely(result == 0) && arg1 != 0.0) > > > > float_underflow_error(); > > > > > > +1 > > > > Cool. Emre, any chance you could write a patch along those lines? > > Yes, I am happy to do. It makes more sense to me too.
How about the one attached?
From 161384d51f517f1f4d9f403b46e90fbe1c869cbe Mon Sep 17 00:00:00 2001 From: Emre Hasegeli <e...@hasegeli.com> Date: Fri, 7 Feb 2020 10:27:25 +0000 Subject: [PATCH] Optimize float overflow/underflow checks The inline functions added by 6bf0bc842b caused the conditions of overflow/underflow checks to be evaluated when no overflow/underflow happen. This slowed down floating point operations. This commit inlines the checks to prevent the performance regression. This also moves the error messages further out of line. All these otherwise very small functions having their own ereports() was making them much bigger. Our low code density, and the resulting rate of ITLB misses, is pretty significant cost. And also this commit is changing the usage of unlikely() to cover the whole condition. Using it only for the result is not semantically correct. It is more than likely for the result to be infinite when the input is, or it to be 0 when the input is. Reported-by: Keisuke Kuroda <keisuke.kuroda.3...@gmail.com> Discussion: https://postgr.es/m/CANDwggLe1Gc1OrRqvPfGE%3DkM9K0FSfia0hbeFCEmwabhLz95AA%40mail.gmail.com --- src/backend/utils/adt/float.c | 146 ++++++++++++++++++++++---------- src/backend/utils/adt/geo_ops.c | 5 +- src/include/utils/float.h | 67 ++++++++------- 3 files changed, 145 insertions(+), 73 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index a90d4db215..30abe06b87 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -79,20 +79,35 @@ static void init_degree_constants(void); * function, which causes configure to not set HAVE_CBRT. Furthermore, * their compilers spit up at the mismatch between extern declaration * and static definition. We work around that here by the expedient * of a #define to make the actual name of the static function different. */ #define cbrt my_cbrt static double cbrt(double x); #endif /* HAVE_CBRT */ +void float_overflow_error() +{ + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: overflow"))); +} + +void float_underflow_error() +{ + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value out of range: underflow"))); +} + + /* * Returns -1 if 'val' represents negative infinity, 1 if 'val' * represents (positive) infinity, and 0 otherwise. On some platforms, * this is equivalent to the isinf() macro, but not everywhere: C99 * does not specify that isinf() needs to distinguish between positive * and negative infinity. */ int is_infinite(double val) { @@ -1183,24 +1198,29 @@ ftod(PG_FUNCTION_ARGS) } /* * dtof - converts a float8 number to a float4 number */ Datum dtof(PG_FUNCTION_ARGS) { float8 num = PG_GETARG_FLOAT8(0); + float4 result; - check_float4_val((float4) num, isinf(num), num == 0); + result = (float4) num; + if (unlikely(isinf(result) && !isinf(num))) + float_overflow_error(); + if (unlikely(result == 0.0f && num != 0.0f)) + float_underflow_error(); - PG_RETURN_FLOAT4((float4) num); + PG_RETURN_FLOAT4(result); } /* * dtoi4 - converts a float8 number to an int4 number */ Datum dtoi4(PG_FUNCTION_ARGS) { float8 num = PG_GETARG_FLOAT8(0); @@ -1437,37 +1457,44 @@ dsqrt(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); float8 result; if (arg1 < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), errmsg("cannot take square root of a negative number"))); result = sqrt(arg1); + if (unlikely(isinf(result) && !isinf(arg1))) + float_overflow_error(); + if (unlikely(result == 0.0 && arg1 != 0.0)) + float_underflow_error(); - check_float8_val(result, isinf(arg1), arg1 == 0); PG_RETURN_FLOAT8(result); } /* * dcbrt - returns cube root of arg1 */ Datum dcbrt(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); float8 result; result = cbrt(arg1); - check_float8_val(result, isinf(arg1), arg1 == 0); + if (unlikely(isinf(result) && !isinf(arg1))) + float_overflow_error(); + if (unlikely(result == 0.0 && arg1 != 0.0)) + float_underflow_error(); + PG_RETURN_FLOAT8(result); } /* * dpow - returns pow(arg1,arg2) */ Datum dpow(PG_FUNCTION_ARGS) { @@ -1525,40 +1552,48 @@ dpow(PG_FUNCTION_ARGS) /* The sign of Inf is not significant in this case. */ result = get_float8_infinity(); else if (fabs(arg1) != 1) result = 0; else result = 1; } else if (errno == ERANGE && result != 0 && !isinf(result)) result = get_float8_infinity(); - check_float8_val(result, isinf(arg1) || isinf(arg2), arg1 == 0); + if (unlikely(isinf(result) && !isinf(arg1) && !isinf(arg2))) + float_overflow_error(); + if (unlikely(result == 0.0 && arg1 != 0.0)) + float_underflow_error(); + PG_RETURN_FLOAT8(result); } /* * dexp - returns the exponential function of arg1 */ Datum 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(); - check_float8_val(result, isinf(arg1), false); + if (unlikely(isinf(result) && !isinf(arg1))) + float_overflow_error(); + if (unlikely(result == 0.0)) + float_underflow_error(); + PG_RETURN_FLOAT8(result); } /* * dlog1 - returns the natural logarithm of arg1 */ Datum dlog1(PG_FUNCTION_ARGS) { @@ -1572,22 +1607,25 @@ dlog1(PG_FUNCTION_ARGS) if (arg1 == 0.0) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), errmsg("cannot take logarithm of zero"))); if (arg1 < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), errmsg("cannot take logarithm of a negative number"))); result = log(arg1); + if (unlikely(isinf(result) && !isinf(arg1))) + float_overflow_error(); + if (unlikely(result == 0.0 && arg1 != 1.0)) + float_underflow_error(); - check_float8_val(result, isinf(arg1), arg1 == 1); PG_RETURN_FLOAT8(result); } /* * dlog10 - returns the base 10 logarithm of arg1 */ Datum dlog10(PG_FUNCTION_ARGS) { @@ -1602,22 +1640,25 @@ dlog10(PG_FUNCTION_ARGS) if (arg1 == 0.0) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), errmsg("cannot take logarithm of zero"))); if (arg1 < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG), errmsg("cannot take logarithm of a negative number"))); result = log10(arg1); + if (unlikely(isinf(result) && !isinf(arg1))) + float_overflow_error(); + if (unlikely(result == 0.0 && arg1 != 1.0)) + float_underflow_error(); - check_float8_val(result, isinf(arg1), arg1 == 1); PG_RETURN_FLOAT8(result); } /* * dacos - returns the arccos of arg1 (radians) */ Datum dacos(PG_FUNCTION_ARGS) { @@ -1632,22 +1673,23 @@ dacos(PG_FUNCTION_ARGS) * The principal branch of the inverse cosine function maps values in the * range [-1, 1] to values in the range [0, Pi], so we should reject any * inputs outside that range and the result will always be finite. */ if (arg1 < -1.0 || arg1 > 1.0) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); result = acos(arg1); + if (unlikely(isinf(result))) + float_overflow_error(); - check_float8_val(result, false, true); PG_RETURN_FLOAT8(result); } /* * dasin - returns the arcsin of arg1 (radians) */ Datum dasin(PG_FUNCTION_ARGS) { @@ -1662,22 +1704,23 @@ dasin(PG_FUNCTION_ARGS) * The principal branch of the inverse sine function maps values in the * range [-1, 1] to values in the range [-Pi/2, Pi/2], so we should reject * any inputs outside that range and the result will always be finite. */ if (arg1 < -1.0 || arg1 > 1.0) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); result = asin(arg1); + if (unlikely(isinf(result))) + float_overflow_error(); - check_float8_val(result, false, true); PG_RETURN_FLOAT8(result); } /* * datan - returns the arctan of arg1 (radians) */ Datum datan(PG_FUNCTION_ARGS) { @@ -1687,22 +1730,23 @@ datan(PG_FUNCTION_ARGS) /* Per the POSIX spec, return NaN if the input is NaN */ if (isnan(arg1)) PG_RETURN_FLOAT8(get_float8_nan()); /* * The principal branch of the inverse tangent function maps all inputs to * values in the range [-Pi/2, Pi/2], so the result should always be * finite, even if the input is infinite. */ result = atan(arg1); + if (unlikely(isinf(result))) + float_overflow_error(); - check_float8_val(result, false, true); PG_RETURN_FLOAT8(result); } /* * atan2 - returns the arctan of arg1/arg2 (radians) */ Datum datan2(PG_FUNCTION_ARGS) { @@ -1712,22 +1756,23 @@ datan2(PG_FUNCTION_ARGS) /* Per the POSIX spec, return NaN if either input is NaN */ if (isnan(arg1) || isnan(arg2)) PG_RETURN_FLOAT8(get_float8_nan()); /* * atan2 maps all inputs to values in the range [-Pi, Pi], so the result * should always be finite, even if the inputs are infinite. */ result = atan2(arg1, arg2); + if (unlikely(isinf(result))) + float_overflow_error(); - check_float8_val(result, false, true); PG_RETURN_FLOAT8(result); } /* * dcos - returns the cosine of arg1 (radians) */ Datum dcos(PG_FUNCTION_ARGS) { @@ -1752,22 +1797,23 @@ dcos(PG_FUNCTION_ARGS) * should return a domain error; but we won't notice that unless the * platform reports via errno, so also explicitly test for infinite * inputs. */ errno = 0; result = cos(arg1); if (errno != 0 || isinf(arg1)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); + if (unlikely(isinf(result))) + float_overflow_error(); - check_float8_val(result, false, true); PG_RETURN_FLOAT8(result); } /* * dcot - returns the cotangent of arg1 (radians) */ Datum dcot(PG_FUNCTION_ARGS) { @@ -1780,21 +1826,22 @@ dcot(PG_FUNCTION_ARGS) /* Be sure to throw an error if the input is infinite --- see dcos() */ errno = 0; result = tan(arg1); if (errno != 0 || isinf(arg1)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); result = 1.0 / result; - check_float8_val(result, true /* cot(0) == Inf */ , true); + /* Not checking for overflow because cot(0) == Inf */ + PG_RETURN_FLOAT8(result); } /* * dsin - returns the sine of arg1 (radians) */ Datum dsin(PG_FUNCTION_ARGS) { @@ -1805,22 +1852,23 @@ dsin(PG_FUNCTION_ARGS) if (isnan(arg1)) PG_RETURN_FLOAT8(get_float8_nan()); /* Be sure to throw an error if the input is infinite --- see dcos() */ errno = 0; result = sin(arg1); if (errno != 0 || isinf(arg1)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); + if (unlikely(isinf(result))) + float_overflow_error(); - check_float8_val(result, false, true); PG_RETURN_FLOAT8(result); } /* * dtan - returns the tangent of arg1 (radians) */ Datum dtan(PG_FUNCTION_ARGS) { @@ -1831,22 +1879,22 @@ dtan(PG_FUNCTION_ARGS) if (isnan(arg1)) PG_RETURN_FLOAT8(get_float8_nan()); /* Be sure to throw an error if the input is infinite --- see dcos() */ errno = 0; result = tan(arg1); if (errno != 0 || isinf(arg1)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); + /* Not checking for overflow because tan(pi/2) == Inf */ - check_float8_val(result, true /* tan(pi/2) == Inf */ , true); PG_RETURN_FLOAT8(result); } /* ========== DEGREE-BASED TRIGONOMETRIC FUNCTIONS ========== */ /* * Initialize the cached constants declared at the head of this file * (sin_30 etc). The fact that we need those at all, let alone need this @@ -1984,21 +2032,23 @@ dacosd(PG_FUNCTION_ARGS) if (arg1 < -1.0 || arg1 > 1.0) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); if (arg1 >= 0.0) result = acosd_q1(arg1); else result = 90.0 + asind_q1(-arg1); - check_float8_val(result, false, true); + if (unlikely(isinf(result))) + float_overflow_error(); + PG_RETURN_FLOAT8(result); } /* * dasind - returns the arcsin of arg1 (degrees) */ Datum dasind(PG_FUNCTION_ARGS) { @@ -2019,21 +2069,23 @@ dasind(PG_FUNCTION_ARGS) if (arg1 < -1.0 || arg1 > 1.0) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); if (arg1 >= 0.0) result = asind_q1(arg1); else result = -asind_q1(-arg1); - check_float8_val(result, false, true); + if (unlikely(isinf(result))) + float_overflow_error(); + PG_RETURN_FLOAT8(result); } /* * datand - returns the arctan of arg1 (degrees) */ Datum datand(PG_FUNCTION_ARGS) { @@ -2049,21 +2101,23 @@ datand(PG_FUNCTION_ARGS) /* * The principal branch of the inverse tangent function maps all inputs to * values in the range [-90, 90], so the result should always be finite, * even if the input is infinite. Additionally, we take care to ensure * than when arg1 is 1, the result is exactly 45. */ atan_arg1 = atan(arg1); result = (atan_arg1 / atan_1_0) * 45.0; - check_float8_val(result, false, true); + if (unlikely(isinf(result))) + float_overflow_error(); + PG_RETURN_FLOAT8(result); } /* * atan2d - returns the arctan of arg1/arg2 (degrees) */ Datum datan2d(PG_FUNCTION_ARGS) { @@ -2083,21 +2137,23 @@ datan2d(PG_FUNCTION_ARGS) * result should always be finite, even if the inputs are infinite. * * Note: this coding assumes that atan(1.0) is a suitable scaling constant * to get an exact result from atan2(). This might well fail on us at * some point, requiring us to decide exactly what inputs we think we're * going to guarantee an exact result for. */ atan2_arg1_arg2 = atan2(arg1, arg2); result = (atan2_arg1_arg2 / atan_1_0) * 45.0; - check_float8_val(result, false, true); + if (unlikely(isinf(result))) + float_overflow_error(); + PG_RETURN_FLOAT8(result); } /* * sind_0_to_30 - returns the sine of an angle that lies between 0 and * 30 degrees. This will return exactly 0 when x is 0, * and exactly 0.5 when x is 30 degrees. */ static double @@ -2204,21 +2260,23 @@ dcosd(PG_FUNCTION_ARGS) if (arg1 > 90.0) { /* cosd(180-x) = -cosd(x) */ arg1 = 180.0 - arg1; sign = -sign; } result = sign * cosd_q1(arg1); - check_float8_val(result, false, true); + if (unlikely(isinf(result))) + float_overflow_error(); + PG_RETURN_FLOAT8(result); } /* * dcotd - returns the cotangent of arg1 (degrees) */ Datum dcotd(PG_FUNCTION_ARGS) { @@ -2269,21 +2327,22 @@ dcotd(PG_FUNCTION_ARGS) result = sign * (cot_arg1 / cot_45); /* * On some machines we get cotd(270) = minus zero, but this isn't always * true. For portability, and because the user constituency for this * function probably doesn't want minus zero, force it to plain zero. */ if (result == 0.0) result = 0.0; - check_float8_val(result, true /* cotd(0) == Inf */ , true); + /* Not checking for overflow because cotd(0) == Inf */ + PG_RETURN_FLOAT8(result); } /* * dsind - returns the sine of arg1 (degrees) */ Datum dsind(PG_FUNCTION_ARGS) { @@ -2323,21 +2382,23 @@ dsind(PG_FUNCTION_ARGS) } if (arg1 > 90.0) { /* sind(180-x) = sind(x) */ arg1 = 180.0 - arg1; } result = sign * sind_q1(arg1); - check_float8_val(result, false, true); + if (unlikely(isinf(result))) + float_overflow_error(); + PG_RETURN_FLOAT8(result); } /* * dtand - returns the tangent of arg1 (degrees) */ Datum dtand(PG_FUNCTION_ARGS) { @@ -2388,21 +2449,22 @@ dtand(PG_FUNCTION_ARGS) result = sign * (tan_arg1 / tan_45); /* * On some machines we get tand(180) = minus zero, but this isn't always * true. For portability, and because the user constituency for this * function probably doesn't want minus zero, force it to plain zero. */ if (result == 0.0) result = 0.0; - check_float8_val(result, true /* tand(90) == Inf */ , true); + /* Not checking for overflow because tand(90) == Inf */ + PG_RETURN_FLOAT8(result); } /* * degrees - returns degrees converted from radians */ Datum degrees(PG_FUNCTION_ARGS) { @@ -2455,21 +2517,20 @@ dsinh(PG_FUNCTION_ARGS) * sign of arg1. */ if (errno == ERANGE) { if (arg1 < 0) result = -get_float8_infinity(); else result = get_float8_infinity(); } - check_float8_val(result, true, true); PG_RETURN_FLOAT8(result); } /* * dcosh - returns the hyperbolic cosine of arg1 */ Datum dcosh(PG_FUNCTION_ARGS) { @@ -2479,57 +2540,60 @@ dcosh(PG_FUNCTION_ARGS) errno = 0; result = cosh(arg1); /* * if an ERANGE error occurs, it means there is an overflow. As cosh is * always positive, it always means the result is positive infinity. */ if (errno == ERANGE) result = get_float8_infinity(); - check_float8_val(result, true, false); + if (unlikely(result == 0.0)) + float_underflow_error(); + PG_RETURN_FLOAT8(result); } /* * dtanh - returns the hyperbolic tangent of arg1 */ Datum dtanh(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); float8 result; /* * For tanh, we don't need an errno check because it never overflows. */ result = tanh(arg1); - check_float8_val(result, false, true); + if (unlikely(isinf(result))) + float_overflow_error(); + PG_RETURN_FLOAT8(result); } /* * dasinh - returns the inverse hyperbolic sine of arg1 */ Datum dasinh(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); float8 result; /* * For asinh, we don't need an errno check because it never overflows. */ result = asinh(arg1); - check_float8_val(result, true, true); PG_RETURN_FLOAT8(result); } /* * dacosh - returns the inverse hyperbolic cosine of arg1 */ Datum dacosh(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); @@ -2541,21 +2605,20 @@ dacosh(PG_FUNCTION_ARGS) * thing because some implementations will report that for NaN. Otherwise, * no error is possible. */ if (arg1 < 1.0) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("input is out of range"))); result = acosh(arg1); - check_float8_val(result, true, true); PG_RETURN_FLOAT8(result); } /* * datanh - returns the inverse hyperbolic tangent of arg1 */ Datum datanh(PG_FUNCTION_ARGS) { float8 arg1 = PG_GETARG_FLOAT8(0); @@ -2576,21 +2639,20 @@ datanh(PG_FUNCTION_ARGS) * glibc versions may produce the wrong errno for this. All other inputs * cannot produce an error. */ if (arg1 == -1.0) result = -get_float8_infinity(); else if (arg1 == 1.0) result = get_float8_infinity(); else result = atanh(arg1); - check_float8_val(result, true, true); PG_RETURN_FLOAT8(result); } /* * drandom - returns a random number */ Datum drandom(PG_FUNCTION_ARGS) { @@ -2777,21 +2839,22 @@ float8_combine(PG_FUNCTION_ARGS) N = N1; Sx = Sx1; Sxx = Sxx1; } else { N = N1 + N2; Sx = float8_pl(Sx1, Sx2); tmp = Sx1 / N1 - Sx2 / N2; Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N; - check_float8_val(Sxx, isinf(Sxx1) || isinf(Sxx2), true); + if (unlikely(isinf(Sxx) && !isinf(Sxx1) && !isinf(Sxx2))) + float_overflow_error(); } /* * If we're invoked as an aggregate, we can cheat and modify our first * parameter in-place to reduce palloc overhead. Otherwise we construct a * new array with the updated transition data and return it. */ if (AggCheckCallContext(fcinfo, NULL)) { transvalues1[0] = N; @@ -2846,23 +2909,21 @@ float8_accum(PG_FUNCTION_ARGS) /* * Overflow check. We only report an overflow error when finite * inputs lead to infinite results. Note also that Sxx should be NaN * if any of the inputs are infinite, so we intentionally prevent Sxx * from becoming infinite. */ if (isinf(Sx) || isinf(Sxx)) { if (!isinf(transvalues[1]) && !isinf(newval)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: overflow"))); + float_overflow_error(); Sxx = get_float8_nan(); } } /* * If we're invoked as an aggregate, we can cheat and modify our first * parameter in-place to reduce palloc overhead. Otherwise we construct a * new array with the updated transition data and return it. */ @@ -2922,23 +2983,21 @@ float4_accum(PG_FUNCTION_ARGS) /* * Overflow check. We only report an overflow error when finite * inputs lead to infinite results. Note also that Sxx should be NaN * if any of the inputs are infinite, so we intentionally prevent Sxx * from becoming infinite. */ if (isinf(Sx) || isinf(Sxx)) { if (!isinf(transvalues[1]) && !isinf(newval)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: overflow"))); + float_overflow_error(); Sxx = get_float8_nan(); } } /* * If we're invoked as an aggregate, we can cheat and modify our first * parameter in-place to reduce palloc overhead. Otherwise we construct a * new array with the updated transition data and return it. */ @@ -3145,23 +3204,21 @@ float8_regr_accum(PG_FUNCTION_ARGS) */ if (isinf(Sx) || isinf(Sxx) || isinf(Sy) || isinf(Syy) || isinf(Sxy)) { if (((isinf(Sx) || isinf(Sxx)) && !isinf(transvalues[1]) && !isinf(newvalX)) || ((isinf(Sy) || isinf(Syy)) && !isinf(transvalues[3]) && !isinf(newvalY)) || (isinf(Sxy) && !isinf(transvalues[1]) && !isinf(newvalX) && !isinf(transvalues[3]) && !isinf(newvalY))) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: overflow"))); + float_overflow_error(); if (isinf(Sxx)) Sxx = get_float8_nan(); if (isinf(Syy)) Syy = get_float8_nan(); if (isinf(Sxy)) Sxy = get_float8_nan(); } } @@ -3287,27 +3344,30 @@ float8_regr_combine(PG_FUNCTION_ARGS) Sy = Sy1; Syy = Syy1; Sxy = Sxy1; } else { N = N1 + N2; Sx = float8_pl(Sx1, Sx2); tmp1 = Sx1 / N1 - Sx2 / N2; Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N; - check_float8_val(Sxx, isinf(Sxx1) || isinf(Sxx2), true); + if (unlikely(isinf(Sxx) && !isinf(Sxx1) && !isinf(Sxx2))) + float_overflow_error(); Sy = float8_pl(Sy1, Sy2); tmp2 = Sy1 / N1 - Sy2 / N2; Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N; - check_float8_val(Syy, isinf(Syy1) || isinf(Syy2), true); + if (unlikely(isinf(Syy) && !isinf(Syy1) && !isinf(Syy2))) + float_overflow_error(); Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N; - check_float8_val(Sxy, isinf(Sxy1) || isinf(Sxy2), true); + if (unlikely(isinf(Sxy) && !isinf(Sxy1) && !isinf(Sxy2))) + float_overflow_error(); } /* * If we're invoked as an aggregate, we can cheat and modify our first * parameter in-place to reduce palloc overhead. Otherwise we construct a * new array with the updated transition data and return it. */ if (AggCheckCallContext(fcinfo, NULL)) { transvalues1[0] = N; diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c index d5ded471c4..71ee7f8f08 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -5538,14 +5538,17 @@ pg_hypot(float8 x, float8 y) * such cases, but more importantly it also protects against * divide-by-zero errors, since now x >= y. */ if (y == 0.0) return x; /* Determine the hypotenuse */ yx = y / x; result = x * sqrt(1.0 + (yx * yx)); - check_float8_val(result, false, false); + if (unlikely(isinf(result))) + float_overflow_error(); + if (unlikely(result == 0.0)) + float_underflow_error(); return result; } diff --git a/src/include/utils/float.h b/src/include/utils/float.h index e2c5dc0f57..0018b3b5af 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -30,20 +30,22 @@ static const uint32 nan[2] = {0xffffffff, 0x7fffffff}; #define NAN (*(const float8 *) nan) #endif extern PGDLLIMPORT int extra_float_digits; /* * Utility functions in float.c */ +extern void float_overflow_error(void); +extern void float_underflow_error(void); extern int is_infinite(float8 val); extern float8 float8in_internal(char *num, char **endptr_p, const char *type_name, const char *orig_string); extern float8 float8in_internal_opt_error(char *num, char **endptr_p, const char *type_name, const char *orig_string, bool *have_error); extern char *float8out_internal(float8 num); extern int float4_cmp_internal(float4 a, float4 b); extern int float8_cmp_internal(float8 a, float8 b); @@ -123,158 +125,165 @@ get_float8_nan(void) /* C99 standard way */ return (float8) NAN; #else /* Assume we can get a NaN via zero divide */ return (float8) (0.0 / 0.0); #endif } /* * Checks to see if a float4/8 val has underflowed or overflowed + * + * They are no longer used because of the performance regression they cause, + * but left for backwards compatibility. */ static inline void check_float4_val(const float4 val, const bool inf_is_valid, const bool zero_is_valid) { - if (!inf_is_valid && unlikely(isinf(val))) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: overflow"))); - - if (!zero_is_valid && unlikely(val == 0.0)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: underflow"))); + if (unlikely(isinf(val) && !inf_is_valid)) + float_overflow_error(); + if (unlikely(val == 0.0f && !zero_is_valid)) + float_underflow_error(); } static inline void check_float8_val(const float8 val, const bool inf_is_valid, const bool zero_is_valid) { - if (!inf_is_valid && unlikely(isinf(val))) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: overflow"))); - - if (!zero_is_valid && unlikely(val == 0.0)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value out of range: underflow"))); + if (unlikely(isinf(val) && !inf_is_valid)) + float_overflow_error(); + if (unlikely(val == 0.0 && !zero_is_valid)) + float_underflow_error(); } /* - * Routines for operations with the checks above + * Routines for operations with overflow/underflow checks * * There isn't any way to check for underflow of addition/subtraction * because numbers near the underflow value have already been rounded to * the point where we can't detect that the two values were originally * different, e.g. on x86, '1e-45'::float4 == '2e-45'::float4 == * 1.4013e-45. */ static inline float4 float4_pl(const float4 val1, const float4 val2) { float4 result; result = val1 + val2; - check_float4_val(result, isinf(val1) || isinf(val2), true); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); return result; } static inline float8 float8_pl(const float8 val1, const float8 val2) { float8 result; result = val1 + val2; - check_float8_val(result, isinf(val1) || isinf(val2), true); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); return result; } static inline float4 float4_mi(const float4 val1, const float4 val2) { float4 result; result = val1 - val2; - check_float4_val(result, isinf(val1) || isinf(val2), true); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); return result; } static inline float8 float8_mi(const float8 val1, const float8 val2) { float8 result; result = val1 - val2; - check_float8_val(result, isinf(val1) || isinf(val2), true); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); return result; } static inline float4 float4_mul(const float4 val1, const float4 val2) { float4 result; result = val1 * val2; - check_float4_val(result, isinf(val1) || isinf(val2), - val1 == 0.0f || val2 == 0.0f); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); + if (unlikely(result == 0.0f && val1 != 0.0f && val2 != 0.0f)) + float_underflow_error(); return result; } static inline float8 float8_mul(const float8 val1, const float8 val2) { float8 result; result = val1 * val2; - check_float8_val(result, isinf(val1) || isinf(val2), - val1 == 0.0 || val2 == 0.0); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); + if (unlikely(result == 0.0 && val1 != 0.0 && val2 != 0.0)) + float_underflow_error(); return result; } static inline float4 float4_div(const float4 val1, const float4 val2) { float4 result; if (val2 == 0.0f) ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); result = val1 / val2; - check_float4_val(result, isinf(val1) || isinf(val2), val1 == 0.0f); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); + if (unlikely(result == 0.0f && val1 != 0.0f)) + float_underflow_error(); return result; } static inline float8 float8_div(const float8 val1, const float8 val2) { float8 result; if (val2 == 0.0) ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); result = val1 / val2; - check_float8_val(result, isinf(val1) || isinf(val2), val1 == 0.0); + if (unlikely(isinf(result) && !isinf(val1) && !isinf(val2))) + float_overflow_error(); + if (unlikely(result == 0.0 && val1 != 0.0)) + float_underflow_error(); return result; } /* * Routines for NaN-aware comparisons * * We consider all NaNs to be equal and larger than any non-NaN. This is * somewhat arbitrary; the important thing is to have a consistent sort * order. -- 2.17.1