aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/adt/float.c43
-rw-r--r--src/test/regress/expected/numeric.out39
-rw-r--r--src/test/regress/sql/numeric.sql22
3 files changed, 89 insertions, 15 deletions
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()
--