Hi, Hackers! This continues BUG #19586 (money division overflow): https://www.postgresql.org/message-id/[email protected]
cash_div_int64() rejects division by zero, but not INT64_MIN / -1. On x86-64 that surfaces as a floating-point exception via SIGFPE; on aarch64 it silently returns INT64_MIN. Multiplication by -1 is already guarded (commit 4f96281587). The attached patch handles division by -1 as negation, matching int8div(), and raises "money out of range" when the dividend is PG_INT64_MIN. Regression tests cover int8/int4/int2. Tristan looked at the patch on -bugs and suggested posting it here. Patch attached.
From: Andrey Rachitskiy <[email protected]> Date: Thu, 30 Jul 2026 06:20:00 +0500 Subject: [PATCH] Fix money overflow for INT64_MIN / -1 cash_div_int64() already rejected division by zero, but not the two's-complement overflow case INT64_MIN / -1. On some platforms that raises SIGFPE (reported as a floating-point exception); on others it silently returns INT64_MIN. Handle division by -1 as negation, matching int8div(), and raise "money out of range" when the dividend is PG_INT64_MIN. Multiplication by -1 was already covered by pg_mul_s64_overflow() from commit 4f96281587. Author: Andrey Rachitskiy <[email protected]> Reported-by: Michael Malis <[email protected]> Discussion: https://www.postgresql.org/message-id/19586-bb603bf5ad9934dd%40postgresql.org --- src/backend/utils/adt/cash.c | 16 ++++++++++++++++ src/test/regress/expected/money.out | 8 ++++++++ src/test/regress/sql/money.sql | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index 4bf60085c61..310b3bb3fca 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 cc2ff4d96e8..e7fdb9b7d0d 100644 --- a/src/test/regress/expected/money.out +++ b/src/test/regress/expected/money.out @@ -539,6 +539,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 b888ec21c30..d769a211090 100644 --- a/src/test/regress/sql/money.sql +++ b/src/test/regress/sql/money.sql @@ -142,6 +142,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; -- 2.43.0
