diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index 387bef50d62..29846610309 100644 --- a/src/backend/utils/adt/cash.c +++ b/src/backend/utils/adt/cash.c @@ -161,6 +161,22 @@ cash_div_int64(Cash c, int64 i) (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); + /* + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. + */ + if (i == -1) + { + if (unlikely(c == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); + return -c; + } + + /* No overflow is possible */ return c / i; } diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out index 90140f66767..7cebe8d2efb 100644 --- a/src/test/regress/expected/money.out +++ b/src/test/regress/expected/money.out @@ -514,6 +514,14 @@ SELECT '-1'::money / 1.175494e-38::float4; ERROR: money out of range SELECT '92233720368547758.07'::money * 2::int4; ERROR: money out of range +SELECT '-92233720368547758.08'::money * -1::int8; +ERROR: money out of range +SELECT '-92233720368547758.08'::money / -1::int8; +ERROR: money out of range +SELECT '-92233720368547758.08'::money / -1::int4; +ERROR: money out of range +SELECT '-92233720368547758.08'::money / -1::int2; +ERROR: money out of range SELECT '1'::money / 0::int2; ERROR: division by zero SELECT '42'::money * 'inf'::float8; diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql index 1eb471d1e4e..6f8f7263a59 100644 --- a/src/test/regress/sql/money.sql +++ b/src/test/regress/sql/money.sql @@ -136,6 +136,10 @@ SELECT '-92233720368547758.08'::money - '0.01'::money; SELECT '92233720368547758.07'::money * 2::float8; SELECT '-1'::money / 1.175494e-38::float4; SELECT '92233720368547758.07'::money * 2::int4; +SELECT '-92233720368547758.08'::money * -1::int8; +SELECT '-92233720368547758.08'::money / -1::int8; +SELECT '-92233720368547758.08'::money / -1::int4; +SELECT '-92233720368547758.08'::money / -1::int2; SELECT '1'::money / 0::int2; SELECT '42'::money * 'inf'::float8; SELECT '42'::money * '-inf'::float8;