Re: dividing money by money - Mailing list pgsql-bugs

From Andy Balholm
Subject Re: dividing money by money
Date
Msg-id 7D642D1E-18A0-42B7-8666-18A242955402@balholm.com
Whole thread Raw
In response to Re: dividing money by money  ("Kevin Grittner" <Kevin.Grittner@wicourts.gov>)
Responses Re: dividing money by money  (Tom Lane <tgl@sss.pgh.pa.us>)
List 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 res=
ult in an infinite loop.) So I went back to my float8 version and wrote a c=
ast 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 =3D PG_GETARG_CASH(0);
    Cash    divisor  =3D PG_GETARG_CASH(1);
    float8  quotient;
=20=20=20=20
    if (divisor =3D=3D 0)
        ereport(ERROR,
                (errcode(ERRCODE_DIVISION_BY_ZERO),
                 errmsg("division by zero")));
=20=20=20=20
    quotient =3D (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 =3D PG_GETARG_CASH(0);
    int     fpoint;
    int64   scale;
    int     i;
    Numeric result;
    Datum   amount;
    Datum   numeric_scale;
    Datum   one;
=20=20=20=20
    struct lconv *lconvert =3D PGLC_localeconv();
=20=20=20=20
    /*=20
     * Find the number of digits after the decimal point.
     * (These lines were copied from cash_in().)
     */
    fpoint =3D lconvert->frac_digits;
    if (fpoint < 0 || fpoint > 10)
        fpoint =3D 2;
    scale =3D 1;
    for (i =3D 0; i < fpoint; i++)=20
        scale *=3D 10;
=20=20=20=20
    amount        =3D DirectFunctionCall1(&int8_numeric, Int64GetDatum(mone=
y));
    one           =3D DirectFunctionCall1(&int8_numeric, Int64GetDatum(1));
    numeric_scale =3D DirectFunctionCall1(&int8_numeric, Int64GetDatum(scal=
e));
    numeric_scale =3D DirectFunctionCall2(&numeric_div, one, numeric_scale);
    result =3D DatumGetNumeric(DirectFunctionCall2(&numeric_mul, amount, nu=
meric_scale));
=20=20=20=20
    result->n_sign_dscale =3D NUMERIC_SIGN(result) | fpoint; /* Display the=
 right number of decimal digits. */
=20=20=20=20
    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 =3D cash_div_cash,
    LEFTARG =3D money,
    RIGHTARG =3D money
);

CREATE CAST (money AS numeric) WITH FUNCTION cash_numeric(money) AS ASSIGNM=
ENT;

pgsql-bugs by date:

Previous
From: Andy Balholm
Date:
Subject: Re: dividing money by money
Next
From: Tom Lane
Date:
Subject: Re: dividing money by money