diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index ba19cfb672..9f16f09c11 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -3455,11 +3455,13 @@ numericvar_to_int32(const NumericVar *var, int32 *result) if (!numericvar_to_int64(var, &val)) return false; + if (unlikely(val < PG_INT32_MIN) || unlikely(val > PG_INT32_MAX)) + return false; + /* Down-convert to int4 */ *result = (int32) val; - /* Test for overflow by reverse-conversion. */ - return ((int64) *result == val); + return true; } Datum @@ -3547,15 +3549,14 @@ numeric_int2(PG_FUNCTION_ARGS) (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); - /* Down-convert to int2 */ - result = (int16) val; - - /* Test for overflow by reverse-conversion. */ - if ((int64) result != val) + if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); + /* Down-convert to int2 */ + result = (int16) val; + PG_RETURN_INT16(result); } @@ -9165,10 +9166,7 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result) if (numericvar_to_int64(exp, &expval64)) { - int expval = (int) expval64; - - /* Test for overflow by reverse-conversion. */ - if ((int64) expval == expval64) + if (expval64 >= PG_INT32_MIN && expval64 <= PG_INT32_MAX) { /* Okay, select rscale */ rscale = NUMERIC_MIN_SIG_DIGITS; @@ -9176,7 +9174,7 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result) rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); - power_var_int(base, expval, result, rscale); + power_var_int(base, (int) expval64, result, rscale); return; } } diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index 6e7c8e42ec..5c54d7bc48 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -708,6 +708,55 @@ SELECT * FROM fract_only; (6 rows) DROP TABLE fract_only; +-- Check conversion to integers +SELECT (-9223372036854775808.5)::int8; -- should fail +ERROR: bigint out of range +SELECT (-9223372036854775808.4)::int8; -- ok + int8 +---------------------- + -9223372036854775808 +(1 row) + +SELECT 9223372036854775807.4::int8; -- ok + int8 +--------------------- + 9223372036854775807 +(1 row) + +SELECT 9223372036854775807.5::int8; -- should fail +ERROR: bigint out of range +SELECT (-2147483648.5)::int4; -- should fail +ERROR: integer out of range +SELECT (-2147483648.4)::int4; -- ok + int4 +------------- + -2147483648 +(1 row) + +SELECT 2147483647.4::int4; -- ok + int4 +------------ + 2147483647 +(1 row) + +SELECT 2147483647.5::int4; -- should fail +ERROR: integer out of range +SELECT (-32768.5)::int2; -- should fail +ERROR: smallint out of range +SELECT (-32768.4)::int2; -- ok + int2 +-------- + -32768 +(1 row) + +SELECT 32767.4::int2; -- ok + int2 +------- + 32767 +(1 row) + +SELECT 32767.5::int2; -- should fail +ERROR: smallint out of range -- Check inf/nan conversion behavior SELECT 'NaN'::float8::numeric; numeric @@ -1719,10 +1768,10 @@ select 1.000000000123 ^ (-2147483648); 0.7678656556403084 (1 row) -select 0.9999999999 ^ 23300000000000 = 0 as rounds_to_zero; +select coalesce(nullif(0.9999999999 ^ 23300000000000, 0), 0) as rounds_to_zero; rounds_to_zero ---------------- - t + 0 (1 row) -- cases that used to error out @@ -1738,10 +1787,10 @@ select 0.5678 ^ (-85); 782333637740774446257.7719390061997396 (1 row) -select 0.9999999999 ^ 70000000000000 = 0 as underflows; +select coalesce(nullif(0.9999999999 ^ 70000000000000, 0), 0) as underflows; underflows ------------ - t + 0 (1 row) -- negative base to integer powers @@ -1893,16 +1942,16 @@ select exp(1.0::numeric(71,70)); 2.7182818284590452353602874713526624977572470936999595749669676277240766 (1 row) -select exp(-5000::numeric) = 0 as rounds_to_zero; +select coalesce(nullif(exp(-5000::numeric), 0), 0) as rounds_to_zero; rounds_to_zero ---------------- - t + 0 (1 row) -select exp(-10000::numeric) = 0 as underflows; +select coalesce(nullif(exp(-10000::numeric), 0), 0) as underflows; underflows ------------ - t + 0 (1 row) -- cases that used to generate inaccurate results diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 1697a148ef..c0388691e5 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -655,6 +655,20 @@ INSERT INTO fract_only VALUES (8, '0.00017'); SELECT * FROM fract_only; DROP TABLE fract_only; +-- Check conversion to integers +SELECT (-9223372036854775808.5)::int8; -- should fail +SELECT (-9223372036854775808.4)::int8; -- ok +SELECT 9223372036854775807.4::int8; -- ok +SELECT 9223372036854775807.5::int8; -- should fail +SELECT (-2147483648.5)::int4; -- should fail +SELECT (-2147483648.4)::int4; -- ok +SELECT 2147483647.4::int4; -- ok +SELECT 2147483647.5::int4; -- should fail +SELECT (-32768.5)::int2; -- should fail +SELECT (-32768.4)::int2; -- ok +SELECT 32767.4::int2; -- ok +SELECT 32767.5::int2; -- should fail + -- Check inf/nan conversion behavior SELECT 'NaN'::float8::numeric; SELECT 'Infinity'::float8::numeric; @@ -920,12 +934,12 @@ select 3.789 ^ 35; select 1.2 ^ 345; select 0.12 ^ (-20); select 1.000000000123 ^ (-2147483648); -select 0.9999999999 ^ 23300000000000 = 0 as rounds_to_zero; +select coalesce(nullif(0.9999999999 ^ 23300000000000, 0), 0) as rounds_to_zero; -- cases that used to error out select 0.12 ^ (-25); select 0.5678 ^ (-85); -select 0.9999999999 ^ 70000000000000 = 0 as underflows; +select coalesce(nullif(0.9999999999 ^ 70000000000000, 0), 0) as underflows; -- negative base to integer powers select (-1.0) ^ 2147483646; @@ -972,8 +986,8 @@ select 1.234 ^ 5678; select exp(0.0); select exp(1.0); select exp(1.0::numeric(71,70)); -select exp(-5000::numeric) = 0 as rounds_to_zero; -select exp(-10000::numeric) = 0 as underflows; +select coalesce(nullif(exp(-5000::numeric), 0), 0) as rounds_to_zero; +select coalesce(nullif(exp(-10000::numeric), 0), 0) as underflows; -- cases that used to generate inaccurate results select exp(32.999);