Logo Search packages:      
Sourcecode: postgresql-8.4 version File versions  Download package

cash.c

/*
 * cash.c
 * Written by D'Arcy J.M. Cain
 * darcy@druid.net
 * http://www.druid.net/darcy/
 *
 * Functions to allow input and output of money normally but store
 * and handle it as 64 bit ints
 *
 * A slightly modified version of this file and a discussion of the
 * workings can be found in the book "Software Solutions in C" by
 * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that
 * 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.82 2009/06/11 14:49:03 momjian Exp $
 */

#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/pg_locale.h"

#define CASH_BUFSZ            36

#define TERMINATOR            (CASH_BUFSZ - 1)
#define LAST_PAREN            (TERMINATOR - 1)
#define LAST_DIGIT            (LAST_PAREN - 1)


/*************************************************************************
 * Private routines
 ************************************************************************/

static const char *
num_word(Cash value)
{
      static char buf[128];
      static const char *small[] = {
            "zero", "one", "two", "three", "four", "five", "six", "seven",
            "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
            "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
            "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
      };
      const char **big = small + 18;
      int               tu = value % 100;

      /* deal with the simple cases first */
      if (value <= 20)
            return small[value];

      /* is it an even multiple of 100? */
      if (!tu)
      {
            sprintf(buf, "%s hundred", small[value / 100]);
            return buf;
      }

      /* more than 99? */
      if (value > 99)
      {
            /* is it an even multiple of 10 other than 10? */
            if (value % 10 == 0 && tu > 10)
                  sprintf(buf, "%s hundred %s",
                              small[value / 100], big[tu / 10]);
            else if (tu < 20)
                  sprintf(buf, "%s hundred and %s",
                              small[value / 100], small[tu]);
            else
                  sprintf(buf, "%s hundred %s %s",
                              small[value / 100], big[tu / 10], small[tu % 10]);
      }
      else
      {
            /* is it an even multiple of 10 other than 10? */
            if (value % 10 == 0 && tu > 10)
                  sprintf(buf, "%s", big[tu / 10]);
            else if (tu < 20)
                  sprintf(buf, "%s", small[tu]);
            else
                  sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
      }

      return buf;
}     /* num_word() */


/* cash_in()
 * Convert a string to a cash data type.
 * Format is [$]###[,]###[.##]
 * Examples: 123.45 $123.45 $123,456.78
 *
 */
Datum
cash_in(PG_FUNCTION_ARGS)
{
      char     *str = PG_GETARG_CSTRING(0);
      Cash        result;
      Cash        value = 0;
      Cash        dec = 0;
      Cash        sgn = 1;
      int               seen_dot = 0;
      const char *s = str;
      int               fpoint;
      char        dsymbol,
                        ssymbol,
                        psymbol;
      const char *nsymbol,
                     *csymbol;

      struct lconv *lconvert = PGLC_localeconv();

      /*
       * frac_digits will be CHAR_MAX in some locales, notably C.  However, just
       * testing for == CHAR_MAX is risky, because of compilers like gcc that
       * "helpfully" let you alter the platform-standard definition of whether
       * char is signed or not.  If we are so unfortunate as to get compiled
       * with a nonstandard -fsigned-char or -funsigned-char switch, then our
       * idea of CHAR_MAX will not agree with libc's. The safest course is not
       * to test for CHAR_MAX at all, but to impose a range check for plausible
       * frac_digits values.
       */
      fpoint = lconvert->frac_digits;
      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;
      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",
               fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
#endif

      /* we need to add all sorts of checking here.  For now just */
      /* strip all leading whitespace and any leading currency symbol */
      while (isspace((unsigned char) *s))
            s++;
      if (strncmp(s, csymbol, strlen(csymbol)) == 0)
            s += strlen(csymbol);

#ifdef CASHDEBUG
      printf("cashin- string is '%s'\n", s);
#endif

      /* a leading minus or paren signifies a negative number */
      /* again, better heuristics needed */
      /* XXX - doesn't properly check for balanced parens - djmc */
      if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
      {
            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++;

#ifdef CASHDEBUG
      printf("cashin- string is '%s'\n", s);
#endif

      while (isspace((unsigned char) *s))
            s++;
      if (strncmp(s, csymbol, strlen(csymbol)) == 0)
            s += strlen(csymbol);

#ifdef CASHDEBUG
      printf("cashin- string is '%s'\n", s);
#endif

      for (;; s++)
      {
            /* we look for digits as long as we have found less */
            /* than the required number of decimal places */
            if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
            {
                  value = (value * 10) + (*s - '0');

                  if (seen_dot)
                        dec++;
            }
            /* decimal point? then start counting fractions... */
            else if (*s == dsymbol && !seen_dot)
            {
                  seen_dot = 1;
            }
            /* ignore if "thousands" separator, else we're done */
            else if (*s != ssymbol)
            {
                  /* round off */
                  if (isdigit((unsigned char) *s) && *s >= '5')
                        value++;

                  /* adjust for less than required decimal places */
                  for (; dec < fpoint; dec++)
                        value *= 10;

                  break;
            }
      }

      /* should only be trailing digits followed by whitespace or right paren */
      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)));

      result = value * sgn;

