]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/cash.c
Add support for EUI-64 MAC addresses as macaddr8
[postgresql] / src / backend / utils / adt / cash.c
index 84e8c742ec5f19354d6b1a4cff748fe8cac394bc..5afadb65d115606605607a08de48eb1930d72685 100644 (file)
@@ -13,7 +13,7 @@
  * this version handles 64 bit numbers and so can hold values up to
  * $92,233,720,368,547,758.07.
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/cash.c,v 1.78 2008/03/25 22:42:43 tgl Exp $
+ * src/backend/utils/adt/cash.c
  */
 
 #include "postgres.h"
 #include <limits.h>
 #include <ctype.h>
 #include <math.h>
-#include <locale.h>
 
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
 #include "utils/cash.h"
+#include "utils/int8.h"
+#include "utils/numeric.h"
 #include "utils/pg_locale.h"
 
-#define CASH_BUFSZ             36
-
-#define TERMINATOR             (CASH_BUFSZ - 1)
-#define LAST_PAREN             (TERMINATOR - 1)
-#define LAST_DIGIT             (LAST_PAREN - 1)
-
-/*
- * Cash is a pass-by-ref SQL type, so we must pass and return pointers.
- * These macros and support routine hide the pass-by-refness.
- */
-#define PG_GETARG_CASH(n)  (* ((Cash *) PG_GETARG_POINTER(n)))
-#define PG_RETURN_CASH(x)  return CashGetDatum(x)
-
-
 
 /*************************************************************************
  * Private routines
@@ -99,16 +86,6 @@ num_word(Cash value)
        return buf;
 }      /* num_word() */
 
-static Datum
-CashGetDatum(Cash value)
-{
-       Cash       *result = (Cash *) palloc(sizeof(Cash));
-
-       *result = value;
-       return PointerGetDatum(result);
-}
-
-
 /* cash_in()
  * Convert a string to a cash data type.
  * Format is [$]###[,]###[.##]
@@ -123,15 +100,14 @@ cash_in(PG_FUNCTION_ARGS)
        Cash            value = 0;
        Cash            dec = 0;
        Cash            sgn = 1;
-       int                     seen_dot = 0;
+       bool            seen_dot = false;
        const char *s = str;
        int                     fpoint;
-       char            dsymbol,
-                               ssymbol,
-                               psymbol;
-       const char *nsymbol,
+       char            dsymbol;
+       const char *ssymbol,
+                          *psymbol,
+                          *nsymbol,
                           *csymbol;
-
        struct lconv *lconvert = PGLC_localeconv();
 
        /*
@@ -148,18 +124,22 @@ cash_in(PG_FUNCTION_ARGS)
        if (fpoint < 0 || fpoint > 10)
                fpoint = 2;                             /* best guess in this case, I think */
 
-       dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
-       if (*lconvert->mon_thousands_sep != '\0')
-               ssymbol = *lconvert->mon_thousands_sep;
+       /* we restrict dsymbol to be a single byte, but not the other symbols */
+       if (*lconvert->mon_decimal_point != '\0' &&
+               lconvert->mon_decimal_point[1] == '\0')
+               dsymbol = *lconvert->mon_decimal_point;
        else
-               /* ssymbol should not equal dsymbol */
-               ssymbol = (dsymbol != ',') ? ',' : '.';
-       csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
-       psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
-       nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
+               dsymbol = '.';
+       if (*lconvert->mon_thousands_sep != '\0')
+               ssymbol = lconvert->mon_thousands_sep;
+       else    /* ssymbol should not equal dsymbol */
+               ssymbol = (dsymbol != ',') ? "," : ".";
+       csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
+       psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
+       nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
 
 #ifdef CASHDEBUG
-       printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
+       printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
                   fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
 #endif
 
@@ -169,6 +149,8 @@ cash_in(PG_FUNCTION_ARGS)
                s++;
        if (strncmp(s, csymbol, strlen(csymbol)) == 0)
                s += strlen(csymbol);
+       while (isspace((unsigned char) *s))
+               s++;
 
 #ifdef CASHDEBUG
        printf("cashin- string is '%s'\n", s);
@@ -181,79 +163,138 @@ cash_in(PG_FUNCTION_ARGS)
        {
                sgn = -1;
                s += strlen(nsymbol);
-#ifdef CASHDEBUG
-               printf("cashin- negative symbol; string is '%s'\n", s);
-#endif
        }
        else if (*s == '(')
        {
                sgn = -1;
                s++;
        }
