From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
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 <pl0h0yp1@gmail.com>
Reported-by: Michael Malis <malis@pgrust.com>
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