#ifdef CASHDEBUG
      printf("cashin- result is " INT64_FORMAT "\n", result);
#endif

      PG_RETURN_CASH(result);
}


/* 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
 */
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;
      int               points,
                        mon_group;
      char        ssymbol;
      const char *csymbol,
                     *nsymbol;
      char        dsymbol;
      char        convention;

      struct lconv *lconvert = PGLC_localeconv();

      /* see comments about frac_digits in cash_in() */
      points = lconvert->frac_digits;
      if (points < 0 || points > 10)
            points = 2;                   /* best guess in this case, I think */

      /*
       * As with frac_digits, must apply a range check to mon_grouping to avoid
       * being fooled by variant CHAR_MAX values.
       */
      mon_group = *lconvert->mon_grouping;
      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;
      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);

      /* we work with positive amounts and add the minus sign at the end */
      if (value < 0)
      {
            minus = 1;
            value = -value;
      }

      /* allow for trailing negative strings */
      MemSet(buf, ' ', CASH_BUFSZ);
      buf[TERMINATOR] = buf[LAST_PAREN] = '\0';

      while (value || count > (point_pos - 2))
      {
            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;
      }

      strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
      count -= strlen(csymbol) - 1;

      /*
       * 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.
       */
      if (buf[LAST_DIGIT] == ssymbol)
            buf[LAST_DIGIT] = '\0';

      /* see if we need to signify negative amount */
      if (minus)
      {
            result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));

            /* 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
      {
            result = palloc(CASH_BUFSZ + 2 - count);
            strcpy(result, buf + count);
      }

      PG_RETURN_CSTRING(result);
}

/*
 *          cash_recv               - converts external binary format to cash
 */
Datum
cash_recv(PG_FUNCTION_ARGS)
{
      StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);

      PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
}

/*
 *          cash_send               - converts cash to binary format
 */
Datum
cash_send(PG_FUNCTION_ARGS)
{
      Cash        arg1 = PG_GETARG_CASH(0);
      StringInfoData buf;

      pq_begintypsend(&buf);
      pq_sendint64(&buf, arg1);
      PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}

/*
 * Comparison functions
 */

Datum
cash_eq(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);

      PG_RETURN_BOOL(c1 == c2);
}

Datum
cash_ne(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);

      PG_RETURN_BOOL(c1 != c2);
}

Datum
cash_lt(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);

      PG_RETURN_BOOL(c1 < c2);
}

Datum
cash_le(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);

      PG_RETURN_BOOL(c1 <= c2);
}

Datum
cash_gt(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);

      PG_RETURN_BOOL(c1 > c2);
}