-       else if (*s == psymbol)
-               s++;
+       else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
+               s += strlen(psymbol);
 
 #ifdef CASHDEBUG
        printf("cashin- string is '%s'\n", s);
 #endif
 
+       /* allow whitespace and currency symbol after the sign, too */
        while (isspace((unsigned char) *s))
                s++;
        if (strncmp(s, csymbol, strlen(csymbol)) == 0)
                s += strlen(csymbol);
+       while (isspace((unsigned char) *s))
+               s++;
 
 #ifdef CASHDEBUG
        printf("cashin- string is '%s'\n", s);
 #endif
 
-       for (;; s++)
+       /*
+        * We accumulate the absolute amount in "value" and then apply the sign at
+        * the end.  (The sign can appear before or after the digits, so it would
+        * be more complicated to do otherwise.)  Because of the larger range of
+        * negative signed integers, we build "value" in the negative and then
+        * flip the sign at the end, catching most-negative-number overflow if
+        * necessary.
+        */
+
+       for (; *s; s++)
        {
-               /* we look for digits as int8 as we have less */
+               /* we look for digits as long as we have found less */
                /* than the required number of decimal places */
-               if (isdigit((unsigned char) *s) && dec < fpoint)
+               if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
                {
-                       value = (value * 10) + *s - '0';
+                       Cash newvalue = (value * 10) - (*s - '0');
+
+                       if (newvalue / 10 != value)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                                errmsg("value \"%s\" is out of range for type %s",
+                                                               str, "money")));
+
+                       value = newvalue;
 
                        if (seen_dot)
                                dec++;
-
                }
                /* decimal point? then start counting fractions... */
                else if (*s == dsymbol && !seen_dot)
                {
-                       seen_dot = 1;
-
+                       seen_dot = true;
                }
-               /* not "thousands" separator? */
-               else if (*s != ssymbol)
-               {
-                       /* round off */
-                       if (isdigit((unsigned char) *s) && *s >= '5')
-                               value++;
+               /* ignore if "thousands" separator, else we're done */
+               else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
+                       s += strlen(ssymbol) - 1;
+               else
+                       break;
+       }
 
-                       /* adjust for less than required decimal places */
-                       for (; dec < fpoint; dec++)
-                               value *= 10;
+       /* round off if there's another digit */
+       if (isdigit((unsigned char) *s) && *s >= '5')
+               value--;  /* remember we build the value in the negative */
 
-                       break;
-               }
+       if (value > 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                errmsg("value \"%s\" is out of range for type %s",
+                                               str, "money")));
+
+       /* adjust for less than required decimal places */
+       for (; dec < fpoint; dec++)
+       {
+               Cash newvalue = value * 10;
+
+               if (newvalue / 10 != value)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                        errmsg("value \"%s\" is out of range for type %s",
+                                                       str, "money")));
+
+               value = newvalue;
        }
 
-       /* should only be trailing digits followed by whitespace or right paren */
+       /*
+        * should only be trailing digits followed by whitespace, right paren,
+        * trailing sign, and/or trailing currency symbol
+        */
        while (isdigit((unsigned char) *s))
                s++;
-       while (isspace((unsigned char) *s) || *s == ')')
-               s++;
 
-       if (*s != '\0')
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-                                errmsg("invalid input syntax for type money: \"%s\"", str)));
+       while (*s)
+       {
+               if (isspace((unsigned char) *s) || *s == ')')
+                       s++;
+               else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
+               {
+                       sgn = -1;
+                       s += strlen(nsymbol);
+               }
+               else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
+                       s += strlen(psymbol);
+               else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
+                       s += strlen(csymbol);
+               else
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                        errmsg("invalid input syntax for type %s: \"%s\"",
+                                                       "money", str)));
+       }
 
-       result = value * sgn;
+       /* If the value is supposed to be positive, flip the sign, but check for
+        * the most negative number. */
+       if (sgn > 0)
+       {
+               result = -value;
+               if (result < 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                        errmsg("value \"%s\" is out of range for type %s",
+                                                       str, "money")));
+       }
+       else
+               result = value;
 
 #ifdef CASHDEBUG
-       printf("cashin- result is %d\n", result);
+       printf("cashin- result is " INT64_FORMAT "\n", result);
 #endif
 
        PG_RETURN_CASH(result);
