0001-Avoid-floating-point-overflow-in-width_bucket.patch
text/x-patch
Filename: 0001-Avoid-floating-point-overflow-in-width_bucket.patch
Type: text/x-patch
Part: 0
Patch
Format: format-patch
Series: patch 0001
Subject: Avoid floating-point overflow in width_bucket()
| File | + | − |
|---|---|---|
| src/backend/utils/adt/float.c | 20 | 1 |
| src/test/regress/expected/numeric.out | 38 | 0 |
| src/test/regress/sql/numeric.sql | 26 | 0 |
From 1ee425f4dfb35b73b68e50295ee37664618a1856 Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats@timescale.com>
Date: Wed, 29 Mar 2023 10:07:18 +0200
Subject: [PATCH] Avoid floating-point overflow in width_bucket()
The floating-point calculation in width_bucket() can lead to overflow
when values are close to DBL_MAX and -DBL_MAX, and further result in a
NaN in the computation. This is then cast to an integer value, which
produces an out-of-bounds value from width_bucket().
This is fixed by scaling down the values to avoid that the
floating-point expressions overflow.
---
src/backend/utils/adt/float.c | 21 ++++++++++++++-
src/test/regress/expected/numeric.out | 38 +++++++++++++++++++++++++++
src/test/regress/sql/numeric.sql | 26 ++++++++++++++++++
3 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index aa79487a11..39a7f1a6e4 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -4117,8 +4117,26 @@ width_bucket_float8(PG_FUNCTION_ARGS)
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
+ /*
+ * Since the expressions "operand - bound1" and "bound2 - bound1" can
+ * overflow for some combinations of values that are close to -DBL_MAX
+ * or DBL_MAX, this can lead NaN values, which when cast to integer
+ * lead to strange values.
+ *
+ * To avoid this, we divide the values by 2 before performing the
+ * subtraction if the result of either of them is infinite. This will
+ * ensure that the expressions will not overflow and lead to a strange
+ * result.
+ *
+ * Since we know from the previous checks that bound1 <= operand <=
+ * bound2 it is sufficient to check that bound2 - bound1 is not
+ * infinite. If it is not infinite, then operand - bound1 cannot be
+ * infinite either.
+ */
+ else if (isinf(bound2 - bound1))
+ result = (count * ((operand / 2 - bound1 / 2) / (bound2 / 2 - bound1 / 2))) + 1;
else
- result = ((float8) count * (operand - bound1) / (bound2 - bound1)) + 1;
+ result = (count * ((operand - bound1) / (bound2 - bound1))) + 1;
}
else if (bound1 > bound2)
{
@@ -4142,5 +4160,6 @@ width_bucket_float8(PG_FUNCTION_ARGS)
result = 0; /* keep the compiler quiet */
}
+ Assert(result >= 0 && result <= count + 1);
PG_RETURN_INT32(result);
}
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 26930f4db4..2191110554 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -1433,6 +1433,44 @@ SELECT width_bucket('Infinity'::float8, 1, 10, 10),
11 | 0
(1 row)
+WITH sample(oper, low, high, cnt) AS (
+ VALUES
+ -- Check ranges that can cause overflow in the internal
+ -- calculations. These are basically computations where difference
+ -- between operand and low and high can exceed DBL_MAX.
+ (10.5::float8, -1.797e+308::float8, 1.797e+308::float8, 1::int4),
+ (10.5::float8, -1.797e+308::float8, 1.797e+308::float8, 2),
+ (10.5::float8, -1.797e+308::float8, 1.797e+308::float8, 3),
+ (1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 10),
+ (-1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 10),
+ (1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 16),
+ (-1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 16),
+ (1.797e+308::float8, -1.797e+308::float8 / 2, 1.797e+308::float8 / 2, 10),
+ (-1.797e+308::float8, -1.797e+308::float8 / 2, 1.797e+308::float8 / 2, 10),
+ -- Check that potential underflow situations does not result in
+ -- weird values.
+ (2.22507e-308::float8, 2 * -2.22507e-308::float8, 2 * 2.22507e-308::float8, 4),
+ (-2.22507e-308::float8, 2 * -2.22507e-308::float8, 2 * 2.22507e-308::float8, 4),
+ (2.22507e-308::float8, 3 * -2.22507e-308::float8, 3 * 2.22507e-308::float8, 6),
+ (-2.22507e-308::float8, 3 * -2.22507e-308::float8, 3 * 2.22507e-308::float8, 6)
+) SELECT width_bucket(oper, low, high, cnt) FROM sample;
+ width_bucket
+--------------
+ 1
+ 2
+ 2
+ 8
+ 3
+ 12
+ 5
+ 11
+ 0
+ 4
+ 2
+ 5
+ 3
+(13 rows)
+
DROP TABLE width_bucket_test;
-- Simple test for roundoff error when results should be exact
SELECT x, width_bucket(x::float8, 10, 100, 9) as flt,
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 2dddb58625..13ae07b6ff 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -900,6 +900,32 @@ SELECT width_bucket(0.0::float8, 5, '-Infinity'::float8, 20); -- error
SELECT width_bucket('Infinity'::float8, 1, 10, 10),
width_bucket('-Infinity'::float8, 1, 10, 10);
+WITH sample(oper, low, high, cnt) AS (
+ VALUES
+ -- Check ranges that can cause overflow in the internal
+ -- calculations. These are basically computations where difference
+ -- between operand and low and high can exceed DBL_MAX.
+ (10.5::float8, -1.797e+308::float8, 1.797e+308::float8, 1::int4),
+ (10.5::float8, -1.797e+308::float8, 1.797e+308::float8, 2),
+ (10.5::float8, -1.797e+308::float8, 1.797e+308::float8, 3),
+
+ (1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 10),
+ (-1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 10),
+
+ (1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 16),
+ (-1.797e+308::float8 / 2, -1.797e+308::float8, 1.797e+308::float8, 16),
+
+ (1.797e+308::float8, -1.797e+308::float8 / 2, 1.797e+308::float8 / 2, 10),
+ (-1.797e+308::float8, -1.797e+308::float8 / 2, 1.797e+308::float8 / 2, 10),
+
+ -- Check that potential underflow situations does not result in
+ -- weird values.
+ (2.22507e-308::float8, 2 * -2.22507e-308::float8, 2 * 2.22507e-308::float8, 4),
+ (-2.22507e-308::float8, 2 * -2.22507e-308::float8, 2 * 2.22507e-308::float8, 4),
+ (2.22507e-308::float8, 3 * -2.22507e-308::float8, 3 * 2.22507e-308::float8, 6),
+ (-2.22507e-308::float8, 3 * -2.22507e-308::float8, 3 * 2.22507e-308::float8, 6)
+) SELECT width_bucket(oper, low, high, cnt) FROM sample;
+
DROP TABLE width_bucket_test;
-- Simple test for roundoff error when results should be exact
--
2.34.1