diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index c92e9d5046acba7296178f4c30d7dec2cba95d98..6515fc8ec6955dc8a9f001c2928ef9231b3c347c 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -1032,13 +1032,8 @@ Datum
 cash_numeric(PG_FUNCTION_ARGS)
 {
 	Cash		money = PG_GETARG_CASH(0);
-	Numeric		result;
+	Datum		result;
 	int			fpoint;
-	int64		scale;
-	int			i;
-	Datum		amount;
-	Datum		numeric_scale;
-	Datum		quotient;
 	struct lconv *lconvert = PGLC_localeconv();
 
 	/* see comments about frac_digits in cash_in() */
@@ -1046,22 +1041,45 @@ cash_numeric(PG_FUNCTION_ARGS)
 	if (fpoint < 0 || fpoint > 10)
 		fpoint = 2;
 
-	/* compute required scale factor */
-	scale = 1;
-	for (i = 0; i < fpoint; i++)
-		scale *= 10;
+	/* convert the integral money value to numeric */
+	result = DirectFunctionCall1(int8_numeric, Int64GetDatum(money));
 
-	/* form the result as money / scale */
-	amount = DirectFunctionCall1(int8_numeric, Int64GetDatum(money));
-	numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale));
-	quotient = DirectFunctionCall2(numeric_div, amount, numeric_scale);
+	/* scale appropriately, if needed */
+	if (fpoint > 0)
+	{
+		int64		scale;
+		int			i;
+		Datum		numeric_scale;
+		Datum		quotient;
+
+		/* compute required scale factor */
+		scale = 1;
+		for (i = 0; i < fpoint; i++)
+			scale *= 10;
+		numeric_scale = DirectFunctionCall1(int8_numeric,
+											Int64GetDatum(scale));
 
-	/* forcibly round to exactly the intended number of digits */
-	result = DatumGetNumeric(DirectFunctionCall2(numeric_round,
-												 quotient,
-												 Int32GetDatum(fpoint)));
+		/*
+		 * Given integral inputs approaching INT64_MAX, select_div_scale()
+		 * might choose a result scale of zero, causing loss of fractional
+		 * digits in the quotient.  We can ensure an exact result by setting
+		 * the dscale of either input to be at least as large as the desired
+		 * result scale.  numeric_round() will do that for us.
+		 */
+		numeric_scale = DirectFunctionCall2(numeric_round,
+											numeric_scale,
+											Int32GetDatum(fpoint));
+
+		/* Now we can safely divide ... */
+		quotient = DirectFunctionCall2(numeric_div, result, numeric_scale);
+
+		/* ... and forcibly round to exactly the intended number of digits */
+		result = DirectFunctionCall2(numeric_round,
+									 quotient,
+									 Int32GetDatum(fpoint));
+	}
 
-	PG_RETURN_NUMERIC(result);
+	PG_RETURN_DATUM(result);
 }
 
 /* numeric_cash()
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index ab86595fc0219343da3374c11303e7d2d435d7a8..fc71a72fed319b76848db757efb4092a54cb3a8d 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -1,6 +1,8 @@
 --
 -- MONEY
 --
+-- Note that we assume lc_monetary has been set to C.
+--
 CREATE TABLE money_data (m money);
 INSERT INTO money_data VALUES ('123');
 SELECT * FROM money_data;
@@ -476,7 +478,7 @@ SELECT (-12345678901234567)::numeric::money;
  -$12,345,678,901,234,567.00
 (1 row)
 
--- Cast from money
+-- Cast from money to numeric
 SELECT '12345678901234567'::money::numeric;
        numeric        
 ----------------------
@@ -489,3 +491,15 @@ SELECT '-12345678901234567'::money::numeric;
  -12345678901234567.00
 (1 row)
 
+SELECT '92233720368547758.07'::money::numeric;
+       numeric        
+----------------------
+ 92233720368547758.07
+(1 row)
+
+SELECT '-92233720368547758.08'::money::numeric;
+        numeric        
+-----------------------
+ -92233720368547758.08
+(1 row)
+
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 37b9ecce1fc4fea57bfc79d41ef99e7d80892d9a..5e746286c9079e0444c509d81e02e0359ce3e83a 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -1,6 +1,8 @@
 --
 -- MONEY
 --
+-- Note that we assume lc_monetary has been set to C.
+--
 
 CREATE TABLE money_data (m money);
 
@@ -122,6 +124,8 @@ SELECT (-1234567890)::int4::money;
 SELECT (-12345678901234567)::int8::money;
 SELECT (-12345678901234567)::numeric::money;
 
--- Cast from money
+-- Cast from money to numeric
 SELECT '12345678901234567'::money::numeric;
 SELECT '-12345678901234567'::money::numeric;
+SELECT '92233720368547758.07'::money::numeric;
+SELECT '-92233720368547758.08'::money::numeric;