Datum
cash_ge(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);

      PG_RETURN_BOOL(c1 >= c2);
}

Datum
cash_cmp(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);

      if (c1 > c2)
            PG_RETURN_INT32(1);
      else if (c1 == c2)
            PG_RETURN_INT32(0);
      else
            PG_RETURN_INT32(-1);
}


/* cash_pl()
 * Add two cash values.
 */
Datum
cash_pl(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);
      Cash        result;

      result = c1 + c2;

      PG_RETURN_CASH(result);
}


/* cash_mi()
 * Subtract two cash values.
 */
Datum
cash_mi(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);
      Cash        result;

      result = c1 - c2;

      PG_RETURN_CASH(result);
}


/* cash_mul_flt8()
 * Multiply cash by float8.
 */
Datum
cash_mul_flt8(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      float8            f = PG_GETARG_FLOAT8(1);
      Cash        result;

      result = c * f;
      PG_RETURN_CASH(result);
}


/* flt8_mul_cash()
 * Multiply float8 by cash.
 */
Datum
flt8_mul_cash(PG_FUNCTION_ARGS)
{
      float8            f = PG_GETARG_FLOAT8(0);
      Cash        c = PG_GETARG_CASH(1);
      Cash        result;

      result = f * c;
      PG_RETURN_CASH(result);
}


/* cash_div_flt8()
 * Divide cash by float8.
 */
Datum
cash_div_flt8(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      float8            f = PG_GETARG_FLOAT8(1);
      Cash        result;

      if (f == 0.0)
            ereport(ERROR,
                        (errcode(ERRCODE_DIVISION_BY_ZERO),
                         errmsg("division by zero")));

      result = rint(c / f);
      PG_RETURN_CASH(result);
}


/* cash_mul_flt4()
 * Multiply cash by float4.
 */
Datum
cash_mul_flt4(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      float4            f = PG_GETARG_FLOAT4(1);
      Cash        result;

      result = c * f;
      PG_RETURN_CASH(result);
}


/* flt4_mul_cash()
 * Multiply float4 by cash.
 */
Datum
flt4_mul_cash(PG_FUNCTION_ARGS)
{
      float4            f = PG_GETARG_FLOAT4(0);
      Cash        c = PG_GETARG_CASH(1);
      Cash        result;

      result = f * c;
      PG_RETURN_CASH(result);
}


/* cash_div_flt4()
 * Divide cash by float4.
 *
 */
Datum
cash_div_flt4(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      float4            f = PG_GETARG_FLOAT4(1);
      Cash        result;

      if (f == 0.0)
            ereport(ERROR,
                        (errcode(ERRCODE_DIVISION_BY_ZERO),
                         errmsg("division by zero")));

      result = rint(c / f);
      PG_RETURN_CASH(result);
}


/* cash_mul_int8()
 * Multiply cash by int8.
 */
Datum
cash_mul_int8(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      int64       i = PG_GETARG_INT64(1);
      Cash        result;

      result = c * i;
      PG_RETURN_CASH(result);
}


/* int8_mul_cash()
 * Multiply int8 by cash.
 */
Datum
int8_mul_cash(PG_FUNCTION_ARGS)
{
      int64       i = PG_GETARG_INT64(0);
      Cash        c = PG_GETARG_CASH(1);
      Cash        result;

      result = i * c;
      PG_RETURN_CASH(result);
}

/* cash_div_int8()
 * Divide cash by 8-byte integer.
 */
Datum
cash_div_int8(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      int64       i = PG_GETARG_INT64(1);
      Cash        result;

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

      result = rint(c / i);

      PG_RETURN_CASH(result);
}


/* cash_mul_int4()
 * Multiply cash by int4.
 */
Datum
cash_mul_int4(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      int32       i = PG_GETARG_INT32(1);
      Cash        result;

      result = c * i;
      PG_RETURN_CASH(result);
}


