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() */
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()
--
-- 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;
-$12,345,678,901,234,567.00
(1 row)
--- Cast from money
+-- Cast from money to numeric
SELECT '12345678901234567'::money::numeric;
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)
+
--
-- MONEY
--
+-- Note that we assume lc_monetary has been set to C.
+--
CREATE TABLE money_data (m 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;