@@ -261,28 +302,26 @@ cash_in(PG_FUNCTION_ARGS)
 
 
 /* cash_out()
- * Function to convert cash to a dollars and cents representation.
- * XXX HACK This code appears to assume US conventions for
- *     positive-valued amounts. - tgl 97/04/14
+ * Function to convert cash to a dollars and cents representation, using
+ * the lc_monetary locale's formatting.
  */
 Datum
 cash_out(PG_FUNCTION_ARGS)
 {
        Cash            value = PG_GETARG_CASH(0);
        char       *result;
-       char            buf[CASH_BUFSZ];
-       int                     minus = 0;
-       int                     count = LAST_DIGIT;
-       int                     point_pos;
-       int                     ssymbol_position = 0;
+       char            buf[128];
+       char       *bufptr;
+       int                     digit_pos;
        int                     points,
                                mon_group;
-       char            ssymbol;
-       const char *csymbol,
-                          *nsymbol;
        char            dsymbol;
-       char            convention;
-
+       const char *ssymbol,
+                          *csymbol,
+                          *signsymbol;
+       char            sign_posn,
+                               cs_precedes,
+                               sep_by_space;
        struct lconv *lconvert = PGLC_localeconv();
 
        /* see comments about frac_digits in cash_in() */
@@ -298,71 +337,169 @@ cash_out(PG_FUNCTION_ARGS)
        if (mon_group <= 0 || mon_group > 6)
                mon_group = 3;
 
-       convention = lconvert->n_sign_posn;
-       dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
-       if (*lconvert->mon_thousands_sep != '\0')
-               ssymbol = *lconvert->mon_thousands_sep;
+       /* we restrict dsymbol to be a single byte, but not the other symbols */
+       if (*lconvert->mon_decimal_point != '\0' &&
+               lconvert->mon_decimal_point[1] == '\0')
+               dsymbol = *lconvert->mon_decimal_point;
        else
-               /* ssymbol should not equal dsymbol */
-               ssymbol = (dsymbol != ',') ? ',' : '.';
-       csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
-       nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
-
-       point_pos = LAST_DIGIT - points;
-
-       point_pos -= (points - 1) / mon_group;
-       ssymbol_position = point_pos % (mon_group + 1);
+               dsymbol = '.';
+       if (*lconvert->mon_thousands_sep != '\0')
+               ssymbol = lconvert->mon_thousands_sep;
+       else    /* ssymbol should not equal dsymbol */
+               ssymbol = (dsymbol != ',') ? "," : ".";
+       csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
 
-       /* we work with positive amounts and add the minus sign at the end */
        if (value < 0)
        {
-               minus = 1;
+               /* make the amount positive for digit-reconstruction loop */
                value = -value;
+               /* set up formatting data */
+               signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
+               sign_posn = lconvert->n_sign_posn;
+               cs_precedes = lconvert->n_cs_precedes;
+               sep_by_space = lconvert->n_sep_by_space;
        }
-
-       /* allow for trailing negative strings */
-       MemSet(buf, ' ', CASH_BUFSZ);
-       buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
-
-       while (value || count > (point_pos - 2))
+       else
        {
-               if (points && count == point_pos)
-                       buf[count--] = dsymbol;
-               else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
-                       buf[count--] = ssymbol;
-
-               buf[count--] = ((uint64) value % 10) + '0';
-               value = ((uint64) value) / 10;
+               signsymbol = lconvert->positive_sign;
+               sign_posn = lconvert->p_sign_posn;
+               cs_precedes = lconvert->p_cs_precedes;
+               sep_by_space = lconvert->p_sep_by_space;
        }
 
-       strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
-       count -= strlen(csymbol) - 1;
+       /* we build the digits+decimal-point+sep string right-to-left in buf[] */
+       bufptr = buf + sizeof(buf) - 1;
+       *bufptr = '\0';
 
        /*
-        *      If points == 0 and the number of digits % mon_group == 0,
-        *      the code above adds a trailing ssymbol on the far right,
-        *      so remove it.
+        * Generate digits till there are no non-zero digits left and we emitted
+        * at least one to the left of the decimal point.  digit_pos is the
+        * current digit position, with zero as the digit just left of the decimal
+        * point, increasing to the right.
         */
-       if (buf[LAST_DIGIT] == ssymbol)
-               buf[LAST_DIGIT] = '\0';
-
-       /* see if we need to signify negative amount */
-       if (minus)
+       digit_pos = points;
+       do
        {
-               result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
+               if (points && digit_pos == 0)
+               {
+                       /* insert decimal point, but not if value cannot be fractional */
+                       *(--bufptr) = dsymbol;
+               }
+               else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
+               {
+                       /* insert thousands sep, but only to left of radix point */
+                       bufptr -= strlen(ssymbol);
+                       memcpy(bufptr, ssymbol, strlen(ssymbol));
+               }
 
-               /* Position code of 0 means use parens */
-               if (convention == 0)
-                       sprintf(result, "(%s)", buf + count);
-               else if (convention == 2)
-                       sprintf(result, "%s%s", buf + count, nsymbol);
-               else
-                       sprintf(result, "%s%s", nsymbol, buf + count);
-       }
-       else
+               *(--bufptr) = ((uint64) value % 10) + '0';
+               value = ((uint64) value) / 10;
+               digit_pos--;
+       } while (value || digit_pos >= 0);
+
+       /*----------
+        * Now, attach currency symbol and sign symbol in the correct order.
+        *
+        * The POSIX spec defines these values controlling this code:
+        *
+        * p/n_sign_posn:
+        *      0       Parentheses enclose the quantity and the currency_symbol.
+        *      1       The sign string precedes the quantity and the currency_symbol.
+        *      2       The sign string succeeds the quantity and the currency_symbol.
+        *      3       The sign string precedes the currency_symbol.
+        *      4       The sign string succeeds the currency_symbol.
+        *
+        * p/n_cs_precedes: 0 means currency symbol after value, else before it.
+        *
+        * p/n_sep_by_space:
+        *      0       No <space> separates the currency symbol and value.
+        *      1       If the currency symbol and sign string are adjacent, a <space>
+        *              separates them from the value; otherwise, a <space> separates
+        *              the currency symbol from the value.
+        *      2       If the currency symbol and sign string are adjacent, a <space>
+        *              separates them; otherwise, a <space> separates the sign string
+        *              from the value.
+        *----------
+        */
+       switch (sign_posn)
        {
-               result = palloc(CASH_BUFSZ + 2 - count);
-               strcpy(result, buf + count);
+               case 0:
+                       if (cs_precedes)
+                               result = psprintf("(%s%s%s)",
+                                                                 csymbol,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 bufptr);
+                       else
+                               result = psprintf("(%s%s%s)",
+                                                                 bufptr,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 csymbol);
+                       break;
+               case 1:
+               default:
+                       if (cs_precedes)
+                               result = psprintf("%s%s%s%s%s",
+                                                                 signsymbol,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 csymbol,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 bufptr);
+                       else
+                               result = psprintf("%s%s%s%s%s",
+                                                                 signsymbol,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 bufptr,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 csymbol);
+                       break;
+               case 2:
+                       if (cs_precedes)
+                               result = psprintf("%s%s%s%s%s",
+                                                                 csymbol,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 bufptr,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 signsymbol);
+                       else
+                               result = psprintf("%s%s%s%s%s",
+                                                                 bufptr,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 csymbol,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 signsymbol);
+                       break;
+               case 3:
+                       if (cs_precedes)
+                               result = psprintf("%s%s%s%s%s",
+                                                                 signsymbol,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 csymbol,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 bufptr);
+                       else
+                               result = psprintf("%s%s%s%s%s",
+                                                                 bufptr,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 signsymbol,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 csymbol);
+                       break;
+               case 4:
+                       if (cs_precedes)
+                               result = psprintf("%s%s%s%s%s",
+                                                                 csymbol,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 signsymbol,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 bufptr);
+                       else
+                               result = psprintf("%s%s%s%s%s",
+                                                                 bufptr,
+                                                                 (sep_by_space == 1) ? " " : "",
+                                                                 csymbol,
+                                                                 (sep_by_space == 2) ? " " : "",
+                                                                 signsymbol);
+                       break;
        }
 
        PG_RETURN_CSTRING(result);
