diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 8aa17e1dcb..79573f14d5 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1219,16 +1219,28 @@ Datum dtoi4(PG_FUNCTION_ARGS) { float8 num = PG_GETARG_FLOAT8(0); - int32 result; - /* 'Inf' is handled by INT_MAX */ - if (num < INT_MIN || num > INT_MAX || isnan(num)) + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* + * Range check. We must be careful here that the boundary values are + * expressed exactly in the float domain. We expect PG_INT32_MIN to be an + * exact power of 2, so it will be represented exactly; but PG_INT32_MAX + * isn't, and might get rounded off, so avoid using it. + */ + if (num < (float8) PG_INT32_MIN || + num >= -((float8) PG_INT32_MIN) || + isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); - result = (int32) rint(num); - PG_RETURN_INT32(result); + PG_RETURN_INT32((int32) num); } @@ -1240,12 +1252,27 @@ dtoi2(PG_FUNCTION_ARGS) { float8 num = PG_GETARG_FLOAT8(0); - if (num < SHRT_MIN || num > SHRT_MAX || isnan(num)) + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* + * Range check. We must be careful here that the boundary values are + * expressed exactly in the float domain. We expect PG_INT16_MIN to be an + * exact power of 2, so it will be represented exactly; but PG_INT16_MAX + * isn't, and might get rounded off, so avoid using it. + */ + if (num < (float8) PG_INT16_MIN || + num >= -((float8) PG_INT16_MIN) || + isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); - PG_RETURN_INT16((int16) rint(num)); + PG_RETURN_INT16((int16) num); } @@ -1281,12 +1308,27 @@ ftoi4(PG_FUNCTION_ARGS) { float4 num = PG_GETARG_FLOAT4(0); - if (num < INT_MIN || num > INT_MAX || isnan(num)) + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* + * Range check. We must be careful here that the boundary values are + * expressed exactly in the float domain. We expect PG_INT32_MIN to be an + * exact power of 2, so it will be represented exactly; but PG_INT32_MAX + * isn't, and might get rounded off, so avoid using it. + */ + if (num < (float4) PG_INT32_MIN || + num >= -((float4) PG_INT32_MIN) || + isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); - PG_RETURN_INT32((int32) rint(num)); + PG_RETURN_INT32((int32) num); } @@ -1298,12 +1340,27 @@ ftoi2(PG_FUNCTION_ARGS) { float4 num = PG_GETARG_FLOAT4(0); - if (num < SHRT_MIN || num > SHRT_MAX || isnan(num)) + /* + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. + */ + num = rint(num); + + /* + * Range check. We must be careful here that the boundary values are + * expressed exactly in the float domain. We expect PG_INT16_MIN to be an + * exact power of 2, so it will be represented exactly; but PG_INT16_MAX + * isn't, and might get rounded off, so avoid using it. + */ + if (num < (float4) PG_INT16_MIN || + num >= -((float4) PG_INT16_MIN) || + isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); - PG_RETURN_INT16((int16) rint(num)); + PG_RETURN_INT16((int16) num); } diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 17d2973280..62f54c12d5 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1342,25 +1342,29 @@ i8tod(PG_FUNCTION_ARGS) Datum dtoi8(PG_FUNCTION_ARGS) { - float8 arg = PG_GETARG_FLOAT8(0); - int64 result; - - /* Round arg to nearest integer (but it's still in float form) */ - arg = rint(arg); + float8 num = PG_GETARG_FLOAT8(0); /* - * Does it fit in an int64? Avoid assuming that we have handy constants - * defined for the range boundaries, instead test for overflow by - * reverse-conversion. + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. */ - result = (int64) arg; + num = rint(num); - if ((float8) result != arg) + /* + * Range check. We must be careful here that the boundary values are + * expressed exactly in the float domain. We expect PG_INT64_MIN to be an + * exact power of 2, so it will be represented exactly; but PG_INT64_MAX + * isn't, and might get rounded off, so avoid using it. + */ + if (num < (float8) PG_INT64_MIN || + num >= -((float8) PG_INT64_MIN) || + isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); - PG_RETURN_INT64(result); + PG_RETURN_INT64((int64) num); } Datum @@ -1380,26 +1384,29 @@ i8tof(PG_FUNCTION_ARGS) Datum ftoi8(PG_FUNCTION_ARGS) { - float4 arg = PG_GETARG_FLOAT4(0); - int64 result; - float8 darg; - - /* Round arg to nearest integer (but it's still in float form) */ - darg = rint(arg); + float4 num = PG_GETARG_FLOAT4(0); /* - * Does it fit in an int64? Avoid assuming that we have handy constants - * defined for the range boundaries, instead test for overflow by - * reverse-conversion. + * Get rid of any fractional part in the input. This is so we don't fail + * on just-out-of-range values that would round into range. Note + * assumption that rint() will pass through a NaN or Inf unchanged. */ - result = (int64) darg; + num = rint(num); - if ((float8) result != darg) + /* + * Range check. We must be careful here that the boundary values are + * expressed exactly in the float domain. We expect PG_INT64_MIN to be an + * exact power of 2, so it will be represented exactly; but PG_INT64_MAX + * isn't, and might get rounded off, so avoid using it. + */ + if (num < (float4) PG_INT64_MIN || + num >= -((float4) PG_INT64_MIN) || + isnan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); - PG_RETURN_INT64(result); + PG_RETURN_INT64((int64) num); } Datum diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out index fd46a4a1db..2f47e1c202 100644 --- a/src/test/regress/expected/float4.out +++ b/src/test/regress/expected/float4.out @@ -257,3 +257,52 @@ SELECT '' AS five, * FROM FLOAT4_TBL; | -1.23457e-20 (5 rows) +-- test edge-case coercions to integer +SELECT '32767.4'::float4::int2; + int2 +------- + 32767 +(1 row) + +SELECT '32767.6'::float4::int2; +ERROR: smallint out of range +SELECT '-32768.4'::float4::int2; + int2 +-------- + -32768 +(1 row) + +SELECT '-32768.6'::float4::int2; +ERROR: smallint out of range +SELECT '2147483520'::float4::int4; + int4 +------------ + 2147483520 +(1 row) + +SELECT '2147483647'::float4::int4; +ERROR: integer out of range +SELECT '-2147483648.5'::float4::int4; + int4 +------------- + -2147483648 +(1 row) + +SELECT '-2147483900'::float4::int4; +ERROR: integer out of range +SELECT '9223369837831520256'::float4::int8; + int8 +--------------------- + 9223369837831520256 +(1 row) + +SELECT '9223372036854775807'::float4::int8; +ERROR: bigint out of range +SELECT '-9223372036854775808.5'::float4::int8; + int8 +---------------------- + -9223372036854775808 +(1 row) + +SELECT '-9223380000000000000'::float4::int8; +ERROR: bigint out of range diff --git a/src/test/regress/expected/float8-small-is-zero.out b/src/test/regress/expected/float8-small-is-zero.out index 26b8378150..f433ddcbc1 100644 --- a/src/test/regress/expected/float8-small-is-zero.out +++ b/src/test/regress/expected/float8-small-is-zero.out @@ -442,6 +442,55 @@ SELECT '' AS five, * FROM FLOAT8_TBL; | -1.2345678901234e-200 (5 rows) +-- test edge-case coercions to integer +SELECT '32767.4'::float8::int2; + int2 +------- + 32767 +(1 row) + +SELECT '32767.6'::float8::int2; +ERROR: smallint out of range +SELECT '-32768.4'::float8::int2; + int2 +-------- + -32768 +(1 row) + +SELECT '-32768.6'::float8::int2; +ERROR: smallint out of range +SELECT '2147483647.4'::float8::int4; + int4 +------------ + 2147483647 +(1 row) + +SELECT '2147483647.6'::float8::int4; +ERROR: integer out of range +SELECT '-2147483648.4'::float8::int4; + int4 +------------- + -2147483648 +(1 row) + +SELECT '-2147483648.6'::float8::int4; +ERROR: integer out of range +SELECT '9223372036854773760'::float8::int8; + int8 +--------------------- + 9223372036854773760 +(1 row) + +SELECT '9223372036854775807'::float8::int8; +ERROR: bigint out of range +SELECT '-9223372036854775808.5'::float8::int8; + int8 +---------------------- + -9223372036854775808 +(1 row) + +SELECT '-9223372036854780000'::float8::int8; +ERROR: bigint out of range -- test exact cases for trigonometric functions in degrees SET extra_float_digits = 3; SELECT x, diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out index 20c985e5df..d5d5b16f6c 100644 --- a/src/test/regress/expected/float8.out +++ b/src/test/regress/expected/float8.out @@ -444,6 +444,55 @@ SELECT '' AS five, * FROM FLOAT8_TBL; | -1.2345678901234e-200 (5 rows) +-- test edge-case coercions to integer +SELECT '32767.4'::float8::int2; + int2 +------- + 32767 +(1 row) + +SELECT '32767.6'::float8::int2; +ERROR: smallint out of range +SELECT '-32768.4'::float8::int2; + int2 +-------- + -32768 +(1 row) + +SELECT '-32768.6'::float8::int2; +ERROR: smallint out of range +SELECT '2147483647.4'::float8::int4; + int4 +------------ + 2147483647 +(1 row) + +SELECT '2147483647.6'::float8::int4; +ERROR: integer out of range +SELECT '-2147483648.4'::float8::int4; + int4 +------------- + -2147483648 +(1 row) + +SELECT '-2147483648.6'::float8::int4; +ERROR: integer out of range +SELECT '9223372036854773760'::float8::int8; + int8 +--------------------- + 9223372036854773760 +(1 row) + +SELECT '9223372036854775807'::float8::int8; +ERROR: bigint out of range +SELECT '-9223372036854775808.5'::float8::int8; + int8 +---------------------- + -9223372036854775808 +(1 row) + +SELECT '-9223372036854780000'::float8::int8; +ERROR: bigint out of range -- test exact cases for trigonometric functions in degrees SET extra_float_digits = 3; SELECT x, diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql index 3b363f9463..46a9166d13 100644 --- a/src/test/regress/sql/float4.sql +++ b/src/test/regress/sql/float4.sql @@ -81,3 +81,17 @@ UPDATE FLOAT4_TBL WHERE FLOAT4_TBL.f1 > '0.0'; SELECT '' AS five, * FROM FLOAT4_TBL; + +-- test edge-case coercions to integer +SELECT '32767.4'::float4::int2; +SELECT '32767.6'::float4::int2; +SELECT '-32768.4'::float4::int2; +SELECT '-32768.6'::float4::int2; +SELECT '2147483520'::float4::int4; +SELECT '2147483647'::float4::int4; +SELECT '-2147483648.5'::float4::int4; +SELECT '-2147483900'::float4::int4; +SELECT '9223369837831520256'::float4::int8; +SELECT '9223372036854775807'::float4::int8; +SELECT '-9223372036854775808.5'::float4::int8; +SELECT '-9223380000000000000'::float4::int8; diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql index 215e7a4784..fa8d64b890 100644 --- a/src/test/regress/sql/float8.sql +++ b/src/test/regress/sql/float8.sql @@ -168,6 +168,20 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('-1.2345678901234e-200'); SELECT '' AS five, * FROM FLOAT8_TBL; +-- test edge-case coercions to integer +SELECT '32767.4'::float8::int2; +SELECT '32767.6'::float8::int2; +SELECT '-32768.4'::float8::int2; +SELECT '-32768.6'::float8::int2; +SELECT '2147483647.4'::float8::int4; +SELECT '2147483647.6'::float8::int4; +SELECT '-2147483648.4'::float8::int4; +SELECT '-2147483648.6'::float8::int4; +SELECT '9223372036854773760'::float8::int8; +SELECT '9223372036854775807'::float8::int8; +SELECT '-9223372036854775808.5'::float8::int8; +SELECT '-9223372036854780000'::float8::int8; + -- test exact cases for trigonometric functions in degrees SET extra_float_digits = 3;