/* int4_mul_cash()
 * Multiply int4 by cash.
 */
Datum
int4_mul_cash(PG_FUNCTION_ARGS)
{
      int32       i = PG_GETARG_INT32(0);
      Cash        c = PG_GETARG_CASH(1);
      Cash        result;

      result = i * c;
      PG_RETURN_CASH(result);
}


/* cash_div_int4()
 * Divide cash by 4-byte integer.
 *
 */
Datum
cash_div_int4(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      int32       i = PG_GETARG_INT32(1);
      Cash        result;

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

      result = rint(c / i);

      PG_RETURN_CASH(result);
}


/* cash_mul_int2()
 * Multiply cash by int2.
 */
Datum
cash_mul_int2(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      int16       s = PG_GETARG_INT16(1);
      Cash        result;

      result = c * s;
      PG_RETURN_CASH(result);
}

/* int2_mul_cash()
 * Multiply int2 by cash.
 */
Datum
int2_mul_cash(PG_FUNCTION_ARGS)
{
      int16       s = PG_GETARG_INT16(0);
      Cash        c = PG_GETARG_CASH(1);
      Cash        result;

      result = s * c;
      PG_RETURN_CASH(result);
}

/* cash_div_int2()
 * Divide cash by int2.
 *
 */
Datum
cash_div_int2(PG_FUNCTION_ARGS)
{
      Cash        c = PG_GETARG_CASH(0);
      int16       s = PG_GETARG_INT16(1);
      Cash        result;

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

      result = rint(c / s);
      PG_RETURN_CASH(result);
}

/* cashlarger()
 * Return larger of two cash values.
 */
Datum
cashlarger(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);
      Cash        result;

      result = (c1 > c2) ? c1 : c2;

      PG_RETURN_CASH(result);
}

/* cashsmaller()
 * Return smaller of two cash values.
 */
Datum
cashsmaller(PG_FUNCTION_ARGS)
{
      Cash        c1 = PG_GETARG_CASH(0);
      Cash        c2 = PG_GETARG_CASH(1);
      Cash        result;

      result = (c1 < c2) ? c1 : c2;

      PG_RETURN_CASH(result);
}

/* cash_words()
 * This converts a int4 as well but to a representation using words
 * Obviously way North American centric - sorry
 */
Datum
cash_words(PG_FUNCTION_ARGS)
{
      Cash        value = PG_GETARG_CASH(0);
      uint64            val;
      char        buf[256];
      char     *p = buf;
      Cash        m0;
      Cash        m1;
      Cash        m2;
      Cash        m3;
      Cash        m4;
      Cash        m5;
      Cash        m6;

      /* work with positive numbers */
      if (value < 0)
      {
            value = -value;
            strcpy(buf, "minus ");
            p += 6;
      }
      else
            buf[0] = '\0';

      /* Now treat as unsigned, to avoid trouble at INT_MIN */
      val = (uint64) value;

      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)
      {
            strcat(buf, num_word(m6));
            strcat(buf, " quadrillion ");
      }

      if (m5)
      {
            strcat(buf, num_word(m5));
            strcat(buf, " trillion ");
      }

      if (m4)
      {
            strcat(buf, num_word(m4));
            strcat(buf, " billion ");
      }

      if (m3)
      {
            strcat(buf, num_word(m3));
            strcat(buf, " million ");
      }

      if (m2)
      {
            strcat(buf, num_word(m2));
            strcat(buf, " thousand ");
      }

      if (m1)
            strcat(buf, num_word(m1));

      if (!*p)
            strcat(buf, "zero");

      strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
      strcat(buf, num_word(m0));
      strcat(buf, m0 == 1 ? " cent" : " cents");

      /* capitalize output */
      buf[0] = pg_toupper((unsigned char) buf[0]);

      /* return as text datum */
      PG_RETURN_TEXT_P(cstring_to_text(buf));
}

Generated by  Doxygen 1.6.0   Back to index