@@ -498,6 +635,26 @@ cash_mi(PG_FUNCTION_ARGS)
 }
 
 
+/* 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);
+}
+
+
 /* cash_mul_flt8()
  * Multiply cash by float8.
  */
@@ -780,7 +937,7 @@ cashsmaller(PG_FUNCTION_ARGS)
 }
 
 /* cash_words()
- * This converts a int4 as well but to a representation using words
+ * This converts an int4 as well but to a representation using words
  * Obviously way North American centric - sorry
  */
 Datum
@@ -811,13 +968,13 @@ cash_words(PG_FUNCTION_ARGS)
        /* Now treat as unsigned, to avoid trouble at INT_MIN */
        val = (uint64) value;
 
-       m0 = val % 100ll;                       /* cents */
-       m1 = (val / 100ll) % 1000;      /* hundreds */
-       m2 = (val / 100000ll) % 1000;           /* thousands */
-       m3 = val / 100000000ll % 1000;          /* millions */
-       m4 = val / 100000000000ll % 1000;       /* billions */
-       m5 = val / 100000000000000ll % 1000;            /* trillions */
-       m6 = val / 100000000000000000ll % 1000;         /* quadrillions */
+       m0 = val % INT64CONST(100); /* cents */
+       m1 = (val / INT64CONST(100)) % 1000;            /* hundreds */
+       m2 = (val / INT64CONST(100000)) % 1000;         /* thousands */
+       m3 = (val / INT64CONST(100000000)) % 1000;      /* millions */
+       m4 = (val / INT64CONST(100000000000)) % 1000;           /* billions */
+       m5 = (val / INT64CONST(100000000000000)) % 1000;        /* trillions */
+       m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
 
        if (m6)
        {
@@ -865,3 +1022,137 @@ cash_words(PG_FUNCTION_ARGS)
        /* return as text datum */
        PG_RETURN_TEXT_P(cstring_to_text(buf));
 }
