Re: dividing money by money

From: Andy Balholm <andy(at)balholm(dot)com>
To: Kevin Grittner <Kevin(dot)Grittner(at)wicourts(dot)gov>
Cc: <pgsql-bugs(at)postgresql(dot)org>
Subject: Re: dividing money by money
Date: 2010-03-31 22:35:29
Message-ID: 7D642D1E-18A0-42B7-8666-18A242955402@balholm.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

> Or I guess we could leave this as you've written it and add support
> for a cast from money to numeric.

I tried rewriting my function to use numeric, but I discovered that numeric division is not exact. (Otherwise SELECT 1::numeric / 3::numeric would result in an infinite loop.) So I went back to my float8 version and wrote a cast from money to numeric.

Here is my C source code now:

#include <postgres.h>
#include <fmgr.h>
#include <utils/cash.h>
#include <utils/numeric.h>
#include <utils/pg_locale.h>

PG_MODULE_MAGIC;

extern Datum int8_numeric(PG_FUNCTION_ARGS);
extern Datum numeric_div(PG_FUNCTION_ARGS);
extern Datum numeric_mul(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(cash_div_cash);

/* cash_div_cash()
* Divide cash by cash, returning float8.
*/
Datum
cash_div_cash(PG_FUNCTION_ARGS)
{
Cash dividend = PG_GETARG_CASH(0);
Cash divisor = PG_GETARG_CASH(1);
float8 quotient;

if (divisor == 0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));

quotient = (float8)dividend / (float8)divisor;
PG_RETURN_FLOAT8(quotient);
}

PG_FUNCTION_INFO_V1(cash_numeric);

/* cash_numeric()
* Convert cash to numeric.
*/
Datum
cash_numeric(PG_FUNCTION_ARGS)
{
Cash money = PG_GETARG_CASH(0);
int fpoint;
int64 scale;
int i;
Numeric result;
Datum amount;
Datum numeric_scale;
Datum one;

struct lconv *lconvert = PGLC_localeconv();

/*
* Find the number of digits after the decimal point.
* (These lines were copied from cash_in().)
*/
fpoint = lconvert->frac_digits;
if (fpoint < 0 || fpoint > 10)
fpoint = 2;
scale = 1;
for (i = 0; i < fpoint; i++)
scale *= 10;

amount = DirectFunctionCall1(&int8_numeric, Int64GetDatum(money));
one = DirectFunctionCall1(&int8_numeric, Int64GetDatum(1));
numeric_scale = DirectFunctionCall1(&int8_numeric, Int64GetDatum(scale));
numeric_scale = DirectFunctionCall2(&numeric_div, one, numeric_scale);
result = DatumGetNumeric(DirectFunctionCall2(&numeric_mul, amount, numeric_scale));

result->n_sign_dscale = NUMERIC_SIGN(result) | fpoint; /* Display the right number of decimal digits. */

PG_RETURN_NUMERIC(result);
}

------------------------------------------------------------------------------------

And here is the SQL it takes to load it:

CREATE FUNCTION cash_div_cash(money, money) RETURNS double precision
LANGUAGE c IMMUTABLE STRICT
AS '$libdir/divide_money', 'cash_div_cash';

CREATE FUNCTION cash_numeric(money) RETURNS numeric
LANGUAGE c IMMUTABLE STRICT
AS '$libdir/divide_money', 'cash_numeric';

CREATE OPERATOR / (
PROCEDURE = cash_div_cash,
LEFTARG = money,
RIGHTARG = money
);

CREATE CAST (money AS numeric) WITH FUNCTION cash_numeric(money) AS ASSIGNMENT;

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Tom Lane 2010-03-31 22:50:08 Re: dividing money by money
Previous Message Andy Balholm 2010-03-31 18:30:38 Re: dividing money by money