> But the comment does not explain that this test has to be in that > order, or the compiler will for non-constant arguments evalute > the (now) right-side first. E.g. if I understand this correctly: > > + if (!(zero_is_valid) && unlikely((val) == 0.0) > > would have the same problem of evaluating "zero_is_valid" (which > might be an isinf(arg1) || isinf(arg2)) first and so be the same thing > we try to avoid with the macro? Maybe adding this bit of info to the > comment makes it clearer?
Added. > Also, a few places use the macro as: > > + CHECKFLOATVAL(result, true, true); > > which evaluates to a complete NOP in both cases. IMHO this could be > replaced with a comment like: > > + // No CHECKFLOATVAL() needed, as both inf and 0.0 are valid > > (or something along the lines of "no error can occur"), as otherwise > CHECKFLOATVAL() implies to the casual reader that there are some checks > done, while in reality no real checks are done at all (and hopefully > the compiler optimizes everything away, which might not be true for > debug builds). I don't know why those trigonometric functions don't check for overflow/underflow like all the rest of float.c. I'll submit another patch to make them error when overflow/underflow. The new version is attached.
From fb5052b869255ef9465b1de92e84b2fb66dd6eb3 Mon Sep 17 00:00:00 2001 From: Emre Hasegeli <e...@hasegeli.com> Date: Fri, 7 Feb 2020 10:27:25 +0000 Subject: [PATCH] Bring back CHECKFLOATVAL() macro 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 brings back the macro that was in use before 6bf0bc842b to fix the performace regression. Reported-by: Keisuke Kuroda <keisuke.kuroda.3...@gmail.com> Author: Keisuke Kuroda <keisuke.kuroda.3...@gmail.com> Discussion: https://www.postgresql.org/message-id/CANDwggLe1Gc1OrRqvPfGE%3DkM9K0FSfia0hbeFCEmwabhLz95AA%40mail.gmail.com --- contrib/btree_gist/btree_utils_num.h | 6 +-- src/backend/utils/adt/float.c | 66 ++++++++++++------------ src/backend/utils/adt/geo_ops.c | 2 +- src/include/utils/float.h | 76 ++++++++++++---------------- 4 files changed, 70 insertions(+), 80 deletions(-) diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h index 1fedfbe82d..a3227fd758 100644 --- a/contrib/btree_gist/btree_utils_num.h +++ b/contrib/btree_gist/btree_utils_num.h @@ -84,30 +84,30 @@ typedef struct */ #define INTERVAL_TO_SEC(ivp) \ (((double) (ivp)->time) / ((double) USECS_PER_SEC) + \ (ivp)->day * (24.0 * SECS_PER_HOUR) + \ (ivp)->month * (30.0 * SECS_PER_DAY)) #define GET_FLOAT_DISTANCE(t, arg1, arg2) Abs( ((float8) *((const t *) (arg1))) - ((float8) *((const t *) (arg2))) ) /* * check to see if a float4/8 val has underflowed or overflowed - * borrowed from src/backend/utils/adt/float.c + * borrowed from src/include/utils/float.c */ #define CHECKFLOATVAL(val, inf_is_valid, zero_is_valid) \ do { \ - if (isinf(val) && !(inf_is_valid)) \ + if (unlikely(isinf(val) && !(inf_is_valid))) \ ereport(ERROR, \ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), \ errmsg("value out of range: overflow"))); \ \ - if ((val) == 0.0 && !(zero_is_valid)) \ + if (unlikely((val) == 0.0 && !(zero_is_valid))) \ ereport(ERROR, \ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), \ errmsg("value out of range: underflow"))); \ } while(0) extern Interval *abs_interval(Interval *a); extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query, const StrategyNumber *strategy, bool is_leaf, diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index a90d4db215..5885719850 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1184,21 +1184,21 @@ ftod(PG_FUNCTION_ARGS) /* * dtof - converts a float8 number to a float4 number */ Datum dtof(PG_FUNCTION_ARGS) { float8 num = PG_GETARG_FLOAT8(0); - check_float4_val((float4) num, isinf(num), num == 0); + CHECKFLOATVAL((float4) num, isinf(num), num == 0); PG_RETURN_FLOAT4((float4) num); } /* * dtoi4 - converts a float8 number to an int4 number */ Datum dtoi4(PG_FUNCTION_ARGS) @@ -1438,36 +1438,36 @@ 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); - check_float8_val(result, isinf(arg1), arg1 == 0); + CHECKFLOATVAL(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); + CHECKFLOATVAL(result, isinf(arg1), arg1 == 0); PG_RETURN_FLOAT8(result); } /* * dpow - returns pow(arg1,arg2) */ Datum dpow(PG_FUNCTION_ARGS) { @@ -1525,40 +1525,40 @@ 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); + CHECKFLOATVAL(result, isinf(arg1) || isinf(arg2), arg1 == 0); 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); + CHECKFLOATVAL(result, isinf(arg1), false); PG_RETURN_FLOAT8(result); } /* * dlog1 - returns the natural logarithm of arg1 */ Datum dlog1(PG_FUNCTION_ARGS) { @@ -1573,21 +1573,21 @@ dlog1(PG_FUNCTION_ARGS) 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); - check_float8_val(result, isinf(arg1), arg1 == 1); + CHECKFLOATVAL(result, isinf(arg1), arg1 == 1); PG_RETURN_FLOAT8(result); } /* * dlog10 - returns the base 10 logarithm of arg1 */ Datum dlog10(PG_FUNCTION_ARGS) { @@ -1603,21 +1603,21 @@ dlog10(PG_FUNCTION_ARGS) 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); - check_float8_val(result, isinf(arg1), arg1 == 1); + CHECKFLOATVAL(result, isinf(arg1), arg1 == 1); PG_RETURN_FLOAT8(result); } /* * dacos - returns the arccos of arg1 (radians) */ Datum dacos(PG_FUNCTION_ARGS) { @@ -1633,21 +1633,21 @@ dacos(PG_FUNCTION_ARGS) * 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); - check_float8_val(result, false, true); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * dasin - returns the arcsin of arg1 (radians) */ Datum dasin(PG_FUNCTION_ARGS) { @@ -1663,21 +1663,21 @@ dasin(PG_FUNCTION_ARGS) * 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); - check_float8_val(result, false, true); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * datan - returns the arctan of arg1 (radians) */ Datum datan(PG_FUNCTION_ARGS) { @@ -1688,21 +1688,21 @@ datan(PG_FUNCTION_ARGS) 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); - check_float8_val(result, false, true); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * atan2 - returns the arctan of arg1/arg2 (radians) */ Datum datan2(PG_FUNCTION_ARGS) { @@ -1713,21 +1713,21 @@ 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); - check_float8_val(result, false, true); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * dcos - returns the cosine of arg1 (radians) */ Datum dcos(PG_FUNCTION_ARGS) { @@ -1753,21 +1753,21 @@ dcos(PG_FUNCTION_ARGS) * 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"))); - check_float8_val(result, false, true); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * dcot - returns the cotangent of arg1 (radians) */ Datum dcot(PG_FUNCTION_ARGS) { @@ -1780,21 +1780,21 @@ 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); + CHECKFLOATVAL(result, true /* cot(0) == Inf */ , true); PG_RETURN_FLOAT8(result); } /* * dsin - returns the sine of arg1 (radians) */ Datum dsin(PG_FUNCTION_ARGS) { @@ -1806,21 +1806,21 @@ dsin(PG_FUNCTION_ARGS) 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"))); - check_float8_val(result, false, true); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * dtan - returns the tangent of arg1 (radians) */ Datum dtan(PG_FUNCTION_ARGS) { @@ -1832,21 +1832,21 @@ dtan(PG_FUNCTION_ARGS) 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"))); - check_float8_val(result, true /* tan(pi/2) == Inf */ , true); + CHECKFLOATVAL(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 +1984,21 @@ 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); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * dasind - returns the arcsin of arg1 (degrees) */ Datum dasind(PG_FUNCTION_ARGS) { @@ -2019,21 +2019,21 @@ 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); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * datand - returns the arctan of arg1 (degrees) */ Datum datand(PG_FUNCTION_ARGS) { @@ -2049,21 +2049,21 @@ 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); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * atan2d - returns the arctan of arg1/arg2 (degrees) */ Datum datan2d(PG_FUNCTION_ARGS) { @@ -2083,21 +2083,21 @@ 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); + CHECKFLOATVAL(result, false, true); 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 +2204,21 @@ 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); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * dcotd - returns the cotangent of arg1 (degrees) */ Datum dcotd(PG_FUNCTION_ARGS) { @@ -2269,21 +2269,21 @@ 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); + CHECKFLOATVAL(result, true /* cotd(0) == Inf */ , true); PG_RETURN_FLOAT8(result); } /* * dsind - returns the sine of arg1 (degrees) */ Datum dsind(PG_FUNCTION_ARGS) { @@ -2323,21 +2323,21 @@ 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); + CHECKFLOATVAL(result, false, true); PG_RETURN_FLOAT8(result); } /* * dtand - returns the tangent of arg1 (degrees) */ Datum dtand(PG_FUNCTION_ARGS) { @@ -2388,21 +2388,21 @@ 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); + CHECKFLOATVAL(result, true /* tand(90) == Inf */ , true); PG_RETURN_FLOAT8(result); } /* * degrees - returns degrees converted from radians */ Datum degrees(PG_FUNCTION_ARGS) { @@ -2455,21 +2455,21 @@ 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); + CHECKFLOATVAL(result, true, true); PG_RETURN_FLOAT8(result); } /* * dcosh - returns the hyperbolic cosine of arg1 */ Datum dcosh(PG_FUNCTION_ARGS) { @@ -2479,57 +2479,57 @@ 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); + CHECKFLOATVAL(result, true, false); 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); + CHECKFLOATVAL(result, false, true); 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); + CHECKFLOATVAL(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 +2541,21 @@ 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); + CHECKFLOATVAL(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 +2576,21 @@ 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); + CHECKFLOATVAL(result, true, true); PG_RETURN_FLOAT8(result); } /* * drandom - returns a random number */ Datum drandom(PG_FUNCTION_ARGS) { @@ -2777,21 +2777,21 @@ 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); + CHECKFLOATVAL(Sxx, isinf(Sxx1) || isinf(Sxx2), true); } /* * 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; @@ -3287,27 +3287,27 @@ 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); + CHECKFLOATVAL(Sxx, isinf(Sxx1) || isinf(Sxx2), true); 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); + CHECKFLOATVAL(Syy, isinf(Syy1) || isinf(Syy2), true); Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N; - check_float8_val(Sxy, isinf(Sxy1) || isinf(Sxy2), true); + CHECKFLOATVAL(Sxy, isinf(Sxy1) || isinf(Sxy2), true); } /* * 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..a0d02c6a90 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -5538,14 +5538,14 @@ 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); + CHECKFLOATVAL(result, false, false); return result; } diff --git a/src/include/utils/float.h b/src/include/utils/float.h index e2c5dc0f57..f9c0222c56 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -25,20 +25,44 @@ /* Radians per degree, a.k.a. PI / 180 */ #define RADIANS_PER_DEGREE 0.0174532925199432957692 /* Visual C++ etc lacks NAN, and won't accept 0.0/0.0. */ #if defined(WIN32) && !defined(NAN) static const uint32 nan[2] = {0xffffffff, 0x7fffffff}; #define NAN (*(const float8 *) nan) #endif +/* + * Checks to see if a float4/8 val has underflowed or overflowed + * + * The rest of the API uses inline functions, but this has to stay as macro + * to prevent the inf_is_valid and zero_is_valid arguments to be evaluated + * when the val is not inf or zero. We first check the val, and then + * the other arguments. Evaluation the other arguments is twice as expensive + * on the common code paths. + * + * Note that this macro double evaluates the val. + */ +#define CHECKFLOATVAL(val, inf_is_valid, zero_is_valid) \ +do { \ + if (unlikely(isinf(val) && !(inf_is_valid))) \ + ereport(ERROR, \ + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), \ + errmsg("value out of range: overflow"))); \ + \ + if (unlikely((val) == 0.0 && !(zero_is_valid))) \ + ereport(ERROR, \ + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), \ + errmsg("value out of range: underflow"))); \ +} while(0) + extern PGDLLIMPORT int extra_float_digits; /* * Utility functions in float.c */ 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, @@ -122,159 +146,125 @@ get_float8_nan(void) #if defined(NAN) && !(defined(__NetBSD__) && defined(__mips__)) /* 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 - */ - -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"))); -} - -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"))); -} - -/* - * 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); + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), true); 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); + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), true); 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); + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), true); 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); + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), true); 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), + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), val1 == 0.0f || val2 == 0.0f); 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), + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), val1 == 0.0 || val2 == 0.0); 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); + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), val1 == 0.0f); 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); + CHECKFLOATVAL(result, isinf(val1) || isinf(val2), val1 == 0.0); 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