+
+
+/* cash_numeric()
+ * Convert cash to numeric.
+ */
+Datum
+cash_numeric(PG_FUNCTION_ARGS)
+{
+       Cash            money = PG_GETARG_CASH(0);
+       Numeric         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() */
+       fpoint = lconvert->frac_digits;
+       if (fpoint < 0 || fpoint > 10)
+               fpoint = 2;
+
+       /* compute required scale factor */
+       scale = 1;
+       for (i = 0; i < fpoint; i++)
+               scale *= 10;
+
+       /* 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);
+
+       /* forcibly round to exactly the intended number of digits */
+       result = DatumGetNumeric(DirectFunctionCall2(numeric_round,
+                                                                                                quotient,
+                                                                                                Int32GetDatum(fpoint)));
+
+       PG_RETURN_NUMERIC(result);
+}
+
+/* numeric_cash()
+ * Convert numeric to cash.
+ */
+Datum
+numeric_cash(PG_FUNCTION_ARGS)
+{
+       Datum           amount = PG_GETARG_DATUM(0);
+       Cash            result;
+       int                     fpoint;
+       int64           scale;
+       int                     i;
+       Datum           numeric_scale;
+       struct lconv *lconvert = PGLC_localeconv();
+
+       /* see comments about frac_digits in cash_in() */
+       fpoint = lconvert->frac_digits;
+       if (fpoint < 0 || fpoint > 10)
+               fpoint = 2;
+
+       /* compute required scale factor */
+       scale = 1;
+       for (i = 0; i < fpoint; i++)
+               scale *= 10;
+
+       /* multiply the input amount by scale factor */
+       numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale));
+       amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
+
+       /* note that numeric_int8 will round to nearest integer for us */
+       result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
+
+       PG_RETURN_CASH(result);
+}
+
+/* int4_cash()
+ * Convert int4 (int) to cash
+ */
+Datum
+int4_cash(PG_FUNCTION_ARGS)
+{
+       int32           amount = PG_GETARG_INT32(0);
+       Cash            result;
+       int                     fpoint;
+       int64           scale;
+       int                     i;
+       struct lconv *lconvert = PGLC_localeconv();
+
+       /* see comments about frac_digits in cash_in() */
+       fpoint = lconvert->frac_digits;
+       if (fpoint < 0 || fpoint > 10)
+               fpoint = 2;
+
+       /* compute required scale factor */
+       scale = 1;
+       for (i = 0; i < fpoint; i++)
+               scale *= 10;
+
+       /* compute amount * scale, checking for overflow */
+       result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
+                                                                                          Int64GetDatum(scale)));
+
+       PG_RETURN_CASH(result);
+}
+
+/* int8_cash()
+ * Convert int8 (bigint) to cash
+ */
+Datum
+int8_cash(PG_FUNCTION_ARGS)
+{
+       int64           amount = PG_GETARG_INT64(0);
+       Cash            result;
+       int                     fpoint;
+       int64           scale;
+       int                     i;
+       struct lconv *lconvert = PGLC_localeconv();
+
+       /* see comments about frac_digits in cash_in() */
+       fpoint = lconvert->frac_digits;
+       if (fpoint < 0 || fpoint > 10)
+               fpoint = 2;
+
+       /* compute required scale factor */
+       scale = 1;
+       for (i = 0; i < fpoint; i++)
+               scale *= 10;
+
+       /* compute amount * scale, checking for overflow */
+       result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
+                                                                                          Int64GetDatum(scale)));
+
+       PG_RETURN_CASH(result);
+}