diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index aa79487a11b..4b0795bd24b 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -4090,7 +4090,7 @@ width_bucket_float8(PG_FUNCTION_ARGS) int32 count = PG_GETARG_INT32(3); int32 result; - if (count <= 0.0) + if (count <= 0) ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), errmsg("count must be greater than zero"))); @@ -4108,31 +4108,39 @@ width_bucket_float8(PG_FUNCTION_ARGS) if (bound1 < bound2) { + /* In all cases, we'll add one at the end */ if (operand < bound1) - result = 0; + result = -1; else if (operand >= bound2) + result = count; + else if (!isinf(bound2 - bound1)) { - if (pg_add_s32_overflow(count, 1, &result)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); + /* Result of division is surely in [0,1], so this can't overflow */ + result = count * ((operand - bound1) / (bound2 - bound1)); } else - result = ((float8) count * (operand - bound1) / (bound2 - bound1)) + 1; + { + /* + * We get here if bound2 - bound1 overflows DBL_MAX. Since both + * bounds are finite, their difference can't exceed twice DBL_MAX; + * so we can perform the computation without overflow by dividing + * all the inputs by 2. That should be exact, too, except in the + * case where a very small operand underflows to zero, which would + * have negligible impact on the result given such large bounds. + */ + result = count * ((operand / 2 - bound1 / 2) / (bound2 / 2 - bound1 / 2)); + } } else if (bound1 > bound2) { if (operand > bound1) - result = 0; + result = -1; else if (operand <= bound2) - { - if (pg_add_s32_overflow(count, 1, &result)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); - } + result = count; + else if (!isinf(bound1 - bound2)) + result = count * ((bound1 - operand) / (bound1 - bound2)); else - result = ((float8) count * (bound1 - operand) / (bound1 - bound2)) + 1; + result = count * ((bound1 / 2 - operand / 2) / (bound1 / 2 - bound2 / 2)); } else { @@ -4142,5 +4150,10 @@ width_bucket_float8(PG_FUNCTION_ARGS) result = 0; /* keep the compiler quiet */ } + if (pg_add_s32_overflow(result, 1, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); } diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index 26930f4db47..65a9c757638 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1473,6 +1473,45 @@ FROM generate_series(0, 110, 10) x; 110 | 0 | 0 (12 rows) +-- Check cases that could trigger overflow or underflow within the calculation +SELECT oper, low, high, cnt, width_bucket(oper, low, high, cnt) +FROM + (SELECT 1.797e+308::float8 AS big, 5e-324::float8 AS tiny) as v, + LATERAL (VALUES + (10.5::float8, -big, big, 1), + (10.5::float8, -big, big, 2), + (10.5::float8, -big, big, 3), + (big / 4, -big / 2, big / 2, 10), + (10.5::float8, big, -big, 1), + (10.5::float8, big, -big, 2), + (10.5::float8, big, -big, 3), + (big / 4, big / 2, -big / 2, 10), + (0, 0, tiny, 4), + (tiny, 0, tiny, 4), + (0, 0, 1, 2147483647), + (1, 1, 0, 2147483647) + ) as sample(oper, low, high, cnt); + oper | low | high | cnt | width_bucket +-------------+-------------+-------------+------------+-------------- + 10.5 | -1.797e+308 | 1.797e+308 | 1 | 1 + 10.5 | -1.797e+308 | 1.797e+308 | 2 | 2 + 10.5 | -1.797e+308 | 1.797e+308 | 3 | 2 + 4.4925e+307 | -8.985e+307 | 8.985e+307 | 10 | 8 + 10.5 | 1.797e+308 | -1.797e+308 | 1 | 1 + 10.5 | 1.797e+308 | -1.797e+308 | 2 | 2 + 10.5 | 1.797e+308 | -1.797e+308 | 3 | 2 + 4.4925e+307 | 8.985e+307 | -8.985e+307 | 10 | 3 + 0 | 0 | 5e-324 | 4 | 1 + 5e-324 | 0 | 5e-324 | 4 | 5 + 0 | 0 | 1 | 2147483647 | 1 + 1 | 1 | 0 | 2147483647 | 1 +(12 rows) + +-- These fail because the result would be out of int32 range: +SELECT width_bucket(1::float8, 0, 1, 2147483647); +ERROR: integer out of range +SELECT width_bucket(0::float8, 1, 0, 2147483647); +ERROR: integer out of range -- -- TO_CHAR() -- diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 2dddb586255..07ff98741f9 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -910,6 +910,28 @@ SELECT x, width_bucket(x::float8, 100, 10, 9) as flt, width_bucket(x::numeric, 100, 10, 9) as num FROM generate_series(0, 110, 10) x; +-- Check cases that could trigger overflow or underflow within the calculation +SELECT oper, low, high, cnt, width_bucket(oper, low, high, cnt) +FROM + (SELECT 1.797e+308::float8 AS big, 5e-324::float8 AS tiny) as v, + LATERAL (VALUES + (10.5::float8, -big, big, 1), + (10.5::float8, -big, big, 2), + (10.5::float8, -big, big, 3), + (big / 4, -big / 2, big / 2, 10), + (10.5::float8, big, -big, 1), + (10.5::float8, big, -big, 2), + (10.5::float8, big, -big, 3), + (big / 4, big / 2, -big / 2, 10), + (0, 0, tiny, 4), + (tiny, 0, tiny, 4), + (0, 0, 1, 2147483647), + (1, 1, 0, 2147483647) + ) as sample(oper, low, high, cnt); +-- These fail because the result would be out of int32 range: +SELECT width_bucket(1::float8, 0, 1, 2147483647); +SELECT width_bucket(0::float8, 1, 0, 2147483647); + -- -- TO_CHAR() --